From 7779dd9c897187d6e155687233dc2aab3bf77914 Mon Sep 17 00:00:00 2001 From: listlessbird <124798751+listlessbird@users.noreply.github.com> Date: Wed, 30 Oct 2024 01:16:05 +0530 Subject: [PATCH 1/3] feat: add raw markdown view for text --- src/components/artifacts/ArtifactRenderer.tsx | 15 +++ src/components/artifacts/TextRenderer.tsx | 95 +++++++++++-------- 2 files changed, 71 insertions(+), 39 deletions(-) diff --git a/src/components/artifacts/ArtifactRenderer.tsx b/src/components/artifacts/ArtifactRenderer.tsx index a7ddd58d..e88990ee 100644 --- a/src/components/artifacts/ArtifactRenderer.tsx +++ b/src/components/artifacts/ArtifactRenderer.tsx @@ -18,6 +18,7 @@ import { Copy, LoaderCircle, CircleCheck, + Eye, } from "lucide-react"; import { Dispatch, @@ -86,6 +87,7 @@ export function ArtifactRenderer(props: ArtifactRendererProps) { const [isInputVisible, setIsInputVisible] = useState(false); const [isSelectionActive, setIsSelectionActive] = useState(false); const [inputValue, setInputValue] = useState(""); + const [isRawView, setIsRawView] = useState(false); const handleMouseUp = useCallback(() => { const selection = window.getSelection(); @@ -377,6 +379,18 @@ export function ArtifactRenderer(props: ArtifactRendererProps) { />
+ setIsRawView((p) => !p)} + className={cn( + "transition-colors w-fit h-fit p-2", + isRawView && "bg-gray-100 text-gray-900" + )} + > + + ) : null} {currentArtifactContent.type === "code" ? ( diff --git a/src/components/artifacts/TextRenderer.tsx b/src/components/artifacts/TextRenderer.tsx index 4135bad1..0fc4c5a5 100644 --- a/src/components/artifacts/TextRenderer.tsx +++ b/src/components/artifacts/TextRenderer.tsx @@ -24,11 +24,14 @@ export interface TextRendererProps { updateRenderedArtifactRequired: boolean; setUpdateRenderedArtifactRequired: Dispatch>; firstTokenReceived: boolean; + isRawView: boolean; } export function TextRenderer(props: TextRendererProps) { const editor = useCreateBlockNote({}); + const [rawMarkdown, setRawMarkdown] = useState(""); + const [manuallyUpdatingArtifact, setManuallyUpdatingArtifact] = useState(false); @@ -110,6 +113,12 @@ export function TextRenderer(props: TextRendererProps) { } }, [props.artifact, props.updateRenderedArtifactRequired]); + useEffect(() => { + if (props.isRawView) { + editor.blocksToMarkdownLossy(editor.document).then(setRawMarkdown); + } + }, [props.isRawView, editor]); + const isComposition = useRef(false); const onChange = async () => { @@ -153,45 +162,53 @@ export function TextRenderer(props: TextRendererProps) { return (
- - (isComposition.current = true)} - onCompositionEndCapture={() => (isComposition.current = false)} - onChange={onChange} - editable={ - !props.isStreaming || props.isEditing || !manuallyUpdatingArtifact - } - editor={editor} - className={ - props.isStreaming && !props.firstTokenReceived ? "pulse-text" : "" - } - > - - getDefaultReactSlashMenuItems(editor).filter( - (z) => z.group !== "Media" - ) - } - triggerCharacter={"/"} - /> - + {props.isRawView ? ( +
+          {rawMarkdown}
+        
+ ) : ( + <> + + (isComposition.current = true)} + onCompositionEndCapture={() => (isComposition.current = false)} + onChange={onChange} + editable={ + !props.isStreaming || props.isEditing || !manuallyUpdatingArtifact + } + editor={editor} + className={ + props.isStreaming && !props.firstTokenReceived ? "pulse-text" : "" + } + > + + getDefaultReactSlashMenuItems(editor).filter( + (z) => z.group !== "Media" + ) + } + triggerCharacter={"/"} + /> + + + )}
); } From 37732ebcc425e1afffa9288b80787a2caa03614b Mon Sep 17 00:00:00 2001 From: bracesproul Date: Tue, 29 Oct 2024 16:43:53 -0700 Subject: [PATCH 2/3] format --- src/components/artifacts/TextRenderer.tsx | 74 ++++++++++++----------- 1 file changed, 38 insertions(+), 36 deletions(-) diff --git a/src/components/artifacts/TextRenderer.tsx b/src/components/artifacts/TextRenderer.tsx index 0c78a284..2d324196 100644 --- a/src/components/artifacts/TextRenderer.tsx +++ b/src/components/artifacts/TextRenderer.tsx @@ -195,45 +195,47 @@ export function TextRendererComponent(props: TextRendererProps) { ) : ( <> - - (isComposition.current = true)} - onCompositionEndCapture={() => (isComposition.current = false)} - onChange={onChange} - editable={!isStreaming || props.isEditing || !manuallyUpdatingArtifact} - editor={editor} - className={isStreaming && !firstTokenReceived ? "pulse-text" : ""} - > - - getDefaultReactSlashMenuItems(editor).filter( - (z) => z.group !== "Media" - ) - } - triggerCharacter={"/"} - /> - + + (isComposition.current = true)} + onCompositionEndCapture={() => (isComposition.current = false)} + onChange={onChange} + editable={ + !isStreaming || props.isEditing || !manuallyUpdatingArtifact + } + editor={editor} + className={isStreaming && !firstTokenReceived ? "pulse-text" : ""} + > + + getDefaultReactSlashMenuItems(editor).filter( + (z) => z.group !== "Media" + ) + } + triggerCharacter={"/"} + /> + )}
- ) + ); } export const TextRenderer = React.memo(TextRendererComponent); From 9f5dc012b2709f9b65035fb470a1ec34cb82500b Mon Sep 17 00:00:00 2001 From: bracesproul Date: Tue, 29 Oct 2024 17:17:39 -0700 Subject: [PATCH 3/3] make editable --- src/components/artifacts/TextRenderer.tsx | 105 ++++++++++++++++++---- src/constants.ts | 2 +- 2 files changed, 87 insertions(+), 20 deletions(-) diff --git a/src/components/artifacts/TextRenderer.tsx b/src/components/artifacts/TextRenderer.tsx index 2d324196..1454783e 100644 --- a/src/components/artifacts/TextRenderer.tsx +++ b/src/components/artifacts/TextRenderer.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from "react"; +import { Dispatch, SetStateAction, useEffect, useRef, useState } from "react"; import { ArtifactMarkdownV3 } from "@/types"; import "@blocknote/core/fonts/inter.css"; import { @@ -14,13 +14,44 @@ import { getArtifactContent } from "@/contexts/utils"; import { useGraphContext } from "@/contexts/GraphContext"; import React from "react"; import { TooltipIconButton } from "../ui/assistant-ui/tooltip-icon-button"; -import { Eye } from "lucide-react"; -import { cn } from "@/lib/utils"; +import { Eye, EyeOff } from "lucide-react"; +import { motion } from "framer-motion"; +import { Textarea } from "../ui/textarea"; const cleanText = (text: string) => { return text.replaceAll("\\\n", "\n"); }; +function ViewRawText({ + isRawView, + setIsRawView, +}: { + isRawView: boolean; + setIsRawView: Dispatch>; +}) { + return ( + + setIsRawView((p) => !p)} + > + {isRawView ? ( + + ) : ( + + )} + + + ); +} + export interface TextRendererProps { isEditing: boolean; isHovering: boolean; @@ -126,6 +157,18 @@ export function TextRendererComponent(props: TextRendererProps) { useEffect(() => { if (isRawView) { editor.blocksToMarkdownLossy(editor.document).then(setRawMarkdown); + } else if (!isRawView && rawMarkdown) { + try { + (async () => { + setManuallyUpdatingArtifact(true); + const markdownAsBlocks = + await editor.tryParseMarkdownToBlocks(rawMarkdown); + editor.replaceBlocks(editor.document, markdownAsBlocks); + setManuallyUpdatingArtifact(false); + })(); + } catch (_) { + setManuallyUpdatingArtifact(false); + } } }, [isRawView, editor]); @@ -170,29 +213,53 @@ export function TextRendererComponent(props: TextRendererProps) { }); }; + const onChangeRawMarkdown = (e: React.ChangeEvent) => { + const newRawMarkdown = e.target.value; + setRawMarkdown(newRawMarkdown); + setArtifact((prev) => { + if (!prev) { + return { + currentIndex: 1, + contents: [ + { + index: 1, + fullMarkdown: newRawMarkdown, + title: "Untitled", + type: "text", + }, + ], + }; + } else { + return { + ...prev, + contents: prev.contents.map((c) => { + if (c.index === prev.currentIndex) { + return { + ...c, + fullMarkdown: newRawMarkdown, + }; + } + return c; + }), + }; + } + }); + }; + return (
{props.isHovering && artifact && ( -
+
- setIsRawView((p) => !p)} - className={cn( - "transition-colors w-fit h-fit p-2", - isRawView && "bg-gray-100 text-gray-900" - )} - > - - +
)} {isRawView ? ( -
-          {rawMarkdown}
-        
+