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.
Files changed (104) hide show
  1. package/README.md +7 -5
  2. package/cli/dist/index.js +0 -0
  3. package/cli/dist/utils/tokens.js +103 -17
  4. package/cli/registry/basic/button.test.tsx +333 -0
  5. package/cli/registry/chat/{question-part.tsx → ask-user-part.tsx} +4 -4
  6. package/cli/registry/chat/{browser-use-part.tsx → browser-action-part.tsx} +6 -6
  7. package/cli/registry/chat/{suggestion-part.tsx → hint-banner.tsx} +4 -4
  8. package/cli/registry/chat/markdown.test.tsx +387 -0
  9. package/cli/registry/chat/{reasoning-step.tsx → reasoning-step/compound.tsx} +163 -185
  10. package/cli/registry/chat/reasoning-step/context.tsx +114 -0
  11. package/cli/registry/chat/reasoning-step/index.tsx +45 -0
  12. package/cli/registry/chat/reasoning-step/types.ts +109 -0
  13. package/cli/registry/chat/response/compound.tsx +210 -0
  14. package/cli/registry/chat/{response.tsx → response/context.tsx} +65 -136
  15. package/cli/registry/chat/response/index.tsx +87 -0
  16. package/cli/registry/chat/response/types.ts +123 -0
  17. package/cli/registry/chat/thinking-indicator.test.tsx +244 -0
  18. package/cli/registry/chat/tool-invocation-card.test.tsx +346 -0
  19. package/cli/registry/chat/{request.tsx → user-message.tsx} +3 -3
  20. package/cli/registry/chat/user-question/compound.tsx +324 -0
  21. package/cli/registry/chat/user-question/context.tsx +456 -0
  22. package/cli/registry/chat/user-question/index.tsx +71 -316
  23. package/cli/registry/chat/user-question/useUserQuestionKeyboard.ts +5 -6
  24. package/cli/registry/tokens/index.css +31 -0
  25. package/cli/registry/tokens/scale/computed.css +103 -0
  26. package/cli/registry/tokens/scale/config.css +110 -0
  27. package/cli/registry/tokens/scale/index.css +30 -0
  28. package/cli/registry/tokens/scale/presets/compact.css +30 -0
  29. package/cli/registry/tokens/scale/presets/dense.css +64 -0
  30. package/cli/registry/tokens/scale/presets/sharp.css +40 -0
  31. package/cli/registry/tokens/scale/presets/soft.css +16 -0
  32. package/cli/registry/tokens/scale.css +12 -298
  33. package/cli/registry/tokens/scrollbar-utility.css +35 -0
  34. package/cli/registry/tokens/themes/dark-parchment.css +132 -0
  35. package/cli/registry/tokens/themes/dark-qoder.css +132 -0
  36. package/cli/registry/tokens/themes/light-parchment.css +123 -0
  37. package/cli/registry/tokens/themes/light-qoder.css +131 -0
  38. package/dist/qoder-design.css +1 -1
  39. package/dist/registry/chat/ask-user-part.d.ts +24 -0
  40. package/dist/registry/chat/browser-action-part.d.ts +28 -0
  41. package/dist/registry/chat/{suggestion-part.d.ts → hint-banner.d.ts} +4 -4
  42. package/dist/registry/chat/reasoning-step/compound.d.ts +17 -0
  43. package/dist/registry/chat/reasoning-step/context.d.ts +10 -0
  44. package/dist/registry/chat/reasoning-step/index.d.ts +14 -0
  45. package/dist/registry/chat/reasoning-step/types.d.ts +95 -0
  46. package/dist/registry/chat/response/compound.d.ts +25 -0
  47. package/dist/registry/chat/response/context.d.ts +9 -0
  48. package/dist/registry/chat/response/index.d.ts +15 -0
  49. package/dist/registry/chat/response/types.d.ts +99 -0
  50. package/dist/registry/chat/user-message.d.ts +6 -0
  51. package/dist/registry/chat/user-question/compound.d.ts +37 -0
  52. package/dist/registry/chat/user-question/context.d.ts +55 -0
  53. package/dist/registry/chat/user-question/index.d.ts +13 -5
  54. package/dist/registry/chat/user-question/useUserQuestionKeyboard.d.ts +2 -3
  55. package/dist/scale.css +9 -303
  56. package/dist/spark-design.cjs.js +62 -62
  57. package/dist/spark-design.es.js +3992 -3826
  58. package/dist/src/components/chat/AskUserPart/index.d.ts +6 -0
  59. package/dist/src/components/chat/BrowserActionPart/index.d.ts +7 -0
  60. package/dist/src/components/chat/HintBanner/index.d.ts +6 -0
  61. package/dist/src/components/chat/ReasoningStep/index.d.ts +11 -5
  62. package/dist/src/components/chat/Response/index.d.ts +16 -6
  63. package/dist/src/components/chat/UserMessage/index.d.ts +7 -0
  64. package/dist/src/components/chat/UserQuestion/index.d.ts +18 -4
  65. package/dist/src/components/index.d.ts +63 -63
  66. package/dist/theme.css +13 -800
  67. package/package.json +27 -3
  68. package/dist/registry/chat/browser-use-part.d.ts +0 -28
  69. package/dist/registry/chat/question-part.d.ts +0 -24
  70. package/dist/registry/chat/reasoning-step.d.ts +0 -35
  71. package/dist/registry/chat/request.d.ts +0 -6
  72. package/dist/registry/chat/response.d.ts +0 -28
  73. package/dist/src/components/chat/BrowserUsePart/index.d.ts +0 -7
  74. package/dist/src/components/chat/QuestionPart/index.d.ts +0 -6
  75. package/dist/src/components/chat/Request/index.d.ts +0 -7
  76. package/dist/src/components/chat/SuggestionPart/index.d.ts +0 -6
  77. /package/dist/src/components/{foundation → basic}/AlertDialog/index.d.ts +0 -0
  78. /package/dist/src/components/{foundation → basic}/Avatar/index.d.ts +0 -0
  79. /package/dist/src/components/{foundation → basic}/Button/index.d.ts +0 -0
  80. /package/dist/src/components/{foundation → basic}/Collapse/index.d.ts +0 -0
  81. /package/dist/src/components/{foundation → basic}/Collapsible/index.d.ts +0 -0
  82. /package/dist/src/components/{foundation → basic}/CollapsibleSection/index.d.ts +0 -0
  83. /package/dist/src/components/{foundation → basic}/DropdownMenu/index.d.ts +0 -0
  84. /package/dist/src/components/{foundation → basic}/EllipsisText/index.d.ts +0 -0
  85. /package/dist/src/components/{foundation → basic}/IconButton/index.d.ts +0 -0
  86. /package/dist/src/components/{foundation → basic}/Kbd/index.d.ts +0 -0
  87. /package/dist/src/components/{foundation → basic}/OptionList/index.d.ts +0 -0
  88. /package/dist/src/components/{foundation → basic}/Pagination/index.d.ts +0 -0
  89. /package/dist/src/components/{foundation → basic}/Progress/index.d.ts +0 -0
  90. /package/dist/src/components/{foundation → basic}/RadioGroup/index.d.ts +0 -0
  91. /package/dist/src/components/{foundation → basic}/Resizable/index.d.ts +0 -0
  92. /package/dist/src/components/{foundation → basic}/Scrollbar/index.d.ts +0 -0
  93. /package/dist/src/components/{foundation → basic}/Select/index.d.ts +0 -0
  94. /package/dist/src/components/{foundation → basic}/Skeleton/index.d.ts +0 -0
  95. /package/dist/src/components/{foundation → basic}/Slider/index.d.ts +0 -0
  96. /package/dist/src/components/{foundation → basic}/Spinner/index.d.ts +0 -0
  97. /package/dist/src/components/{foundation → basic}/Switch/index.d.ts +0 -0
  98. /package/dist/src/components/{foundation → basic}/Table/index.d.ts +0 -0
  99. /package/dist/src/components/{foundation → basic}/Tabs/index.d.ts +0 -0
  100. /package/dist/src/components/{foundation → basic}/Tag/index.d.ts +0 -0
  101. /package/dist/src/components/{foundation → basic}/Toast/index.d.ts +0 -0
  102. /package/dist/src/components/{foundation → basic}/Toggle/index.d.ts +0 -0
  103. /package/dist/src/components/{foundation → basic}/Tooltip/index.d.ts +0 -0
  104. /package/dist/src/components/{foundation → basic}/Typography/index.d.ts +0 -0
@@ -0,0 +1,210 @@
1
+ import { useMemo, type ReactNode } from 'react'
2
+ import { cn } from '../../lib/utils'
3
+ import { MarkdownBody } from '../markdown'
4
+ import { ReasoningStep } from '../reasoning-step'
5
+ import { StreamingMarkdownBlock } from '../streaming-markdown-block'
6
+ import { ImageGenerating as ImageGeneratingBase } from '../image-generating'
7
+ import { useResponseContext } from './context'
8
+ import type {
9
+ ResponseThinkingProps,
10
+ ResponseStepsProps,
11
+ ResponseStepProps,
12
+ ResponseContentProps,
13
+ ResponseImageGeneratingProps,
14
+ ResponseDefaultLayoutProps,
15
+ } from './types'
16
+
17
+ // ===== Response.Thinking =====
18
+
19
+ export function ResponseThinking({ className }: ResponseThinkingProps) {
20
+ const ctx = useResponseContext()
21
+ const effectiveShowThink = ctx.simulate
22
+ ? ctx.showThink
23
+ : ctx.phase === 'thinking' || ctx.phase === 'streaming'
24
+
25
+ if (!effectiveShowThink) return null
26
+
27
+ return <span className={className}>{ctx.thinkingIndicator}</span>
28
+ }
29
+
30
+ ResponseThinking.displayName = 'ResponseThinking'
31
+
32
+ // ===== Response.Steps =====
33
+
34
+ export function ResponseSteps({ className, children }: ResponseStepsProps) {
35
+ return <div className={cn('flex flex-col gap-1', className)}>{children}</div>
36
+ }
37
+
38
+ ResponseSteps.displayName = 'ResponseSteps'
39
+
40
+ // ===== Response.Step =====
41
+
42
+ export function ResponseStepComponent({ step, status = 'completed' }: ResponseStepProps) {
43
+ return (
44
+ <ReasoningStep
45
+ text={step.text}
46
+ description={step.description}
47
+ details={step.details}
48
+ status={status}
49
+ />
50
+ )
51
+ }
52
+
53
+ ResponseStepComponent.displayName = 'ResponseStep'
54
+
55
+ // ===== Response.Content =====
56
+
57
+ export function ResponseContent({
58
+ markdown,
59
+ streaming = false,
60
+ onStreamComplete,
61
+ className,
62
+ }: ResponseContentProps) {
63
+ const ctx = useResponseContext()
64
+ const content = markdown ?? ctx.finalMarkdown
65
+
66
+ if (streaming) {
67
+ return (
68
+ <div className={cn('mt-1', className)}>
69
+ <StreamingMarkdownBlock
70
+ content={content}
71
+ isActive={streaming}
72
+ onStreamComplete={onStreamComplete}
73
+ />
74
+ </div>
75
+ )
76
+ }
77
+
78
+ return (
79
+ <div className={cn('mt-1', className)}>
80
+ <MarkdownBody>{content}</MarkdownBody>
81
+ </div>
82
+ )
83
+ }
84
+
85
+ ResponseContent.displayName = 'ResponseContent'
86
+
87
+ // ===== Response.ImageGenerating =====
88
+
89
+ export function ResponseImageGenerating({
90
+ width = '320px',
91
+ height = '240px',
92
+ className,
93
+ }: ResponseImageGeneratingProps) {
94
+ return <ImageGeneratingBase width={width} height={height} className={className} />
95
+ }
96
+
97
+ ResponseImageGenerating.displayName = 'ResponseImageGenerating'
98
+
99
+ // ===== Response.DefaultLayout =====
100
+
101
+ export function ResponseDefaultLayout({ className }: ResponseDefaultLayoutProps) {
102
+ const ctx = useResponseContext()
103
+
104
+ // 模拟模式:流式渲染 - useMemo 必须在条件语句前调用
105
+ const contentElements = useMemo(() => {
106
+ if (!ctx.simulate) return []
107
+
108
+ const el: ReactNode[] = []
109
+ const upTo = Math.min(ctx.visibleIndex, ctx.blocks.length - 1)
110
+ let lastVisibleComposerIndex = -1
111
+ for (let j = 0; j <= upTo; j++) {
112
+ if (ctx.blocks[j]?.type === 'composer') lastVisibleComposerIndex = j
113
+ }
114
+ for (let i = 0; i <= upTo; i++) {
115
+ const b = ctx.blocks[i]
116
+ if (!b) continue
117
+ if (b.type === 'think') {
118
+ if (i === ctx.visibleIndex) {
119
+ el.push(<span key={`think-${i}`}>{ctx.thinkingIndicator}</span>)
120
+ }
121
+ continue
122
+ }
123
+ if (b.type === 'composer' && b.step) {
124
+ const inProgress = i === lastVisibleComposerIndex && !ctx.isSimDone
125
+ el.push(
126
+ <ReasoningStep
127
+ key={i}
128
+ text={b.step.text}
129
+ description={b.step.description}
130
+ details={b.step.details}
131
+ status={inProgress ? 'in-progress' : 'completed'}
132
+ />
133
+ )
134
+ }
135
+ if (b.type === 'md' && b.md) {
136
+ el.push(
137
+ <div key={`md-${i}`} className="mt-1">
138
+ <StreamingMarkdownBlock
139
+ content={b.md}
140
+ isActive={i === upTo}
141
+ onStreamComplete={() => {
142
+ ctx.setVisibleIndex((v) => Math.min(v + 1, ctx.blocks.length))
143
+ if (i + 1 >= ctx.blocks.length) ctx.onSimulateComplete?.()
144
+ }}
145
+ />
146
+ </div>
147
+ )
148
+ }
149
+ }
150
+ return el
151
+ }, [ctx])
152
+
153
+ // 静态内容元素(非模拟模式)
154
+ const staticElements = useMemo(() => {
155
+ if (ctx.simulate) return []
156
+ const staticEl: ReactNode[] = []
157
+ for (let i = 0; i < ctx.blocks.length; i++) {
158
+ const b = ctx.blocks[i]
159
+ if (!b) continue
160
+ if (b.type === 'composer' && b.step) {
161
+ staticEl.push(
162
+ <ReasoningStep
163
+ key={i}
164
+ text={b.step.text}
165
+ description={b.step.description}
166
+ details={b.step.details}
167
+ status="completed"
168
+ disableExpandAnimation
169
+ />
170
+ )
171
+ }
172
+ if (b.type === 'md' && b.md) {
173
+ staticEl.push(
174
+ <div key={`md-${i}`} className="mt-1">
175
+ <MarkdownBody>{b.md}</MarkdownBody>
176
+ </div>
177
+ )
178
+ }
179
+ }
180
+ return staticEl
181
+ }, [ctx])
182
+
183
+ // imageGenerating 阶段
184
+ if (ctx.phase === 'imageGenerating') {
185
+ return (
186
+ <div className={cn('flex flex-col gap-3 font-body', className)}>
187
+ <ResponseImageGenerating />
188
+ </div>
189
+ )
190
+ }
191
+
192
+ // 非模拟模式:静态渲染
193
+ if (!ctx.simulate) {
194
+ return (
195
+ <div className={cn('flex flex-col gap-3 font-body', className)}>
196
+ <div className="flex flex-col gap-1">{staticElements}</div>
197
+ {(ctx.phase === 'thinking' || ctx.phase === 'streaming') && ctx.thinkingIndicator}
198
+ </div>
199
+ )
200
+ }
201
+
202
+ // 模拟模式
203
+ return (
204
+ <div className={cn('flex flex-col gap-3 font-body', className)}>
205
+ <div className="flex flex-col gap-1">{contentElements}</div>
206
+ </div>
207
+ )
208
+ }
209
+
210
+ ResponseDefaultLayout.displayName = 'ResponseDefaultLayout'
@@ -1,38 +1,14 @@
1
- import { useEffect, useMemo, useState } from 'react'
2
- import type { HTMLAttributes, ReactNode } from 'react'
3
- import { cn } from '@/lib/utils'
4
- import { MarkdownBody } from './markdown'
5
- import { ThinkingIndicator } from './thinking-indicator'
6
- import { ImageGenerating } from './image-generating'
7
- import { ReasoningStep } from './reasoning-step'
8
- import type { ReasoningStepDetail } from './reasoning-step'
9
- import { StreamingMarkdownBlock } from './streaming-markdown-block'
10
-
11
- export type ResponsePhase = 'thinking' | 'streaming' | 'done' | 'imageGenerating'
12
- export type ResponseStepToolType = 'read' | 'edit' | 'search'
13
-
14
- export interface ResponseStep {
15
- text: string
16
- description?: string
17
- details?: ReasoningStepDetail[]
18
- toolType?: ResponseStepToolType
19
- }
20
-
21
- export interface ResponseRound {
22
- step: ResponseStep
23
- simpleMd: string
24
- }
25
-
26
- export interface ResponseProps extends HTMLAttributes<HTMLDivElement> {
27
- simulate?: boolean
28
- phase?: ResponsePhase
29
- rounds?: ResponseRound[]
30
- finalMarkdown?: string
31
- onSimulateComplete?: () => void
32
- onStepChange?: (text: string, toolType?: ResponseStepToolType) => void
33
- /** 主库可注入带 Lottie 的 ThinkingIndicator,与独立 ThinkingIndicator 动效一致 */
34
- thinkingIndicator?: ReactNode
35
- }
1
+ import { createContext, useContext, useState, useEffect, useMemo, useRef } from 'react'
2
+ import { ThinkingIndicator } from '../thinking-indicator'
3
+ import type {
4
+ ResponseContextValue,
5
+ ResponseRootProps,
6
+ ResponseBlock,
7
+ ResponseRound,
8
+ ResponseStep,
9
+ } from './types'
10
+
11
+ // ===== Demo 数据 =====
36
12
 
37
13
  const DEMO_STEP_1: ResponseStep = {
38
14
  text: 'Scanning local photo library',
@@ -108,10 +84,10 @@ Example:
108
84
  If you want, I can generate a **preview report** first (top 50 renames + album summary) before applying changes.
109
85
  `
110
86
 
111
- type BlockType = 'think' | 'composer' | 'md'
87
+ // ===== 工具函数 =====
112
88
 
113
- function buildBlocks(rounds: ResponseRound[], finalMd: string): { type: BlockType; step?: ResponseStep; md?: string }[] {
114
- const blocks: { type: BlockType; step?: ResponseStep; md?: string }[] = []
89
+ function buildBlocks(rounds: ResponseRound[], finalMd: string): ResponseBlock[] {
90
+ const blocks: ResponseBlock[] = []
115
91
  for (const r of rounds) {
116
92
  blocks.push({ type: 'think' })
117
93
  blocks.push({ type: 'composer', step: r.step })
@@ -122,9 +98,24 @@ function buildBlocks(rounds: ResponseRound[], finalMd: string): { type: BlockTyp
122
98
  return blocks
123
99
  }
124
100
 
101
+ // ===== Context =====
102
+
103
+ const ResponseContext = createContext<ResponseContextValue | null>(null)
104
+ ResponseContext.displayName = 'ResponseContext'
105
+
106
+ export function useResponseContext() {
107
+ const ctx = useContext(ResponseContext)
108
+ if (!ctx) throw new Error('Response compound components must be used within Response.Root')
109
+ return ctx
110
+ }
111
+
112
+ // ===== Default ThinkingIndicator =====
113
+
125
114
  const DEFAULT_THINK = <ThinkingIndicator text="Qoder is thinking..." />
126
115
 
127
- export function Response({
116
+ // ===== Provider =====
117
+
118
+ export function ResponseRootProvider({
128
119
  simulate = true,
129
120
  phase = 'thinking',
130
121
  rounds,
@@ -132,18 +123,21 @@ export function Response({
132
123
  onSimulateComplete,
133
124
  onStepChange,
134
125
  thinkingIndicator,
135
- className,
136
- ...props
137
- }: ResponseProps) {
138
- const thinkNode = thinkingIndicator ?? DEFAULT_THINK
126
+ children,
127
+ }: ResponseRootProps) {
139
128
  const allRounds = rounds ?? DEMO_ROUNDS
140
129
  const finalMd = finalMarkdown ?? DEMO_FINAL_MD
130
+ const thinkNode = thinkingIndicator ?? DEFAULT_THINK
131
+
141
132
  const blocks = useMemo(() => buildBlocks(allRounds, finalMd), [allRounds, finalMd])
142
133
  const [visibleIndex, setVisibleIndex] = useState(0)
134
+ const prevBlocksLengthRef = useRef(blocks.length)
135
+
143
136
  const isSimDone = simulate ? visibleIndex >= blocks.length - 1 : phase === 'done'
144
137
  const currentBlock = blocks[visibleIndex] ?? null
145
138
  const showThink = currentBlock?.type === 'think'
146
139
 
140
+ // 步骤变化回调
147
141
  useEffect(() => {
148
142
  if (!simulate || blocks.length === 0) return
149
143
  const block = blocks[visibleIndex] ?? null
@@ -157,11 +151,17 @@ export function Response({
157
151
  }
158
152
  }, [simulate, blocks, visibleIndex, onStepChange, onSimulateComplete])
159
153
 
154
+ // blocks 变化时重置
160
155
  useEffect(() => {
161
156
  if (!simulate || blocks.length === 0) return
162
- setVisibleIndex(0)
157
+ if (prevBlocksLengthRef.current !== blocks.length) {
158
+ prevBlocksLengthRef.current = blocks.length
159
+ // 使用 requestAnimationFrame 避免同步 setState
160
+ requestAnimationFrame(() => setVisibleIndex(0))
161
+ }
163
162
  }, [simulate, blocks.length])
164
163
 
164
+ // 定时器推进
165
165
  useEffect(() => {
166
166
  if (!simulate || blocks.length === 0) return
167
167
  const block = blocks[visibleIndex] ?? null
@@ -171,101 +171,30 @@ export function Response({
171
171
  }
172
172
  }, [simulate, blocks, visibleIndex])
173
173
 
174
- const effectiveShowThink = simulate ? showThink : phase === 'thinking' || phase === 'streaming'
175
-
176
- const contentElements = useMemo(() => {
177
- const el: ReactNode[] = []
178
- const upTo = simulate ? Math.min(visibleIndex, blocks.length - 1) : blocks.length - 1
179
- let lastVisibleComposerIndex = -1
180
- for (let j = 0; j <= upTo; j++) {
181
- if (blocks[j]?.type === 'composer') lastVisibleComposerIndex = j
182
- }
183
- for (let i = 0; i <= upTo; i++) {
184
- const b = blocks[i]
185
- if (!b) continue
186
- if (b.type === 'think') {
187
- if (simulate && i === visibleIndex) {
188
- el.push(<span key={`think-${i}`}>{thinkNode}</span>)
189
- }
190
- continue
191
- }
192
- if (b.type === 'composer' && b.step) {
193
- const inProgress = i === lastVisibleComposerIndex && !isSimDone
194
- el.push(
195
- <ReasoningStep
196
- key={i}
197
- text={b.step.text}
198
- description={b.step.description}
199
- details={b.step.details}
200
- status={inProgress ? 'in-progress' : 'completed'}
201
- />
202
- )
203
- }
204
- if (b.type === 'md' && b.md) {
205
- el.push(
206
- <div key={`md-${i}`} className="mt-1">
207
- <StreamingMarkdownBlock
208
- content={b.md}
209
- isActive={i === upTo}
210
- onStreamComplete={() => {
211
- setVisibleIndex((v) => Math.min(v + 1, blocks.length))
212
- if (i + 1 >= blocks.length) onSimulateComplete?.()
213
- }}
214
- />
215
- </div>
216
- )
217
- }
218
- }
219
- return el
220
- }, [simulate, visibleIndex, blocks, isSimDone, onSimulateComplete, thinkNode])
221
-
222
- if (phase === 'imageGenerating') {
223
- return (
224
- <div className={cn('flex flex-col gap-3 font-body', className)} {...props}>
225
- <ImageGenerating width="320px" height="240px" />
226
- </div>
227
- )
228
- }
229
-
230
- if (!simulate) {
231
- const staticEl: ReactNode[] = []
232
- for (let i = 0; i < blocks.length; i++) {
233
- const b = blocks[i]
234
- if (!b) continue
235
- if (b.type === 'composer' && b.step) {
236
- staticEl.push(
237
- <ReasoningStep
238
- key={i}
239
- text={b.step.text}
240
- description={b.step.description}
241
- details={b.step.details}
242
- status="completed"
243
- disableExpandAnimation
244
- />
245
- )
246
- }
247
- if (b.type === 'md' && b.md) {
248
- staticEl.push(
249
- <div key={`md-${i}`} className="mt-1">
250
- <MarkdownBody>{b.md}</MarkdownBody>
251
- </div>
252
- )
253
- }
254
- }
255
- return (
256
- <div className={cn('flex flex-col gap-3 font-body', className)} {...props}>
257
- <div className="flex flex-col gap-1">{staticEl}</div>
258
- {(phase === 'thinking' || phase === 'streaming') && thinkNode}
259
- </div>
260
- )
174
+ const ctxValue: ResponseContextValue = {
175
+ phase,
176
+ simulate,
177
+ rounds: allRounds,
178
+ finalMarkdown: finalMd,
179
+ blocks,
180
+ visibleIndex,
181
+ setVisibleIndex,
182
+ isSimDone,
183
+ currentBlock,
184
+ showThink,
185
+ onSimulateComplete,
186
+ onStepChange,
187
+ thinkingIndicator: thinkNode,
261
188
  }
262
189
 
263
190
  return (
264
- <div className={cn('flex flex-col gap-3 font-body', className)} {...props}>
265
- <div className="flex flex-col gap-1">{contentElements}</div>
266
- {effectiveShowThink && !simulate && thinkNode}
267
- </div>
191
+ <ResponseContext.Provider value={ctxValue}>
192
+ {children}
193
+ </ResponseContext.Provider>
268
194
  )
269
195
  }
270
196
 
271
- Response.displayName = 'Response'
197
+ ResponseRootProvider.displayName = 'ResponseRoot'
198
+
199
+ // 导出 Demo 数据供测试使用
200
+ export { DEMO_ROUNDS, DEMO_FINAL_MD }
@@ -0,0 +1,87 @@
1
+ import type { HTMLAttributes } from 'react'
2
+ import { ResponseRootProvider, useResponseContext, DEMO_ROUNDS, DEMO_FINAL_MD } from './context'
3
+ import {
4
+ ResponseThinking,
5
+ ResponseSteps,
6
+ ResponseStepComponent,
7
+ ResponseContent,
8
+ ResponseImageGenerating,
9
+ ResponseDefaultLayout,
10
+ } from './compound'
11
+ import type {
12
+ ResponseProps,
13
+ ResponseCompound,
14
+ ResponsePhase,
15
+ ResponseStep,
16
+ ResponseStepToolType,
17
+ ResponseRound,
18
+ ResponseBlock,
19
+ BlockType,
20
+ } from './types'
21
+
22
+ // ===== Response 便捷组件 =====
23
+
24
+ function ResponseBase({
25
+ simulate,
26
+ phase,
27
+ rounds,
28
+ finalMarkdown,
29
+ onSimulateComplete,
30
+ onStepChange,
31
+ thinkingIndicator,
32
+ className,
33
+ ...props
34
+ }: ResponseProps & HTMLAttributes<HTMLDivElement>) {
35
+ return (
36
+ <ResponseRootProvider
37
+ simulate={simulate}
38
+ phase={phase}
39
+ rounds={rounds}
40
+ finalMarkdown={finalMarkdown}
41
+ onSimulateComplete={onSimulateComplete}
42
+ onStepChange={onStepChange}
43
+ thinkingIndicator={thinkingIndicator}
44
+ >
45
+ <div {...props}>
46
+ <ResponseDefaultLayout className={className} />
47
+ </div>
48
+ </ResponseRootProvider>
49
+ )
50
+ }
51
+
52
+ ResponseBase.displayName = 'Response'
53
+
54
+ // ===== 挂载复合组件 =====
55
+
56
+ type ResponseBaseType = React.FC<ResponseProps & HTMLAttributes<HTMLDivElement>> &
57
+ Partial<ResponseCompound>
58
+
59
+ const ResponseWithSlots = ResponseBase as ResponseBaseType
60
+
61
+ ResponseWithSlots.Root = ResponseRootProvider
62
+ ResponseWithSlots.Thinking = ResponseThinking
63
+ ResponseWithSlots.Steps = ResponseSteps
64
+ ResponseWithSlots.Step = ResponseStepComponent
65
+ ResponseWithSlots.Content = ResponseContent
66
+ ResponseWithSlots.ImageGenerating = ResponseImageGenerating
67
+ ResponseWithSlots.DefaultLayout = ResponseDefaultLayout
68
+
69
+ export const Response = ResponseWithSlots as React.FC<
70
+ ResponseProps & HTMLAttributes<HTMLDivElement>
71
+ > &
72
+ ResponseCompound
73
+
74
+ // ===== 导出类型和工具 =====
75
+
76
+ export type {
77
+ ResponseProps,
78
+ ResponsePhase,
79
+ ResponseStep,
80
+ ResponseStepToolType,
81
+ ResponseRound,
82
+ ResponseBlock,
83
+ BlockType,
84
+ ResponseCompound,
85
+ }
86
+
87
+ export { useResponseContext, DEMO_ROUNDS, DEMO_FINAL_MD }
@@ -0,0 +1,123 @@
1
+ import type { HTMLAttributes, ReactNode } from 'react'
2
+ import type { ReasoningStepDetail } from '../reasoning-step'
3
+
4
+ // ===== 基础类型 =====
5
+
6
+ export type ResponsePhase = 'thinking' | 'streaming' | 'done' | 'imageGenerating'
7
+ export type ResponseStepToolType = 'read' | 'edit' | 'search'
8
+
9
+ export interface ResponseStep {
10
+ text: string
11
+ description?: string
12
+ details?: ReasoningStepDetail[]
13
+ toolType?: ResponseStepToolType
14
+ }
15
+
16
+ export interface ResponseRound {
17
+ step: ResponseStep
18
+ simpleMd: string
19
+ }
20
+
21
+ // ===== Block 类型(内部) =====
22
+
23
+ export type BlockType = 'think' | 'composer' | 'md'
24
+
25
+ export interface ResponseBlock {
26
+ type: BlockType
27
+ step?: ResponseStep
28
+ md?: string
29
+ }
30
+
31
+ // ===== 组件 Props =====
32
+
33
+ export interface ResponseRootProps {
34
+ /** 是否启用流式模拟 */
35
+ simulate?: boolean
36
+ /** 当前阶段 */
37
+ phase?: ResponsePhase
38
+ /** 响应轮次数据 */
39
+ rounds?: ResponseRound[]
40
+ /** 最终 Markdown 内容 */
41
+ finalMarkdown?: string
42
+ /** 模拟完成回调 */
43
+ onSimulateComplete?: () => void
44
+ /** 步骤变化回调 */
45
+ onStepChange?: (text: string, toolType?: ResponseStepToolType) => void
46
+ /** 自定义 ThinkingIndicator(主库注入 Lottie) */
47
+ thinkingIndicator?: ReactNode
48
+ /** 自定义 className */
49
+ className?: string
50
+ /** 子组件 */
51
+ children?: ReactNode
52
+ }
53
+
54
+ export interface ResponseProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'>, ResponseRootProps {}
55
+
56
+ export interface ResponseThinkingProps {
57
+ className?: string
58
+ }
59
+
60
+ export interface ResponseStepsProps {
61
+ className?: string
62
+ children?: ReactNode
63
+ }
64
+
65
+ export interface ResponseStepProps {
66
+ step: ResponseStep
67
+ status?: 'completed' | 'in-progress'
68
+ }
69
+
70
+ export interface ResponseContentProps {
71
+ /** Markdown 内容 */
72
+ markdown?: string
73
+ /** 是否流式渲染 */
74
+ streaming?: boolean
75
+ /** 流式渲染完成回调 */
76
+ onStreamComplete?: () => void
77
+ className?: string
78
+ }
79
+
80
+ export interface ResponseImageGeneratingProps {
81
+ width?: string
82
+ height?: string
83
+ className?: string
84
+ }
85
+
86
+ export interface ResponseDefaultLayoutProps {
87
+ className?: string
88
+ }
89
+
90
+ // ===== Context 类型 =====
91
+
92
+ export interface ResponseContextValue {
93
+ // 阶段控制
94
+ phase: ResponsePhase
95
+ simulate: boolean
96
+ // 数据
97
+ rounds: ResponseRound[]
98
+ finalMarkdown: string
99
+ blocks: ResponseBlock[]
100
+ // 状态
101
+ visibleIndex: number
102
+ setVisibleIndex: React.Dispatch<React.SetStateAction<number>>
103
+ isSimDone: boolean
104
+ currentBlock: ResponseBlock | null
105
+ showThink: boolean
106
+ // 回调
107
+ onSimulateComplete?: () => void
108
+ onStepChange?: (text: string, toolType?: ResponseStepToolType) => void
109
+ // 注入
110
+ thinkingIndicator: ReactNode
111
+ }
112
+
113
+ // ===== 复合组件类型 =====
114
+
115
+ export interface ResponseCompound {
116
+ Root: React.FC<ResponseRootProps>
117
+ Thinking: React.FC<ResponseThinkingProps>
118
+ Steps: React.FC<ResponseStepsProps>
119
+ Step: React.FC<ResponseStepProps>
120
+ Content: React.FC<ResponseContentProps>
121
+ ImageGenerating: React.FC<ResponseImageGeneratingProps>
122
+ DefaultLayout: React.FC<ResponseDefaultLayoutProps>
123
+ }