sparkdesign 0.3.1 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -5
- package/cli/dist/index.js +0 -0
- package/cli/dist/utils/tokens.js +103 -17
- package/cli/registry/basic/button.test.tsx +333 -0
- package/cli/registry/chat/{question-part.tsx → ask-user-part.tsx} +4 -4
- package/cli/registry/chat/{browser-use-part.tsx → browser-action-part.tsx} +6 -6
- package/cli/registry/chat/{suggestion-part.tsx → hint-banner.tsx} +4 -4
- package/cli/registry/chat/markdown.test.tsx +387 -0
- package/cli/registry/chat/{reasoning-step.tsx → reasoning-step/compound.tsx} +163 -185
- package/cli/registry/chat/reasoning-step/context.tsx +114 -0
- package/cli/registry/chat/reasoning-step/index.tsx +45 -0
- package/cli/registry/chat/reasoning-step/types.ts +109 -0
- package/cli/registry/chat/response/compound.tsx +210 -0
- package/cli/registry/chat/{response.tsx → response/context.tsx} +65 -136
- package/cli/registry/chat/response/index.tsx +87 -0
- package/cli/registry/chat/response/types.ts +123 -0
- package/cli/registry/chat/thinking-indicator.test.tsx +244 -0
- package/cli/registry/chat/tool-invocation-card.test.tsx +346 -0
- package/cli/registry/chat/{request.tsx → user-message.tsx} +3 -3
- package/cli/registry/chat/user-question/compound.tsx +324 -0
- package/cli/registry/chat/user-question/context.tsx +456 -0
- package/cli/registry/chat/user-question/index.tsx +71 -316
- package/cli/registry/chat/user-question/useUserQuestionKeyboard.ts +5 -6
- package/cli/registry/tokens/index.css +31 -0
- package/cli/registry/tokens/scale/computed.css +103 -0
- package/cli/registry/tokens/scale/config.css +110 -0
- package/cli/registry/tokens/scale/index.css +30 -0
- package/cli/registry/tokens/scale/presets/compact.css +30 -0
- package/cli/registry/tokens/scale/presets/dense.css +64 -0
- package/cli/registry/tokens/scale/presets/sharp.css +40 -0
- package/cli/registry/tokens/scale/presets/soft.css +16 -0
- package/cli/registry/tokens/scale.css +12 -298
- package/cli/registry/tokens/scrollbar-utility.css +35 -0
- package/cli/registry/tokens/themes/dark-parchment.css +132 -0
- package/cli/registry/tokens/themes/dark-qoder.css +132 -0
- package/cli/registry/tokens/themes/light-parchment.css +123 -0
- package/cli/registry/tokens/themes/light-qoder.css +131 -0
- package/dist/qoder-design.css +1 -1
- package/dist/registry/chat/ask-user-part.d.ts +24 -0
- package/dist/registry/chat/browser-action-part.d.ts +28 -0
- package/dist/registry/chat/{suggestion-part.d.ts → hint-banner.d.ts} +4 -4
- package/dist/registry/chat/reasoning-step/compound.d.ts +17 -0
- package/dist/registry/chat/reasoning-step/context.d.ts +10 -0
- package/dist/registry/chat/reasoning-step/index.d.ts +14 -0
- package/dist/registry/chat/reasoning-step/types.d.ts +95 -0
- package/dist/registry/chat/response/compound.d.ts +25 -0
- package/dist/registry/chat/response/context.d.ts +9 -0
- package/dist/registry/chat/response/index.d.ts +15 -0
- package/dist/registry/chat/response/types.d.ts +99 -0
- package/dist/registry/chat/user-message.d.ts +6 -0
- package/dist/registry/chat/user-question/compound.d.ts +37 -0
- package/dist/registry/chat/user-question/context.d.ts +55 -0
- package/dist/registry/chat/user-question/index.d.ts +13 -5
- package/dist/registry/chat/user-question/useUserQuestionKeyboard.d.ts +2 -3
- package/dist/scale.css +9 -303
- package/dist/spark-design.cjs.js +62 -62
- package/dist/spark-design.es.js +3992 -3826
- package/dist/src/components/chat/AskUserPart/index.d.ts +6 -0
- package/dist/src/components/chat/BrowserActionPart/index.d.ts +7 -0
- package/dist/src/components/chat/HintBanner/index.d.ts +6 -0
- package/dist/src/components/chat/ReasoningStep/index.d.ts +11 -5
- package/dist/src/components/chat/Response/index.d.ts +16 -6
- package/dist/src/components/chat/UserMessage/index.d.ts +7 -0
- package/dist/src/components/chat/UserQuestion/index.d.ts +18 -4
- package/dist/src/components/index.d.ts +63 -63
- package/dist/theme.css +13 -800
- package/package.json +27 -3
- package/dist/registry/chat/browser-use-part.d.ts +0 -28
- package/dist/registry/chat/question-part.d.ts +0 -24
- package/dist/registry/chat/reasoning-step.d.ts +0 -35
- package/dist/registry/chat/request.d.ts +0 -6
- package/dist/registry/chat/response.d.ts +0 -28
- package/dist/src/components/chat/BrowserUsePart/index.d.ts +0 -7
- package/dist/src/components/chat/QuestionPart/index.d.ts +0 -6
- package/dist/src/components/chat/Request/index.d.ts +0 -7
- package/dist/src/components/chat/SuggestionPart/index.d.ts +0 -6
- /package/dist/src/components/{foundation → basic}/AlertDialog/index.d.ts +0 -0
- /package/dist/src/components/{foundation → basic}/Avatar/index.d.ts +0 -0
- /package/dist/src/components/{foundation → basic}/Button/index.d.ts +0 -0
- /package/dist/src/components/{foundation → basic}/Collapse/index.d.ts +0 -0
- /package/dist/src/components/{foundation → basic}/Collapsible/index.d.ts +0 -0
- /package/dist/src/components/{foundation → basic}/CollapsibleSection/index.d.ts +0 -0
- /package/dist/src/components/{foundation → basic}/DropdownMenu/index.d.ts +0 -0
- /package/dist/src/components/{foundation → basic}/EllipsisText/index.d.ts +0 -0
- /package/dist/src/components/{foundation → basic}/IconButton/index.d.ts +0 -0
- /package/dist/src/components/{foundation → basic}/Kbd/index.d.ts +0 -0
- /package/dist/src/components/{foundation → basic}/OptionList/index.d.ts +0 -0
- /package/dist/src/components/{foundation → basic}/Pagination/index.d.ts +0 -0
- /package/dist/src/components/{foundation → basic}/Progress/index.d.ts +0 -0
- /package/dist/src/components/{foundation → basic}/RadioGroup/index.d.ts +0 -0
- /package/dist/src/components/{foundation → basic}/Resizable/index.d.ts +0 -0
- /package/dist/src/components/{foundation → basic}/Scrollbar/index.d.ts +0 -0
- /package/dist/src/components/{foundation → basic}/Select/index.d.ts +0 -0
- /package/dist/src/components/{foundation → basic}/Skeleton/index.d.ts +0 -0
- /package/dist/src/components/{foundation → basic}/Slider/index.d.ts +0 -0
- /package/dist/src/components/{foundation → basic}/Spinner/index.d.ts +0 -0
- /package/dist/src/components/{foundation → basic}/Switch/index.d.ts +0 -0
- /package/dist/src/components/{foundation → basic}/Table/index.d.ts +0 -0
- /package/dist/src/components/{foundation → basic}/Tabs/index.d.ts +0 -0
- /package/dist/src/components/{foundation → basic}/Tag/index.d.ts +0 -0
- /package/dist/src/components/{foundation → basic}/Toast/index.d.ts +0 -0
- /package/dist/src/components/{foundation → basic}/Toggle/index.d.ts +0 -0
- /package/dist/src/components/{foundation → basic}/Tooltip/index.d.ts +0 -0
- /package/dist/src/components/{foundation → basic}/Typography/index.d.ts +0 -0
|
@@ -1,37 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { memo, useMemo, useCallback, Fragment } from 'react'
|
|
2
2
|
import type { ReactNode } from 'react'
|
|
3
3
|
import { motion } from 'framer-motion'
|
|
4
|
-
import { cn } from '
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
lines?: string
|
|
13
|
-
path?: string
|
|
14
|
-
fileType?: string
|
|
15
|
-
details?: ReasoningStepDetail[]
|
|
16
|
-
}
|
|
4
|
+
import { cn } from '../../lib/utils'
|
|
5
|
+
import { useReasoningStepContext } from './context'
|
|
6
|
+
import type {
|
|
7
|
+
ReasoningStepHeaderProps,
|
|
8
|
+
ReasoningStepDetailsProps,
|
|
9
|
+
ReasoningStepDetailRowProps,
|
|
10
|
+
ReasoningStepDetail,
|
|
11
|
+
} from './types'
|
|
17
12
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
status?: 'completed' | 'in-progress'
|
|
22
|
-
icon?: ReactNode
|
|
23
|
-
showIcon?: boolean
|
|
24
|
-
details?: ReasoningStepDetail[]
|
|
25
|
-
children?: ReactNode
|
|
26
|
-
duration?: number
|
|
27
|
-
spread?: number
|
|
28
|
-
disableExpandAnimation?: boolean
|
|
29
|
-
defaultExpanded?: boolean
|
|
30
|
-
expanded?: boolean
|
|
31
|
-
onExpandedChange?: (expanded: boolean) => void
|
|
32
|
-
eyeIcon?: ReactNode
|
|
33
|
-
arrowRightIcon?: ReactNode
|
|
34
|
-
}
|
|
13
|
+
// ===== 样式常量 =====
|
|
14
|
+
|
|
15
|
+
const iconSizeStyle = { width: 'var(--font-size-xs)', height: 'var(--font-size-xs)' }
|
|
35
16
|
|
|
36
17
|
const FILE_ICON_COLOR_MAP: Record<string, string> = {
|
|
37
18
|
ts: 'text-info', tsx: 'text-info', js: 'text-info', jsx: 'text-info',
|
|
@@ -44,10 +25,7 @@ function getFileIconColorClass(fileType?: string, filename?: string): string {
|
|
|
44
25
|
return FILE_ICON_COLOR_MAP[ext] ?? 'text-text-tertiary'
|
|
45
26
|
}
|
|
46
27
|
|
|
47
|
-
|
|
48
|
-
const iconSizeStyle = { width: 'var(--font-size-xs)', height: 'var(--font-size-xs)' }
|
|
49
|
-
const eyeClass = 'w-full h-full text-text-tertiary transition-transform duration-200 ease-in-out'
|
|
50
|
-
const arrowClass = 'w-full h-full transition-transform duration-200 ease-in-out'
|
|
28
|
+
// ===== 辅助组件 =====
|
|
51
29
|
|
|
52
30
|
const FileIconPlaceholder = memo(function FileIconPlaceholder({ className }: { className?: string }) {
|
|
53
31
|
return (
|
|
@@ -87,7 +65,86 @@ const InlineRow = memo(function InlineRow({
|
|
|
87
65
|
)
|
|
88
66
|
})
|
|
89
67
|
|
|
90
|
-
|
|
68
|
+
// ===== ReasoningStep.Header =====
|
|
69
|
+
|
|
70
|
+
export function ReasoningStepHeader({ className }: ReasoningStepHeaderProps) {
|
|
71
|
+
const ctx = useReasoningStepContext()
|
|
72
|
+
|
|
73
|
+
const dynamicSpread = useMemo(() => {
|
|
74
|
+
if (typeof ctx.text === 'string') return ctx.text.length * ctx.spread
|
|
75
|
+
return 10 * ctx.spread
|
|
76
|
+
}, [ctx.text, ctx.spread])
|
|
77
|
+
|
|
78
|
+
const getIcon = () => (ctx.icon ? ctx.icon : ctx.eyeIcon)
|
|
79
|
+
|
|
80
|
+
const renderPrimaryText = () => {
|
|
81
|
+
if (typeof ctx.text !== 'string') return ctx.text
|
|
82
|
+
if (ctx.status === 'in-progress') {
|
|
83
|
+
return (
|
|
84
|
+
<motion.span
|
|
85
|
+
className="text-xs leading-xs font-normal relative inline-block bg-clip-text text-transparent bg-[length:250%_100%,auto] bg-no-repeat truncate"
|
|
86
|
+
style={{
|
|
87
|
+
backgroundImage: `linear-gradient(90deg, transparent calc(50% - ${dynamicSpread}px), var(--color-text), transparent calc(50% + ${dynamicSpread}px)), linear-gradient(var(--color-text-tertiary), var(--color-text-tertiary))`,
|
|
88
|
+
}}
|
|
89
|
+
initial={{ backgroundPosition: '100% center' }}
|
|
90
|
+
animate={{ backgroundPosition: '0% center' }}
|
|
91
|
+
transition={{ repeat: Infinity, duration: ctx.duration, ease: 'linear' }}
|
|
92
|
+
>
|
|
93
|
+
{ctx.text}
|
|
94
|
+
</motion.span>
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
return (
|
|
98
|
+
<span className="text-xs leading-xs font-normal text-text-tertiary truncate">
|
|
99
|
+
{ctx.text}
|
|
100
|
+
</span>
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const renderDescription = () => {
|
|
105
|
+
if (!ctx.description) return null
|
|
106
|
+
return (
|
|
107
|
+
<span className="text-xs leading-xs font-normal text-text-quaternary truncate">
|
|
108
|
+
{ctx.description}
|
|
109
|
+
</span>
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<div
|
|
115
|
+
className={cn('flex flex-row items-center gap-1.5', ctx.hasExpandableContent && 'cursor-pointer', className)}
|
|
116
|
+
onClick={ctx.toggleExpanded}
|
|
117
|
+
>
|
|
118
|
+
{ctx.effectiveShowIcon && (
|
|
119
|
+
<div className="flex items-center justify-center shrink-0" style={iconSizeStyle}>
|
|
120
|
+
{getIcon()}
|
|
121
|
+
</div>
|
|
122
|
+
)}
|
|
123
|
+
<div className="flex flex-row items-center gap-1 flex-1 min-w-0">
|
|
124
|
+
{renderPrimaryText()}
|
|
125
|
+
{ctx.description ? renderDescription() : null}
|
|
126
|
+
{ctx.hasExpandableContent && (
|
|
127
|
+
<div
|
|
128
|
+
className={cn(
|
|
129
|
+
'flex items-center justify-center shrink-0 text-text-tertiary transition-opacity duration-200 ease-in-out',
|
|
130
|
+
ctx.effectiveExpanded ? 'opacity-100' : ctx.isHovered ? 'opacity-100' : 'opacity-0',
|
|
131
|
+
ctx.effectiveExpanded ? 'rotate-90' : 'rotate-0'
|
|
132
|
+
)}
|
|
133
|
+
style={iconSizeStyle}
|
|
134
|
+
>
|
|
135
|
+
{ctx.arrowRightIcon}
|
|
136
|
+
</div>
|
|
137
|
+
)}
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
ReasoningStepHeader.displayName = 'ReasoningStepHeader'
|
|
144
|
+
|
|
145
|
+
// ===== ReasoningStep.DetailRow(内部递归组件)=====
|
|
146
|
+
|
|
147
|
+
interface DetailRowInternalProps {
|
|
91
148
|
detail: ReasoningStepDetail
|
|
92
149
|
depth: number
|
|
93
150
|
pathKey: string
|
|
@@ -98,16 +155,15 @@ interface DetailRowProps {
|
|
|
98
155
|
arrowRightIcon: ReactNode
|
|
99
156
|
}
|
|
100
157
|
|
|
101
|
-
const
|
|
158
|
+
const DetailRowInternal = memo(function DetailRowInternal({
|
|
102
159
|
detail,
|
|
103
160
|
depth,
|
|
104
161
|
pathKey,
|
|
105
162
|
detailIndent,
|
|
106
|
-
disableExpandAnimation,
|
|
107
163
|
expandedNestedKeys,
|
|
108
164
|
onToggleNested,
|
|
109
165
|
arrowRightIcon,
|
|
110
|
-
}:
|
|
166
|
+
}: DetailRowInternalProps) {
|
|
111
167
|
const hasNested = detail.details != null && detail.details.length > 0
|
|
112
168
|
const isExpandedNested = expandedNestedKeys.has(pathKey)
|
|
113
169
|
const showIconOnRow = !hasNested
|
|
@@ -175,13 +231,13 @@ const DetailRow = memo(function DetailRow({
|
|
|
175
231
|
<div className="overflow-hidden">
|
|
176
232
|
<div className={cn(nestedWrapperClass)}>
|
|
177
233
|
{detail.details?.map((d, i) => (
|
|
178
|
-
<
|
|
234
|
+
<DetailRowInternal
|
|
179
235
|
key={`${pathKey}-${i}`}
|
|
180
236
|
detail={d}
|
|
181
237
|
depth={depth + 1}
|
|
182
238
|
pathKey={`${pathKey}-${i}`}
|
|
183
239
|
detailIndent={detailIndent}
|
|
184
|
-
disableExpandAnimation={
|
|
240
|
+
disableExpandAnimation={false}
|
|
185
241
|
expandedNestedKeys={expandedNestedKeys}
|
|
186
242
|
onToggleNested={onToggleNested}
|
|
187
243
|
arrowRightIcon={arrowRightIcon}
|
|
@@ -194,165 +250,87 @@ const DetailRow = memo(function DetailRow({
|
|
|
194
250
|
)
|
|
195
251
|
})
|
|
196
252
|
|
|
197
|
-
|
|
198
|
-
text,
|
|
199
|
-
description,
|
|
200
|
-
status = 'completed',
|
|
201
|
-
icon,
|
|
202
|
-
showIcon = true,
|
|
203
|
-
details,
|
|
204
|
-
children,
|
|
205
|
-
duration = 2,
|
|
206
|
-
spread = 2,
|
|
207
|
-
disableExpandAnimation = false,
|
|
208
|
-
defaultExpanded,
|
|
209
|
-
expanded: expandedProp,
|
|
210
|
-
onExpandedChange,
|
|
211
|
-
eyeIcon,
|
|
212
|
-
arrowRightIcon,
|
|
213
|
-
}: ReasoningStepProps) {
|
|
214
|
-
const isControlled = expandedProp !== undefined
|
|
215
|
-
const [isExpandedInternal, setIsExpandedInternal] = useState(defaultExpanded ?? false)
|
|
216
|
-
const isExpanded = isControlled ? expandedProp : isExpandedInternal
|
|
217
|
-
const [isHovered, setIsHovered] = useState(false)
|
|
218
|
-
|
|
219
|
-
const effectiveExpanded = disableExpandAnimation && defaultExpanded !== undefined
|
|
220
|
-
? defaultExpanded
|
|
221
|
-
: isExpanded
|
|
253
|
+
// ===== ReasoningStep.DetailRow(公开 API)=====
|
|
222
254
|
|
|
223
|
-
|
|
255
|
+
export function ReasoningStepDetailRow({
|
|
256
|
+
detail,
|
|
257
|
+
depth = 0,
|
|
258
|
+
pathKey = '0',
|
|
259
|
+
}: ReasoningStepDetailRowProps) {
|
|
260
|
+
const ctx = useReasoningStepContext()
|
|
261
|
+
return (
|
|
262
|
+
<DetailRowInternal
|
|
263
|
+
detail={detail}
|
|
264
|
+
depth={depth}
|
|
265
|
+
pathKey={pathKey}
|
|
266
|
+
detailIndent={ctx.detailIndent}
|
|
267
|
+
disableExpandAnimation={ctx.disableExpandAnimation}
|
|
268
|
+
expandedNestedKeys={ctx.expandedNestedKeys}
|
|
269
|
+
onToggleNested={ctx.toggleNested}
|
|
270
|
+
arrowRightIcon={ctx.arrowRightIcon}
|
|
271
|
+
/>
|
|
272
|
+
)
|
|
273
|
+
}
|
|
224
274
|
|
|
225
|
-
|
|
275
|
+
ReasoningStepDetailRow.displayName = 'ReasoningStepDetailRow'
|
|
226
276
|
|
|
227
|
-
|
|
228
|
-
if (typeof text === 'string') return text.length * spread
|
|
229
|
-
return 10 * spread
|
|
230
|
-
}, [text, spread])
|
|
277
|
+
// ===== ReasoningStep.Details =====
|
|
231
278
|
|
|
232
|
-
|
|
233
|
-
const
|
|
279
|
+
export function ReasoningStepDetails({ className, children }: ReasoningStepDetailsProps) {
|
|
280
|
+
const ctx = useReasoningStepContext()
|
|
281
|
+
const hasDetails = ctx.details != null && ctx.details.length > 0
|
|
234
282
|
|
|
235
|
-
|
|
283
|
+
return (
|
|
284
|
+
<div
|
|
285
|
+
className={cn(
|
|
286
|
+
'grid transition-[grid-template-rows,opacity] duration-200 ease-out',
|
|
287
|
+
ctx.effectiveExpanded ? 'grid-rows-[1fr] opacity-100' : 'grid-rows-[0fr] opacity-0',
|
|
288
|
+
className
|
|
289
|
+
)}
|
|
290
|
+
>
|
|
291
|
+
<div className="overflow-hidden">
|
|
292
|
+
{hasDetails ? (
|
|
293
|
+
<div className="flex flex-col">
|
|
294
|
+
{ctx.details?.map((d, i) => (
|
|
295
|
+
<DetailRowInternal
|
|
296
|
+
key={String(i)}
|
|
297
|
+
detail={d}
|
|
298
|
+
depth={0}
|
|
299
|
+
pathKey={String(i)}
|
|
300
|
+
detailIndent={ctx.detailIndent}
|
|
301
|
+
disableExpandAnimation={ctx.disableExpandAnimation}
|
|
302
|
+
expandedNestedKeys={ctx.expandedNestedKeys}
|
|
303
|
+
onToggleNested={ctx.toggleNested}
|
|
304
|
+
arrowRightIcon={ctx.arrowRightIcon}
|
|
305
|
+
/>
|
|
306
|
+
))}
|
|
307
|
+
</div>
|
|
308
|
+
) : (
|
|
309
|
+
children ?? ctx.detailsChildren
|
|
310
|
+
)}
|
|
311
|
+
</div>
|
|
312
|
+
</div>
|
|
313
|
+
)
|
|
314
|
+
}
|
|
236
315
|
|
|
237
|
-
|
|
238
|
-
if (typeof text !== 'string') return text
|
|
239
|
-
if (status === 'in-progress') {
|
|
240
|
-
return (
|
|
241
|
-
<motion.span
|
|
242
|
-
className="text-xs leading-xs font-normal relative inline-block bg-clip-text text-transparent bg-[length:250%_100%,auto] bg-no-repeat truncate"
|
|
243
|
-
style={{
|
|
244
|
-
backgroundImage: `linear-gradient(90deg, transparent calc(50% - ${dynamicSpread}px), var(--color-text), transparent calc(50% + ${dynamicSpread}px)), linear-gradient(var(--color-text-tertiary), var(--color-text-tertiary))`,
|
|
245
|
-
}}
|
|
246
|
-
initial={{ backgroundPosition: '100% center' }}
|
|
247
|
-
animate={{ backgroundPosition: '0% center' }}
|
|
248
|
-
transition={{ repeat: Infinity, duration, ease: 'linear' }}
|
|
249
|
-
>
|
|
250
|
-
{text}
|
|
251
|
-
</motion.span>
|
|
252
|
-
)
|
|
253
|
-
}
|
|
254
|
-
return (
|
|
255
|
-
<span className="text-xs leading-xs font-normal text-text-tertiary truncate">
|
|
256
|
-
{text}
|
|
257
|
-
</span>
|
|
258
|
-
)
|
|
259
|
-
}
|
|
316
|
+
ReasoningStepDetails.displayName = 'ReasoningStepDetails'
|
|
260
317
|
|
|
261
|
-
|
|
262
|
-
if (!description) return null
|
|
263
|
-
return (
|
|
264
|
-
<span className="text-xs leading-xs font-normal text-text-quaternary truncate">
|
|
265
|
-
{description}
|
|
266
|
-
</span>
|
|
267
|
-
)
|
|
268
|
-
}
|
|
318
|
+
// ===== ReasoningStep.DefaultLayout =====
|
|
269
319
|
|
|
270
|
-
|
|
271
|
-
const
|
|
272
|
-
setExpandedNestedKeys((prev) => {
|
|
273
|
-
const next = new Set(prev)
|
|
274
|
-
if (next.has(pathKey)) next.delete(pathKey)
|
|
275
|
-
else next.add(pathKey)
|
|
276
|
-
return next
|
|
277
|
-
})
|
|
278
|
-
}, [])
|
|
279
|
-
|
|
280
|
-
const hasDetails = details != null && details.length > 0
|
|
281
|
-
const hasExpandableContent = hasDetails || children != null
|
|
282
|
-
|
|
283
|
-
const handleToggle = () => {
|
|
284
|
-
if (hasExpandableContent) {
|
|
285
|
-
if (isControlled) onExpandedChange?.(!expandedProp)
|
|
286
|
-
else setIsExpandedInternal((e) => !e)
|
|
287
|
-
}
|
|
288
|
-
}
|
|
320
|
+
export function ReasoningStepDefaultLayout() {
|
|
321
|
+
const ctx = useReasoningStepContext()
|
|
289
322
|
|
|
290
323
|
return (
|
|
291
324
|
<div
|
|
292
325
|
data-reasoning-step
|
|
293
326
|
className="flex flex-col gap-1 py-0.5"
|
|
294
|
-
onMouseEnter={() => setIsHovered(true)}
|
|
295
|
-
onMouseLeave={() => setIsHovered(false)}
|
|
327
|
+
onMouseEnter={() => ctx.setIsHovered(true)}
|
|
328
|
+
onMouseLeave={() => ctx.setIsHovered(false)}
|
|
296
329
|
>
|
|
297
|
-
<
|
|
298
|
-
|
|
299
|
-
onClick={handleToggle}
|
|
300
|
-
>
|
|
301
|
-
{effectiveShowIcon && (
|
|
302
|
-
<div className="flex items-center justify-center shrink-0" style={iconSizeStyle}>
|
|
303
|
-
{getIcon()}
|
|
304
|
-
</div>
|
|
305
|
-
)}
|
|
306
|
-
<div className="flex flex-row items-center gap-1 flex-1 min-w-0">
|
|
307
|
-
{renderPrimaryText()}
|
|
308
|
-
{description ? renderDescription() : null}
|
|
309
|
-
{hasExpandableContent && (
|
|
310
|
-
<div
|
|
311
|
-
className={cn(
|
|
312
|
-
'flex items-center justify-center shrink-0 text-text-tertiary transition-opacity duration-200 ease-in-out',
|
|
313
|
-
effectiveExpanded ? 'opacity-100' : isHovered ? 'opacity-100' : 'opacity-0',
|
|
314
|
-
effectiveExpanded ? 'rotate-90' : 'rotate-0'
|
|
315
|
-
)}
|
|
316
|
-
style={iconSizeStyle}
|
|
317
|
-
>
|
|
318
|
-
{defaultArrow}
|
|
319
|
-
</div>
|
|
320
|
-
)}
|
|
321
|
-
</div>
|
|
322
|
-
</div>
|
|
323
|
-
|
|
324
|
-
{hasExpandableContent && (
|
|
325
|
-
<div
|
|
326
|
-
className={cn(
|
|
327
|
-
'grid transition-[grid-template-rows,opacity] duration-200 ease-out',
|
|
328
|
-
effectiveExpanded ? 'grid-rows-[1fr] opacity-100' : 'grid-rows-[0fr] opacity-0'
|
|
329
|
-
)}
|
|
330
|
-
>
|
|
331
|
-
<div className="overflow-hidden">
|
|
332
|
-
{hasDetails ? (
|
|
333
|
-
<div className="flex flex-col">
|
|
334
|
-
{details?.map((d, i) => (
|
|
335
|
-
<DetailRow
|
|
336
|
-
key={String(i)}
|
|
337
|
-
detail={d}
|
|
338
|
-
depth={0}
|
|
339
|
-
pathKey={String(i)}
|
|
340
|
-
detailIndent={detailIndent}
|
|
341
|
-
disableExpandAnimation={disableExpandAnimation}
|
|
342
|
-
expandedNestedKeys={expandedNestedKeys}
|
|
343
|
-
onToggleNested={toggleNested}
|
|
344
|
-
arrowRightIcon={defaultArrow}
|
|
345
|
-
/>
|
|
346
|
-
))}
|
|
347
|
-
</div>
|
|
348
|
-
) : (
|
|
349
|
-
children
|
|
350
|
-
)}
|
|
351
|
-
</div>
|
|
352
|
-
</div>
|
|
353
|
-
)}
|
|
330
|
+
<ReasoningStepHeader />
|
|
331
|
+
{ctx.hasExpandableContent && <ReasoningStepDetails />}
|
|
354
332
|
</div>
|
|
355
333
|
)
|
|
356
334
|
}
|
|
357
335
|
|
|
358
|
-
|
|
336
|
+
ReasoningStepDefaultLayout.displayName = 'ReasoningStepDefaultLayout'
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { createContext, useContext, useState, useCallback, useMemo } from 'react'
|
|
2
|
+
import type { ReactNode } from 'react'
|
|
3
|
+
import { EyeLine, ArrowRightLine } from '../../basic/icons-inline'
|
|
4
|
+
import type { ReasoningStepContextValue, ReasoningStepRootProps } from './types'
|
|
5
|
+
|
|
6
|
+
// ===== 样式常量 =====
|
|
7
|
+
|
|
8
|
+
const eyeClass = 'w-full h-full text-text-tertiary transition-transform duration-200 ease-in-out'
|
|
9
|
+
const arrowClass = 'w-full h-full transition-transform duration-200 ease-in-out'
|
|
10
|
+
|
|
11
|
+
// ===== Context =====
|
|
12
|
+
|
|
13
|
+
const ReasoningStepContext = createContext<ReasoningStepContextValue | null>(null)
|
|
14
|
+
ReasoningStepContext.displayName = 'ReasoningStepContext'
|
|
15
|
+
|
|
16
|
+
export function useReasoningStepContext() {
|
|
17
|
+
const ctx = useContext(ReasoningStepContext)
|
|
18
|
+
if (!ctx) throw new Error('ReasoningStep compound components must be used within ReasoningStep.Root')
|
|
19
|
+
return ctx
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ===== Provider =====
|
|
23
|
+
|
|
24
|
+
export function ReasoningStepRootProvider({
|
|
25
|
+
text,
|
|
26
|
+
description,
|
|
27
|
+
status = 'completed',
|
|
28
|
+
icon,
|
|
29
|
+
showIcon = true,
|
|
30
|
+
details,
|
|
31
|
+
children,
|
|
32
|
+
detailsChildren,
|
|
33
|
+
duration = 2,
|
|
34
|
+
spread = 2,
|
|
35
|
+
disableExpandAnimation = false,
|
|
36
|
+
defaultExpanded,
|
|
37
|
+
expanded: expandedProp,
|
|
38
|
+
onExpandedChange,
|
|
39
|
+
eyeIcon,
|
|
40
|
+
arrowRightIcon,
|
|
41
|
+
}: ReasoningStepRootProps & { children?: ReactNode; detailsChildren?: ReactNode }) {
|
|
42
|
+
const isControlled = expandedProp !== undefined
|
|
43
|
+
const [isExpandedInternal, setIsExpandedInternal] = useState(defaultExpanded ?? false)
|
|
44
|
+
const isExpanded = isControlled ? expandedProp : isExpandedInternal
|
|
45
|
+
const [isHovered, setIsHovered] = useState(false)
|
|
46
|
+
|
|
47
|
+
const effectiveExpanded = disableExpandAnimation && defaultExpanded !== undefined
|
|
48
|
+
? defaultExpanded
|
|
49
|
+
: isExpanded
|
|
50
|
+
|
|
51
|
+
const effectiveShowIcon = showIcon && typeof text === 'string'
|
|
52
|
+
const detailIndent = effectiveShowIcon ? 'var(--spacing-1.5)' : 'var(--spacing-2.5)'
|
|
53
|
+
|
|
54
|
+
const hasDetails = details != null && details.length > 0
|
|
55
|
+
const hasExpandableContent = hasDetails || detailsChildren != null
|
|
56
|
+
|
|
57
|
+
const toggleExpanded = useCallback(() => {
|
|
58
|
+
if (hasExpandableContent) {
|
|
59
|
+
if (isControlled) onExpandedChange?.(!expandedProp)
|
|
60
|
+
else setIsExpandedInternal((e) => !e)
|
|
61
|
+
}
|
|
62
|
+
}, [hasExpandableContent, isControlled, expandedProp, onExpandedChange])
|
|
63
|
+
|
|
64
|
+
const [expandedNestedKeys, setExpandedNestedKeys] = useState<Set<string>>(new Set())
|
|
65
|
+
const toggleNested = useCallback((pathKey: string) => {
|
|
66
|
+
setExpandedNestedKeys((prev) => {
|
|
67
|
+
const next = new Set(prev)
|
|
68
|
+
if (next.has(pathKey)) next.delete(pathKey)
|
|
69
|
+
else next.add(pathKey)
|
|
70
|
+
return next
|
|
71
|
+
})
|
|
72
|
+
}, [])
|
|
73
|
+
|
|
74
|
+
const defaultEye = useMemo(
|
|
75
|
+
() => eyeIcon ?? <EyeLine className={eyeClass} />,
|
|
76
|
+
[eyeIcon]
|
|
77
|
+
)
|
|
78
|
+
const defaultArrow = useMemo(
|
|
79
|
+
() => arrowRightIcon ?? <ArrowRightLine className={arrowClass} />,
|
|
80
|
+
[arrowRightIcon]
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
const ctxValue: ReasoningStepContextValue = {
|
|
84
|
+
status,
|
|
85
|
+
isExpanded,
|
|
86
|
+
isHovered,
|
|
87
|
+
setIsHovered,
|
|
88
|
+
toggleExpanded,
|
|
89
|
+
effectiveExpanded,
|
|
90
|
+
effectiveShowIcon,
|
|
91
|
+
hasExpandableContent,
|
|
92
|
+
text,
|
|
93
|
+
description,
|
|
94
|
+
details,
|
|
95
|
+
detailsChildren,
|
|
96
|
+
duration,
|
|
97
|
+
spread,
|
|
98
|
+
disableExpandAnimation,
|
|
99
|
+
detailIndent,
|
|
100
|
+
expandedNestedKeys,
|
|
101
|
+
toggleNested,
|
|
102
|
+
icon,
|
|
103
|
+
eyeIcon: defaultEye,
|
|
104
|
+
arrowRightIcon: defaultArrow,
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<ReasoningStepContext.Provider value={ctxValue}>
|
|
109
|
+
{children}
|
|
110
|
+
</ReasoningStepContext.Provider>
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
ReasoningStepRootProvider.displayName = 'ReasoningStepRoot'
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { ReasoningStepRootProvider, useReasoningStepContext } from './context'
|
|
2
|
+
import {
|
|
3
|
+
ReasoningStepHeader,
|
|
4
|
+
ReasoningStepDetails,
|
|
5
|
+
ReasoningStepDetailRow,
|
|
6
|
+
ReasoningStepDefaultLayout,
|
|
7
|
+
} from './compound'
|
|
8
|
+
import type {
|
|
9
|
+
ReasoningStepProps,
|
|
10
|
+
ReasoningStepCompound,
|
|
11
|
+
ReasoningStepDetail,
|
|
12
|
+
ReasoningStepStatus,
|
|
13
|
+
} from './types'
|
|
14
|
+
|
|
15
|
+
// ===== ReasoningStep 便捷组件 =====
|
|
16
|
+
|
|
17
|
+
function ReasoningStepBase(props: ReasoningStepProps) {
|
|
18
|
+
return (
|
|
19
|
+
<ReasoningStepRootProvider {...props}>
|
|
20
|
+
<ReasoningStepDefaultLayout />
|
|
21
|
+
</ReasoningStepRootProvider>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
ReasoningStepBase.displayName = 'ReasoningStep'
|
|
26
|
+
|
|
27
|
+
// ===== 挂载复合组件 =====
|
|
28
|
+
|
|
29
|
+
type ReasoningStepBaseType = React.FC<ReasoningStepProps> & Partial<ReasoningStepCompound>
|
|
30
|
+
|
|
31
|
+
const ReasoningStepWithSlots = ReasoningStepBase as ReasoningStepBaseType
|
|
32
|
+
|
|
33
|
+
ReasoningStepWithSlots.Root = ReasoningStepRootProvider
|
|
34
|
+
ReasoningStepWithSlots.Header = ReasoningStepHeader
|
|
35
|
+
ReasoningStepWithSlots.Details = ReasoningStepDetails
|
|
36
|
+
ReasoningStepWithSlots.DetailRow = ReasoningStepDetailRow
|
|
37
|
+
|
|
38
|
+
export const ReasoningStep = ReasoningStepWithSlots as React.FC<ReasoningStepProps> &
|
|
39
|
+
ReasoningStepCompound
|
|
40
|
+
|
|
41
|
+
// ===== 导出类型和工具 =====
|
|
42
|
+
|
|
43
|
+
export type { ReasoningStepProps, ReasoningStepDetail, ReasoningStepStatus }
|
|
44
|
+
|
|
45
|
+
export { useReasoningStepContext }
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
// ===== 基础类型 =====
|
|
4
|
+
|
|
5
|
+
export type ReasoningStepDetail =
|
|
6
|
+
| { type: 'action'; action: string; desc?: string; details?: ReasoningStepDetail[] }
|
|
7
|
+
| {
|
|
8
|
+
type: 'file'
|
|
9
|
+
filename: string
|
|
10
|
+
lines?: string
|
|
11
|
+
path?: string
|
|
12
|
+
fileType?: string
|
|
13
|
+
details?: ReasoningStepDetail[]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type ReasoningStepStatus = 'completed' | 'in-progress'
|
|
17
|
+
|
|
18
|
+
// ===== 组件 Props =====
|
|
19
|
+
|
|
20
|
+
export interface ReasoningStepRootProps {
|
|
21
|
+
/** 主文本 */
|
|
22
|
+
text: ReactNode
|
|
23
|
+
/** 描述文本 */
|
|
24
|
+
description?: string
|
|
25
|
+
/** 状态 */
|
|
26
|
+
status?: ReasoningStepStatus
|
|
27
|
+
/** 自定义图标 */
|
|
28
|
+
icon?: ReactNode
|
|
29
|
+
/** 是否显示图标 */
|
|
30
|
+
showIcon?: boolean
|
|
31
|
+
/** 详情数据 */
|
|
32
|
+
details?: ReasoningStepDetail[]
|
|
33
|
+
/** 子组件 */
|
|
34
|
+
children?: ReactNode
|
|
35
|
+
/** 动画持续时间 */
|
|
36
|
+
duration?: number
|
|
37
|
+
/** 动画扩散度 */
|
|
38
|
+
spread?: number
|
|
39
|
+
/** 禁用展开动画 */
|
|
40
|
+
disableExpandAnimation?: boolean
|
|
41
|
+
/** 默认展开 */
|
|
42
|
+
defaultExpanded?: boolean
|
|
43
|
+
/** 受控展开状态 */
|
|
44
|
+
expanded?: boolean
|
|
45
|
+
/** 展开状态变化回调 */
|
|
46
|
+
onExpandedChange?: (expanded: boolean) => void
|
|
47
|
+
/** 自定义眼睛图标 */
|
|
48
|
+
eyeIcon?: ReactNode
|
|
49
|
+
/** 自定义箭头图标 */
|
|
50
|
+
arrowRightIcon?: ReactNode
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export type ReasoningStepProps = ReasoningStepRootProps
|
|
54
|
+
|
|
55
|
+
export interface ReasoningStepHeaderProps {
|
|
56
|
+
className?: string
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface ReasoningStepDetailsProps {
|
|
60
|
+
className?: string
|
|
61
|
+
children?: ReactNode
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface ReasoningStepDetailRowProps {
|
|
65
|
+
detail: ReasoningStepDetail
|
|
66
|
+
depth?: number
|
|
67
|
+
pathKey?: string
|
|
68
|
+
className?: string
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ===== Context 类型 =====
|
|
72
|
+
|
|
73
|
+
export interface ReasoningStepContextValue {
|
|
74
|
+
// 状态
|
|
75
|
+
status: ReasoningStepStatus
|
|
76
|
+
isExpanded: boolean
|
|
77
|
+
isHovered: boolean
|
|
78
|
+
setIsHovered: (hovered: boolean) => void
|
|
79
|
+
toggleExpanded: () => void
|
|
80
|
+
effectiveExpanded: boolean
|
|
81
|
+
effectiveShowIcon: boolean
|
|
82
|
+
hasExpandableContent: boolean
|
|
83
|
+
// 数据
|
|
84
|
+
text: ReactNode
|
|
85
|
+
description?: string
|
|
86
|
+
details?: ReasoningStepDetail[]
|
|
87
|
+
detailsChildren?: ReactNode
|
|
88
|
+
// 配置
|
|
89
|
+
duration: number
|
|
90
|
+
spread: number
|
|
91
|
+
disableExpandAnimation: boolean
|
|
92
|
+
detailIndent: string
|
|
93
|
+
// 嵌套状态
|
|
94
|
+
expandedNestedKeys: Set<string>
|
|
95
|
+
toggleNested: (pathKey: string) => void
|
|
96
|
+
// 图标
|
|
97
|
+
icon?: ReactNode
|
|
98
|
+
eyeIcon: ReactNode
|
|
99
|
+
arrowRightIcon: ReactNode
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ===== 复合组件类型 =====
|
|
103
|
+
|
|
104
|
+
export interface ReasoningStepCompound {
|
|
105
|
+
Root: React.FC<ReasoningStepRootProps>
|
|
106
|
+
Header: React.FC<ReasoningStepHeaderProps>
|
|
107
|
+
Details: React.FC<ReasoningStepDetailsProps>
|
|
108
|
+
DetailRow: React.FC<ReasoningStepDetailRowProps>
|
|
109
|
+
}
|