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
|
@@ -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,
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
87
|
+
// ===== 工具函数 =====
|
|
112
88
|
|
|
113
|
-
function buildBlocks(rounds: ResponseRound[], finalMd: string):
|
|
114
|
-
const blocks:
|
|
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
|
-
|
|
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
|
-
|
|
136
|
-
|
|
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
|
-
|
|
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
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
<
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
</div>
|
|
191
|
+
<ResponseContext.Provider value={ctxValue}>
|
|
192
|
+
{children}
|
|
193
|
+
</ResponseContext.Provider>
|
|
268
194
|
)
|
|
269
195
|
}
|
|
270
196
|
|
|
271
|
-
|
|
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
|
+
}
|