- 
                Notifications
    You must be signed in to change notification settings 
- Fork 2.4k
UI: Render reasoning as plain italic (match <thinking>) #7752
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 1 commit
ab958bf
              c557a2f
              0acab3b
              8d8111f
              ea322fd
              a84d41b
              a34f392
              1be489b
              d377e45
              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 | 
|---|---|---|
| @@ -1,99 +1,19 @@ | ||
| import { useCallback, useEffect, useRef, useState } from "react" | ||
| import { CaretDownIcon, CaretUpIcon, CounterClockwiseClockIcon } from "@radix-ui/react-icons" | ||
| import { useTranslation } from "react-i18next" | ||
|  | ||
| import MarkdownBlock from "../common/MarkdownBlock" | ||
| import { useMount } from "react-use" | ||
|  | ||
| interface ReasoningBlockProps { | ||
| content: string | ||
| elapsed?: number | ||
| isCollapsed?: boolean | ||
| onToggleCollapse?: () => void | ||
| } | ||
|  | ||
| export const ReasoningBlock = ({ content, elapsed, isCollapsed = false, onToggleCollapse }: ReasoningBlockProps) => { | ||
| const contentRef = useRef<HTMLDivElement>(null) | ||
| const elapsedRef = useRef<number>(0) | ||
| const { t } = useTranslation("chat") | ||
| const [thought, setThought] = useState<string>() | ||
| const [prevThought, setPrevThought] = useState<string>(t("chat:reasoning.thinking")) | ||
| const [isTransitioning, setIsTransitioning] = useState<boolean>(false) | ||
| const cursorRef = useRef<number>(0) | ||
| const queueRef = useRef<string[]>([]) | ||
|  | ||
| useEffect(() => { | ||
| if (contentRef.current && !isCollapsed) { | ||
| contentRef.current.scrollTop = contentRef.current.scrollHeight | ||
| } | ||
| }, [content, isCollapsed]) | ||
|  | ||
| useEffect(() => { | ||
| if (elapsed) { | ||
| elapsedRef.current = elapsed | ||
| } | ||
| }, [elapsed]) | ||
|  | ||
| // Process the transition queue. | ||
| const processNextTransition = useCallback(() => { | ||
| const nextThought = queueRef.current.pop() | ||
| queueRef.current = [] | ||
|  | ||
| if (nextThought) { | ||
| setIsTransitioning(true) | ||
| } | ||
|  | ||
| setTimeout(() => { | ||
| if (nextThought) { | ||
| setPrevThought(nextThought) | ||
| setIsTransitioning(false) | ||
| } | ||
|  | ||
| setTimeout(() => processNextTransition(), 500) | ||
| }, 200) | ||
| }, []) | ||
|  | ||
| useMount(() => { | ||
| processNextTransition() | ||
| }) | ||
|  | ||
| useEffect(() => { | ||
| if (content.length - cursorRef.current > 160) { | ||
| setThought("... " + content.slice(cursorRef.current)) | ||
| cursorRef.current = content.length | ||
| } | ||
| }, [content]) | ||
|  | ||
| useEffect(() => { | ||
| if (thought && thought !== prevThought) { | ||
| queueRef.current.push(thought) | ||
| } | ||
| }, [thought, prevThought]) | ||
|  | ||
| /** | ||
| 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. Nice documentation! Could we expand it slightly to explain the design rationale? For example: | ||
| * Render reasoning as simple italic text, matching how <thinking> content is shown. | ||
| * No borders, boxes, headers, timers, or collapsible behavior. | ||
| */ | ||
| export const ReasoningBlock = ({ content }: ReasoningBlockProps) => { | ||
|          | ||
| return ( | ||
| <div className="bg-vscode-editor-background border border-vscode-border rounded-xs overflow-hidden"> | ||
| <div | ||
| className="flex items-center justify-between gap-1 px-3 py-2 cursor-pointer text-muted-foreground" | ||
| onClick={onToggleCollapse}> | ||
| <div | ||
| className={`truncate flex-1 transition-opacity duration-200 ${isTransitioning ? "opacity-0" : "opacity-100"}`}> | ||
| {prevThought} | ||
| </div> | ||
| <div className="flex flex-row items-center gap-1"> | ||
| {elapsedRef.current > 1000 && ( | ||
| <> | ||
| <CounterClockwiseClockIcon className="scale-80" /> | ||
| <div>{t("reasoning.seconds", { count: Math.round(elapsedRef.current / 1000) })}</div> | ||
| </> | ||
| )} | ||
| {isCollapsed ? <CaretDownIcon /> : <CaretUpIcon />} | ||
| </div> | ||
| <div className="px-3 py-1"> | ||
|          | ||
| <div className="italic text-muted-foreground"> | ||
|          | ||
| <MarkdownBlock markdown={content} /> | ||
| </div> | ||
| {!isCollapsed && ( | ||
| <div ref={contentRef} className="px-3 max-h-[160px] overflow-y-auto"> | ||
| <MarkdownBlock markdown={content} /> | ||
| </div> | ||
| )} | ||
| </div> | ||
| ) | ||
| } | ||
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.
The state and are no longer used after the simplification. Should we remove this unused import and state declaration?