-
-
Notifications
You must be signed in to change notification settings - Fork 5.9k
feat(chat): cleaner tool UI, inline LaTeX, clickable links #4561
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
Changes from all commits
d7bef83
c32c5bc
78c1a4a
3bd54b3
7de118f
752822b
5f8df31
5d294bb
59bda4e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,7 +7,7 @@ import { copyToClipboard } from "@/lib/copy-to-clipboard"; | |
| import type { ToolCallMessagePartComponent } from "@assistant-ui/react"; | ||
| import { code as codePlugin } from "@streamdown/code"; | ||
| import { CheckIcon, CodeIcon, CopyIcon, LoaderIcon } from "lucide-react"; | ||
| import { memo, useCallback, useMemo, useRef, useState } from "react"; | ||
| import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react"; | ||
| import { Streamdown } from "streamdown"; | ||
| import { | ||
| ToolFallbackContent, | ||
|
|
@@ -28,6 +28,15 @@ function truncate(text: string): string { | |
| function CopyBtn({ text }: { text: string }) { | ||
| const [copied, setCopied] = useState(false); | ||
| const timer = useRef<ReturnType<typeof setTimeout> | null>(null); | ||
|
|
||
| useEffect(() => { | ||
| return () => { | ||
| if (timer.current) { | ||
| clearTimeout(timer.current); | ||
| } | ||
| }; | ||
| }, []); | ||
|
Comment on lines
+32
to
+38
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thank you for the compliment |
||
|
|
||
| const copy = useCallback(() => { | ||
| if (copyToClipboard(text)) { | ||
| setCopied(true); | ||
|
|
@@ -98,14 +107,14 @@ const PythonToolUIImpl: ToolCallMessagePartComponent = ({ | |
| icon={CodeIcon} | ||
| /> | ||
| <ToolFallbackContent> | ||
| <div className="flex flex-col px-4"> | ||
| <div className="border-l-2 border-muted-foreground/20 pl-2"> | ||
| {/* Code + copy */} | ||
| {code && ( | ||
| <div className="flex justify-end"> | ||
| <CopyBtn text={code} /> | ||
| </div> | ||
| )} | ||
| <HighlightedCode code={code} language="python" /> | ||
| {code && <HighlightedCode code={code} language="python" />} | ||
|
|
||
| {/* Output */} | ||
| {isRunning ? ( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,7 +6,7 @@ | |
| import { copyToClipboard } from "@/lib/copy-to-clipboard"; | ||
| import type { ToolCallMessagePartComponent } from "@assistant-ui/react"; | ||
| import { CheckIcon, CopyIcon, LoaderIcon, TerminalIcon } from "lucide-react"; | ||
| import { memo, useCallback, useRef, useState } from "react"; | ||
| import { memo, useCallback, useEffect, useRef, useState } from "react"; | ||
| import { | ||
| ToolFallbackContent, | ||
| ToolFallbackRoot, | ||
|
|
@@ -25,6 +25,15 @@ function truncate(text: string): string { | |
| function CopyBtn({ text }: { text: string }) { | ||
| const [copied, setCopied] = useState(false); | ||
| const timer = useRef<ReturnType<typeof setTimeout> | null>(null); | ||
|
|
||
| useEffect(() => { | ||
| return () => { | ||
| if (timer.current) { | ||
| clearTimeout(timer.current); | ||
| } | ||
| }; | ||
| }, []); | ||
|
Comment on lines
+29
to
+35
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thank you?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. lol |
||
|
|
||
| const copy = useCallback(() => { | ||
| if (copyToClipboard(text)) { | ||
| setCopied(true); | ||
|
|
@@ -74,7 +83,7 @@ const TerminalToolUIImpl: ToolCallMessagePartComponent = ({ | |
| icon={TerminalIcon} | ||
| /> | ||
| <ToolFallbackContent> | ||
| <div className="flex flex-col px-4"> | ||
| <div className="border-l-2 border-muted-foreground/20 pl-2"> | ||
| {isRunning ? ( | ||
| <div className="flex items-center gap-2 text-sm text-muted-foreground"> | ||
| <LoaderIcon className="size-3.5 animate-spin" /> | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Defining a custom
arenderer here replaces Streamdown’s built-in link component, which is wherelinkSafetyconfirmation/on-check logic is enforced; as a result, untrusted assistant-generated URLs now open directly in a new tab on click instead of going through the safety gate. This is a security regression for any chat output containing external links and should keep the default safety behavior while adding styling/target attributes.Useful? React with 👍 / 👎.