返回 DeepSeek-Reasonix
ReasoningRow.tsx
根目录 / desktop / frontend / src / components / harness-chat / ReasoningRow.tsx
1 // Adapted from DeepSeek Harness c291e7961a (MIT).
2 /** Assistant reasoning disclosure, independent of Tool-call presentation. */
3 import { useState, type ReactNode } from 'react'
4 import { Atom as IconThinkOutline14 } from 'lucide-react'
5 import { DisclosureRow } from './DisclosureRow'
6 import type { useT } from '../../lib/i18n'
7 const a11yCss = { visuallyHidden: 'sr-only' }
8 import css from './ReasoningRow.styles'
9
10 function firstLine(text: string): string {
11 const newline = text.indexOf('\n')
12 return newline === -1 ? text : text.slice(0, newline)
13 }
14
15 function latestLine(text: string): string {
16 const visible = text.trimEnd()
17 const newline = visible.lastIndexOf('\n')
18 return newline === -1 ? visible : visible.slice(newline + 1)
19 }
20
21 /**
22 * Render one assistant reasoning block as the Think disclosure row. The
23 * collapsed summary omits double-asterisk markers; expanded content preserves
24 * the complete text.
25 * @param props.text - complete or streaming reasoning text.
26 * @param props.running - whether this block is the streaming tail.
27 * @param props.t - conversation locale seat for the running status.
28 * @returns the reasoning disclosure.
29 */
30 export function ReasoningRow({ text, running, t, children, beforeToggle, duration }: { text: string; running: boolean; t: ReturnType<typeof useT>; children?: ReactNode; beforeToggle?: () => void; duration?: string }) {
31 const [expanded, setExpanded] = useState(false)
32 const summary = (running ? latestLine(text) : firstLine(text.trimStart())).replaceAll('**', '')
33
34 return (
35 <div
36 className={css.root}
37 data-variant="think"
38 data-state={running ? 'running' : 'ok'}
39 data-expanded={expanded || undefined}
40 >
41 {running && <span className={a11yCss.visuallyHidden}>{t('chat.running')}</span>}
42 <DisclosureRow
43 rowClassName={css.row}
44 leadingClassName={css.leading}
45 titleClassName={css.title}
46 chevronClassName={css.chevron}
47 icon={<IconThinkOutline14 size={14} />}
48 title={t('chat.reasoning')}
49 open={expanded}
50 expandable
51 expandOnRowClick
52 onToggle={() => { beforeToggle?.(); setExpanded(value => !value) }}
53 collapsedContent={(
54 <>
55 <span className={css.separator} aria-hidden />
56 <span className={css.summary} data-follow-end={running || undefined}>
57 <span className={css.summaryText}>{summary}</span>
58 </span>
59 {duration && !running && <span className="dsh-row-duration">{duration}</span>}
60 </>
61 )}
62 >
63 <div className={css.thinkBody}>{children ?? text}</div>
64 </DisclosureRow>
65 </div>
66 )
67 }
68
68 lines Plain Text