Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Brace/better quick actions #207

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
"tailwind-merge": "^2.5.2",
"tailwind-scrollbar-hide": "^1.1.7",
"tailwindcss-animate": "^1.0.7",
"usehooks-ts": "^3.1.0",
"uuid": "^10.0.0",
"zod": "^3.23.8"
},
Expand Down
17 changes: 13 additions & 4 deletions src/agent/open-canvas/nodes/rewrite-artifact/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@ import { buildPrompt, createNewArtifactContent, validateState } from "./utils";
import {
getFormattedReflections,
getModelFromConfig,
getModelConfig,
optionallyGetSystemPromptFromConfig,
} from "@/agent/utils";
import { isArtifactMarkdownContent } from "@/lib/artifact_content_types";
import { getArtifactContent } from "@/contexts/utils";

export const rewriteArtifact = async (
state: typeof OpenCanvasGraphAnnotation.State,
config: LangGraphRunnableConfig
): Promise<OpenCanvasGraphReturnType> => {
const { modelProvider } = getModelConfig(config);
const smallModelWithConfig = (await getModelFromConfig(config)).withConfig({
runName: "rewrite_artifact_model_call",
});
Expand Down Expand Up @@ -45,10 +48,16 @@ export const rewriteArtifact = async (
? `${userSystemPrompt}\n${formattedPrompt}`
: formattedPrompt;

const newArtifactResponse = await smallModelWithConfig.invoke([
{ role: "system", content: fullSystemPrompt },
recentHumanMessage,
]);
const prediction: Record<string, any> = {
prediction: {
type: "content",
content: state.artifact ? getArtifactContent(state.artifact) : undefined,
},
};
const newArtifactResponse = await smallModelWithConfig.invoke(
[{ role: "system", content: fullSystemPrompt }, recentHumanMessage],
modelProvider === "openai" ? prediction : undefined
);

const newArtifactContent = createNewArtifactContent({
artifactType,
Expand Down
206 changes: 81 additions & 125 deletions src/components/artifacts/ArtifactRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,8 @@ import {
} from "@/types";
import { EditorView } from "@codemirror/view";
import { HumanMessage } from "@langchain/core/messages";
import { Forward, LoaderCircle, CircleCheck } from "lucide-react";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { v4 as uuidv4 } from "uuid";
import { ReflectionsDialog } from "../reflections-dialog/ReflectionsDialog";
import { TooltipIconButton } from "../ui/assistant-ui/tooltip-icon-button";
import { ActionsToolbar, CodeToolBar } from "./actions_toolbar";
import { CodeRenderer } from "./CodeRenderer";
import { TextRenderer } from "./TextRenderer";
Expand All @@ -20,6 +17,7 @@ import { getArtifactContent } from "@/contexts/utils";
import { ArtifactLoading } from "./ArtifactLoading";
import { AskOpenCanvas } from "./components/AskOpenCanvas";
import { useGraphContext } from "@/contexts/GraphContext";
import { ArtifactHeader } from "./header";

export interface ArtifactRendererProps {
isEditing: boolean;
Expand All @@ -32,88 +30,6 @@ interface SelectionBox {
text: string;
}

interface ArtifactTitleProps {
title: string;
isArtifactSaved: boolean;
}

function ArtifactTitle(props: ArtifactTitleProps) {
return (
<>
<h1 className="text-xl font-medium text-gray-600 ">{props.title}</h1>
<span className="mt-auto">
{props.isArtifactSaved ? (
<span className="flex items-center justify-start gap-1 text-gray-400">
<p className="text-xs font-light">Saved</p>
<CircleCheck className="w-[10px] h-[10px]" />
</span>
) : (
<span className="flex items-center justify-start gap-1 text-gray-400">
<p className="text-xs font-light">Saving</p>
<LoaderCircle className="animate-spin w-[10px] h-[10px]" />
</span>
)}
</span>
</>
);
}

interface NavigateArtifactHistoryProps {
isBackwardsDisabled: boolean;
isForwardDisabled: boolean;
setSelectedArtifact: (prevState: number) => void;
currentArtifactContent: ArtifactCodeV3 | ArtifactMarkdownV3;
totalArtifactVersions: number;
}

function NavigateArtifactHistory(props: NavigateArtifactHistoryProps) {
return (
<>
<TooltipIconButton
tooltip="Previous"
side="left"
variant="ghost"
className="transition-colors w-fit h-fit p-2"
delayDuration={400}
onClick={() => {
if (!props.isBackwardsDisabled) {
props.setSelectedArtifact(props.currentArtifactContent.index - 1);
}
}}
disabled={props.isBackwardsDisabled}
>
<Forward
aria-disabled={props.isBackwardsDisabled}
className="w-6 h-6 text-gray-600 scale-x-[-1]"
/>
</TooltipIconButton>
<div className="flex items-center justify-center gap-1">
<p className="text-xs pt-1">
{props.currentArtifactContent.index} / {props.totalArtifactVersions}
</p>
</div>
<TooltipIconButton
tooltip="Next"
variant="ghost"
side="right"
className="transition-colors w-fit h-fit p-2"
delayDuration={400}
onClick={() => {
if (!props.isForwardDisabled) {
props.setSelectedArtifact(props.currentArtifactContent.index + 1);
}
}}
disabled={props.isForwardDisabled}
>
<Forward
aria-disabled={props.isForwardDisabled}
className="w-6 h-6 text-gray-600"
/>
</TooltipIconButton>
</>
);
}

function ArtifactRendererComponent(props: ArtifactRendererProps) {
const {
graphData,
Expand Down Expand Up @@ -144,34 +60,55 @@ function ArtifactRendererComponent(props: ArtifactRendererProps) {
const [isSelectionActive, setIsSelectionActive] = useState(false);
const [inputValue, setInputValue] = useState("");
const [isHoveringOverArtifact, setIsHoveringOverArtifact] = useState(false);
const [isValidSelectionOrigin, setIsValidSelectionOrigin] = useState(false);

const handleMouseUp = useCallback(() => {
const selection = window.getSelection();
if (selection && selection.rangeCount > 0 && contentRef.current) {
const range = selection.getRangeAt(0);
const selectedText = range.toString().trim();

if (selectedText) {
const rects = range.getClientRects();
const firstRect = rects[0];
const lastRect = rects[rects.length - 1];
const contentRect = contentRef.current.getBoundingClientRect();

const boxWidth = 400; // Approximate width of the selection box
let left = lastRect.right - contentRect.left - boxWidth;
// Check if the selection originated from within the artifact content
if (selectedText && artifactContentRef.current) {
const isWithinArtifact = (node: Node | null): boolean => {
if (!node) return false;
if (node === artifactContentRef.current) return true;
return isWithinArtifact(node.parentNode);
};

// Check both start and end containers
const startInArtifact = isWithinArtifact(range.startContainer);
const endInArtifact = isWithinArtifact(range.endContainer);

if (startInArtifact && endInArtifact) {
setIsValidSelectionOrigin(true);
const rects = range.getClientRects();
const firstRect = rects[0];
const lastRect = rects[rects.length - 1];
const contentRect = contentRef.current.getBoundingClientRect();

const boxWidth = 400; // Approximate width of the selection box
let left = lastRect.right - contentRect.left - boxWidth;

if (left < 0) {
left = Math.min(0, firstRect.left - contentRect.left);
}
// Ensure the box doesn't go beyond the left edge
if (left < 0) {
left = Math.min(0, firstRect.left - contentRect.left);
}

// Ensure the box doesn't go beyond the left edge
if (left < 0) {
left = Math.min(0, firstRect.left - contentRect.left);
setSelectionBox({
top: lastRect.bottom - contentRect.top,
left: left,
text: selectedText,
});
setIsInputVisible(false);
setIsSelectionActive(true);
} else {
setIsValidSelectionOrigin(false);
handleCleanupState();
}

setSelectionBox({
top: lastRect.bottom - contentRect.top,
left: left,
text: selectedText,
});
setIsInputVisible(false);
setIsSelectionActive(true);
}
}
}, []);
Expand All @@ -181,6 +118,7 @@ function ArtifactRendererComponent(props: ArtifactRendererProps) {
setSelectionBox(undefined);
setSelectionIndexes(undefined);
setIsSelectionActive(false);
setIsValidSelectionOrigin(false);
setInputValue("");
};

Expand Down Expand Up @@ -314,6 +252,35 @@ function ArtifactRendererComponent(props: ArtifactRendererProps) {
}
}, [selectedBlocks, isSelectionActive]);

useEffect(() => {
const handleKeyPress = (e: KeyboardEvent) => {
// Check if we're in an input/textarea element
const activeElement = document.activeElement;
const isInputActive =
activeElement instanceof HTMLInputElement ||
activeElement instanceof HTMLTextAreaElement;

// If selection states are active and we're not in an input field
if (
(isInputVisible || selectionBox || isSelectionActive) &&
!isInputActive
) {
// Check if the key is a character key or backspace/delete
if (e.key.length === 1 || e.key === "Backspace" || e.key === "Delete") {
handleCleanupState();
}
}

// Handle escape key for input box
if ((isInputVisible || isSelectionActive) && e.key === "Escape") {
handleCleanupState();
}
};

document.addEventListener("keydown", handleKeyPress);
return () => document.removeEventListener("keydown", handleKeyPress);
}, [isInputVisible, selectionBox, isSelectionActive]);

const currentArtifactContent = artifact
? getArtifactContent(artifact)
: undefined;
Expand All @@ -337,26 +304,15 @@ function ArtifactRendererComponent(props: ArtifactRendererProps) {

return (
<div className="relative w-full h-full max-h-screen overflow-auto">
<div className="flex flex-row items-center justify-between">
<div className="pl-[6px] pt-3 flex flex-col items-start justify-start ml-[6px] gap-1">
<ArtifactTitle
title={currentArtifactContent.title}
isArtifactSaved={isArtifactSaved}
/>
</div>
<div className="absolute left-1/2 transform -translate-x-1/2 flex items-center justify-center gap-3 text-gray-600">
<NavigateArtifactHistory
isBackwardsDisabled={isBackwardsDisabled}
isForwardDisabled={isForwardDisabled}
setSelectedArtifact={setSelectedArtifact}
currentArtifactContent={currentArtifactContent}
totalArtifactVersions={artifact.contents.length}
/>
</div>
<div className="ml-auto mt-[10px] mr-[6px]">
<ReflectionsDialog selectedAssistant={selectedAssistant} />
</div>
</div>
<ArtifactHeader
isArtifactSaved={isArtifactSaved}
isBackwardsDisabled={isBackwardsDisabled}
isForwardDisabled={isForwardDisabled}
setSelectedArtifact={setSelectedArtifact}
currentArtifactContent={currentArtifactContent}
totalArtifactVersions={artifact.contents.length}
selectedAssistant={selectedAssistant}
/>
<div
ref={contentRef}
className={cn(
Expand Down Expand Up @@ -395,7 +351,7 @@ function ArtifactRendererComponent(props: ArtifactRendererProps) {
className="absolute top-0 left-0 w-full h-full pointer-events-none"
/>
</div>
{selectionBox && isSelectionActive && (
{selectionBox && isSelectionActive && isValidSelectionOrigin && (
<AskOpenCanvas
ref={selectionBoxRef}
inputValue={inputValue}
Expand Down
Loading