@springbrand/message-panel 0.1.2 → 0.1.3-alpha.0

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 (99) hide show
  1. package/demo/camel-chat-showcase.tsx +1265 -0
  2. package/demo/fixtures.ts +292 -0
  3. package/demo/message-panel-chat-showcase.tsx +250 -0
  4. package/demo/message-panel-gallery.tsx +389 -0
  5. package/package.json +6 -16
  6. package/src/adapters/ai-sdk.ts +32 -0
  7. package/src/camel/camel-chat-messages.tsx +1423 -0
  8. package/src/camel/camel-model-select.tsx +88 -0
  9. package/src/camel/camel-plan-bar.tsx +115 -0
  10. package/src/camel/camel-prompt-input.tsx +547 -0
  11. package/src/camel/camel-tool-presentation.tsx +1270 -0
  12. package/src/camel/camel-turn.ts +316 -0
  13. package/src/camel/turn-summary-bar.tsx +81 -0
  14. package/src/chat-summary-panel.tsx +229 -0
  15. package/src/composer/chat-composer.tsx +216 -0
  16. package/src/composer/composer-attachments.tsx +138 -0
  17. package/src/composer/composer-trigger-popover.tsx +236 -0
  18. package/src/composer/composer.tsx +425 -0
  19. package/src/composer/drop-controller.ts +80 -0
  20. package/src/composer/types.ts +79 -0
  21. package/src/contracts.ts +138 -0
  22. package/src/conversation.tsx +76 -0
  23. package/src/internal/cn.ts +3 -0
  24. package/src/message-panel.tsx +368 -0
  25. package/src/message.tsx +254 -0
  26. package/src/parts/index.tsx +1149 -0
  27. package/src/parts/kind.ts +67 -0
  28. package/src/parts/plan.ts +63 -0
  29. package/src/parts/primitives.tsx +439 -0
  30. package/src/registry.ts +44 -0
  31. package/src/status.tsx +132 -0
  32. package/dist/demo/camel-chat-showcase.d.ts +0 -1
  33. package/dist/demo/camel-chat-showcase.js +0 -1007
  34. package/dist/demo/fixtures.d.ts +0 -32
  35. package/dist/demo/fixtures.js +0 -250
  36. package/dist/demo/index.js +0 -4
  37. package/dist/demo/message-panel-chat-showcase.d.ts +0 -1
  38. package/dist/demo/message-panel-chat-showcase.js +0 -114
  39. package/dist/demo/message-panel-gallery.d.ts +0 -1
  40. package/dist/demo/message-panel-gallery.js +0 -130
  41. package/dist/src/adapters/ai-sdk.d.ts +0 -4
  42. package/dist/src/adapters/ai-sdk.js +0 -25
  43. package/dist/src/camel/camel-chat-messages.d.ts +0 -44
  44. package/dist/src/camel/camel-chat-messages.js +0 -421
  45. package/dist/src/camel/camel-model-select.d.ts +0 -13
  46. package/dist/src/camel/camel-model-select.js +0 -7
  47. package/dist/src/camel/camel-plan-bar.d.ts +0 -5
  48. package/dist/src/camel/camel-plan-bar.js +0 -24
  49. package/dist/src/camel/camel-prompt-input.d.ts +0 -32
  50. package/dist/src/camel/camel-prompt-input.js +0 -182
  51. package/dist/src/camel/camel-tool-presentation.d.ts +0 -21
  52. package/dist/src/camel/camel-tool-presentation.js +0 -784
  53. package/dist/src/camel/camel-turn.d.ts +0 -57
  54. package/dist/src/camel/camel-turn.js +0 -203
  55. package/dist/src/camel/index.js +0 -7
  56. package/dist/src/camel/turn-summary-bar.d.ts +0 -8
  57. package/dist/src/camel/turn-summary-bar.js +0 -12
  58. package/dist/src/chat-summary-panel.d.ts +0 -22
  59. package/dist/src/chat-summary-panel.js +0 -26
  60. package/dist/src/composer/chat-composer.d.ts +0 -4
  61. package/dist/src/composer/chat-composer.js +0 -90
  62. package/dist/src/composer/composer-attachments.d.ts +0 -13
  63. package/dist/src/composer/composer-attachments.js +0 -47
  64. package/dist/src/composer/composer-trigger-popover.d.ts +0 -31
  65. package/dist/src/composer/composer-trigger-popover.js +0 -29
  66. package/dist/src/composer/composer.d.ts +0 -15
  67. package/dist/src/composer/composer.js +0 -180
  68. package/dist/src/composer/drop-controller.d.ts +0 -8
  69. package/dist/src/composer/drop-controller.js +0 -69
  70. package/dist/src/composer/index.js +0 -6
  71. package/dist/src/composer/types.d.ts +0 -62
  72. package/dist/src/composer/types.js +0 -1
  73. package/dist/src/contracts.d.ts +0 -94
  74. package/dist/src/contracts.js +0 -1
  75. package/dist/src/conversation.d.ts +0 -13
  76. package/dist/src/conversation.js +0 -18
  77. package/dist/src/index.js +0 -12
  78. package/dist/src/internal/cn.d.ts +0 -1
  79. package/dist/src/internal/cn.js +0 -3
  80. package/dist/src/message-panel.d.ts +0 -4
  81. package/dist/src/message-panel.js +0 -144
  82. package/dist/src/message.d.ts +0 -36
  83. package/dist/src/message.js +0 -83
  84. package/dist/src/parts/index.d.ts +0 -187
  85. package/dist/src/parts/index.js +0 -332
  86. package/dist/src/parts/kind.d.ts +0 -3
  87. package/dist/src/parts/kind.js +0 -69
  88. package/dist/src/parts/plan.d.ts +0 -10
  89. package/dist/src/parts/plan.js +0 -40
  90. package/dist/src/parts/primitives.d.ts +0 -70
  91. package/dist/src/parts/primitives.js +0 -153
  92. package/dist/src/registry.d.ts +0 -4
  93. package/dist/src/registry.js +0 -31
  94. package/dist/src/status.d.ts +0 -26
  95. package/dist/src/status.js +0 -74
  96. /package/{dist/demo/index.d.ts → demo/index.ts} +0 -0
  97. /package/{dist/src/camel/index.d.ts → src/camel/index.ts} +0 -0
  98. /package/{dist/src/composer/index.d.ts → src/composer/index.ts} +0 -0
  99. /package/{dist/src/index.d.ts → src/index.ts} +0 -0
@@ -0,0 +1,254 @@
1
+ import { cjk } from "@streamdown/cjk";
2
+ import { code } from "@streamdown/code";
3
+ import { math } from "@streamdown/math";
4
+ import { mermaid } from "@streamdown/mermaid";
5
+ import {
6
+ CheckIcon,
7
+ CopyIcon,
8
+ GitForkIcon,
9
+ Loader2Icon,
10
+ RefreshCcwIcon,
11
+ } from "lucide-react";
12
+ import {
13
+ forwardRef,
14
+ memo,
15
+ useCallback,
16
+ useEffect,
17
+ useRef,
18
+ useState,
19
+ type ButtonHTMLAttributes,
20
+ type HTMLAttributes,
21
+ } from "react";
22
+ import {
23
+ defaultRehypePlugins,
24
+ Streamdown,
25
+ type StreamdownProps,
26
+ } from "streamdown";
27
+ import { cn } from "./internal/cn";
28
+ import type { MessageRole } from "./contracts";
29
+
30
+ export type MessageProps = HTMLAttributes<HTMLDivElement> & {
31
+ from: MessageRole;
32
+ };
33
+
34
+ export const Message = forwardRef<HTMLDivElement, MessageProps>(
35
+ ({ className, from, ...props }, ref) => (
36
+ <div
37
+ ref={ref}
38
+ className={cn(
39
+ "sb-message",
40
+ from === "user" ? "sb-message--user" : "sb-message--assistant",
41
+ className,
42
+ )}
43
+ data-role={from}
44
+ {...props}
45
+ />
46
+ ),
47
+ );
48
+ Message.displayName = "Message";
49
+
50
+ export const MessageContent = forwardRef<
51
+ HTMLDivElement,
52
+ HTMLAttributes<HTMLDivElement>
53
+ >(({ className, ...props }, ref) => (
54
+ <div ref={ref} className={cn("sb-message__content", className)} {...props} />
55
+ ));
56
+ MessageContent.displayName = "MessageContent";
57
+
58
+ export const MessageActions = forwardRef<
59
+ HTMLDivElement,
60
+ HTMLAttributes<HTMLDivElement>
61
+ >(({ className, ...props }, ref) => (
62
+ <div ref={ref} className={cn("sb-message__actions", className)} {...props} />
63
+ ));
64
+ MessageActions.displayName = "MessageActions";
65
+
66
+ export type MessageActionProps = ButtonHTMLAttributes<HTMLButtonElement> & {
67
+ tooltip?: string;
68
+ label?: string;
69
+ };
70
+
71
+ export const MessageAction = forwardRef<HTMLButtonElement, MessageActionProps>(
72
+ ({ className, tooltip, label, children, ...props }, ref) => (
73
+ <button
74
+ ref={ref}
75
+ type="button"
76
+ className={cn("sb-message__action", className)}
77
+ title={tooltip}
78
+ aria-label={label ?? tooltip}
79
+ {...props}
80
+ >
81
+ {children}
82
+ </button>
83
+ ),
84
+ );
85
+ MessageAction.displayName = "MessageAction";
86
+
87
+ const streamdownPlugins = { cjk, code, math, mermaid };
88
+ const streamAnimation = {
89
+ animation: "fadeIn",
90
+ sep: "word",
91
+ duration: 260,
92
+ stagger: 14,
93
+ } as const;
94
+
95
+ export type MessageResponseProps = StreamdownProps & {
96
+ streaming?: boolean;
97
+ resolveUrl?: MessageUrlResolver;
98
+ };
99
+
100
+ export type MessageUrlResolver = (url: string) => string | null | undefined;
101
+
102
+ type UrlNode = {
103
+ type?: string;
104
+ properties?: Record<string, unknown>;
105
+ children?: UrlNode[];
106
+ };
107
+
108
+ const streamdownRehypePlugins = Object.values(defaultRehypePlugins) as NonNullable<
109
+ StreamdownProps["rehypePlugins"]
110
+ >;
111
+
112
+ function resolveUrlsPlugin(resolveUrl: MessageUrlResolver) {
113
+ return () => (tree: UrlNode) => {
114
+ const visit = (node: UrlNode): void => {
115
+ if (node.type === "element" && node.properties) {
116
+ const url = node.properties.href;
117
+ if (typeof url === "string") {
118
+ const resolved = resolveUrl(url);
119
+ if (resolved === null) delete node.properties.href;
120
+ else if (resolved !== undefined) node.properties.href = resolved;
121
+ }
122
+ }
123
+ node.children?.forEach(visit);
124
+ };
125
+ visit(tree);
126
+ };
127
+ }
128
+
129
+ export const MessageResponse = memo(
130
+ ({
131
+ className,
132
+ streaming = false,
133
+ resolveUrl,
134
+ rehypePlugins,
135
+ ...props
136
+ }: MessageResponseProps) => (
137
+ <Streamdown
138
+ className={cn("sb-message-response", className)}
139
+ plugins={streamdownPlugins}
140
+ rehypePlugins={
141
+ resolveUrl
142
+ ? [resolveUrlsPlugin(resolveUrl), ...(rehypePlugins ?? streamdownRehypePlugins)]
143
+ : rehypePlugins
144
+ }
145
+ {...(streaming
146
+ ? {
147
+ mode: "streaming" as const,
148
+ isAnimating: true,
149
+ animated: streamAnimation,
150
+ }
151
+ : { mode: "static" as const })}
152
+ {...props}
153
+ />
154
+ ),
155
+ (previous, next) =>
156
+ previous.children === next.children &&
157
+ previous.streaming === next.streaming &&
158
+ previous.resolveUrl === next.resolveUrl,
159
+ );
160
+ MessageResponse.displayName = "MessageResponse";
161
+
162
+ export function AssistantMessageActions({
163
+ text,
164
+ messageId,
165
+ canRetry,
166
+ onCopy,
167
+ onRetry,
168
+ onFork,
169
+ }: {
170
+ text: string;
171
+ messageId: string;
172
+ canRetry: boolean;
173
+ onCopy?: (text: string) => void | Promise<void>;
174
+ onRetry?: (messageId: string) => void | Promise<void>;
175
+ onFork?: (messageId: string) => void | Promise<void>;
176
+ }) {
177
+ const [copied, setCopied] = useState(false);
178
+ const [forking, setForking] = useState(false);
179
+ const timer = useRef<number | null>(null);
180
+
181
+ useEffect(
182
+ () => () => {
183
+ if (timer.current !== null) window.clearTimeout(timer.current);
184
+ },
185
+ [],
186
+ );
187
+
188
+ const copy = useCallback(async () => {
189
+ if (onCopy) await onCopy(text);
190
+ else if (typeof navigator !== "undefined") await navigator.clipboard.writeText(text);
191
+ setCopied(true);
192
+ if (timer.current !== null) window.clearTimeout(timer.current);
193
+ timer.current = window.setTimeout(() => setCopied(false), 1500);
194
+ }, [onCopy, text]);
195
+
196
+ if (!text && (!canRetry || !onRetry) && !onFork) return null;
197
+ return (
198
+ <MessageActions>
199
+ {text && (
200
+ <MessageAction
201
+ tooltip={copied ? "已复制" : "复制"}
202
+ label="复制"
203
+ onClick={() => void copy()}
204
+ >
205
+ {copied ? <CheckIcon size={15} /> : <CopyIcon size={15} />}
206
+ </MessageAction>
207
+ )}
208
+ {canRetry && onRetry && (
209
+ <MessageAction
210
+ tooltip="重试"
211
+ label="重试"
212
+ onClick={() => void onRetry(messageId)}
213
+ >
214
+ <RefreshCcwIcon size={15} />
215
+ </MessageAction>
216
+ )}
217
+ {onFork && (
218
+ <MessageAction
219
+ tooltip={forking ? "正在创建新会话…" : "从这里创建新会话"}
220
+ label={forking ? "正在创建新会话" : "从这里创建新会话"}
221
+ disabled={forking}
222
+ onClick={() => {
223
+ setForking(true);
224
+ void Promise.resolve(onFork(messageId)).finally(() =>
225
+ setForking(false),
226
+ );
227
+ }}
228
+ >
229
+ {forking ? (
230
+ <Loader2Icon size={15} className="sb-spin" />
231
+ ) : (
232
+ <GitForkIcon size={15} />
233
+ )}
234
+ </MessageAction>
235
+ )}
236
+ </MessageActions>
237
+ );
238
+ }
239
+
240
+ export function BrandMark({ className }: { className?: string }) {
241
+ return (
242
+ <span className={cn("sb-brand-mark", className)} aria-hidden="true">
243
+ <svg width="17" height="17" viewBox="0 0 28 28" fill="none">
244
+ <path
245
+ d="M14 14 C 11.5 10.5, 6.5 10.5, 6.5 14 C 6.5 17.5, 11.5 17.5, 14 14 C 16.5 10.5, 21.5 10.5, 21.5 14 C 21.5 17.5, 16.5 17.5, 14 14 Z"
246
+ stroke="currentColor"
247
+ strokeWidth="1.8"
248
+ strokeLinecap="round"
249
+ className="sb-brand-mark__loop"
250
+ />
251
+ </svg>
252
+ </span>
253
+ );
254
+ }