@amaster.ai/components-templates 1.3.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 (40) hide show
  1. package/README.md +193 -0
  2. package/bin/amaster.js +2 -0
  3. package/components/ai-assistant/example.md +34 -0
  4. package/components/ai-assistant/package.json +34 -0
  5. package/components/ai-assistant/template/ai-assistant.tsx +88 -0
  6. package/components/ai-assistant/template/components/Markdown.tsx +70 -0
  7. package/components/ai-assistant/template/components/chat-assistant-message.tsx +190 -0
  8. package/components/ai-assistant/template/components/chat-banner.tsx +17 -0
  9. package/components/ai-assistant/template/components/chat-display-mode-switcher.tsx +70 -0
  10. package/components/ai-assistant/template/components/chat-floating-button.tsx +56 -0
  11. package/components/ai-assistant/template/components/chat-floating-card.tsx +43 -0
  12. package/components/ai-assistant/template/components/chat-header.tsx +66 -0
  13. package/components/ai-assistant/template/components/chat-input.tsx +143 -0
  14. package/components/ai-assistant/template/components/chat-messages.tsx +81 -0
  15. package/components/ai-assistant/template/components/chat-recommends.tsx +36 -0
  16. package/components/ai-assistant/template/components/chat-speech-button.tsx +43 -0
  17. package/components/ai-assistant/template/components/chat-user-message.tsx +26 -0
  18. package/components/ai-assistant/template/components/ui-renderer-lazy.tsx +307 -0
  19. package/components/ai-assistant/template/components/ui-renderer.tsx +34 -0
  20. package/components/ai-assistant/template/components/voice-input.tsx +43 -0
  21. package/components/ai-assistant/template/hooks/useAssistantStore.tsx +36 -0
  22. package/components/ai-assistant/template/hooks/useAutoScroll.ts +90 -0
  23. package/components/ai-assistant/template/hooks/useConversationProcessor.ts +649 -0
  24. package/components/ai-assistant/template/hooks/useDisplayMode.tsx +74 -0
  25. package/components/ai-assistant/template/hooks/useDraggable.ts +125 -0
  26. package/components/ai-assistant/template/hooks/usePosition.ts +206 -0
  27. package/components/ai-assistant/template/hooks/useSpeak.ts +50 -0
  28. package/components/ai-assistant/template/hooks/useVoiceInput.ts +172 -0
  29. package/components/ai-assistant/template/i18n.ts +114 -0
  30. package/components/ai-assistant/template/index.ts +6 -0
  31. package/components/ai-assistant/template/inline-ai-assistant.tsx +78 -0
  32. package/components/ai-assistant/template/mock/mock-data.ts +643 -0
  33. package/components/ai-assistant/template/types.ts +72 -0
  34. package/index.js +13 -0
  35. package/package.json +67 -0
  36. package/packages/cli/dist/index.d.ts +3 -0
  37. package/packages/cli/dist/index.d.ts.map +1 -0
  38. package/packages/cli/dist/index.js +335 -0
  39. package/packages/cli/dist/index.js.map +1 -0
  40. package/packages/cli/package.json +35 -0
package/README.md ADDED
@@ -0,0 +1,193 @@
1
+ # @amaster.ai/components-templates
2
+
3
+ Amaster 组件模板集合,提供 CLI 工具快速初始化和更新组件。
4
+
5
+ ## 项目目录
6
+
7
+ ```
8
+ amaster-components-template/
9
+ ├── bin/amaster.js # CLI 入口
10
+ ├── components/ # 组件模板目录
11
+ │ └── ai-assistant/ # ai-assistant 组件
12
+ │ ├── package.json # 组件配置(依赖、目标目录等)
13
+ │ └── template/ # 模板文件
14
+ │ ├── ai-assistant.tsx
15
+ │ ├── index.ts
16
+ │ ├── types.ts
17
+ │ ├── components/ # 子组件
18
+ │ ├── hooks/ # Hooks
19
+ │ └── mock/ # Mock 数据
20
+ ├── packages/cli/ # CLI 工具
21
+ │ ├── src/index.ts # CLI 源码
22
+ │ ├── dist/ # 编译输出
23
+ │ └── package.json
24
+ ├── scripts/
25
+ │ ├── publish.js # 一键发布脚本
26
+ │ └── create-component.js # 创建新组件模板向导
27
+ ├── index.js # 主入口
28
+ ├── package.json # 包配置
29
+ └── README.md
30
+ ```
31
+
32
+ ## 安装
33
+
34
+ ```bash
35
+ npm install -g @amaster.ai/components-templates
36
+ ```
37
+
38
+ 或使用 npx(推荐):
39
+
40
+ ```bash
41
+ npx @amaster.ai/components-templates <command>
42
+ ```
43
+
44
+ ## 使用
45
+
46
+ ### 列出所有可用组件
47
+
48
+ ```bash
49
+ npx @amaster.ai/components-templates list
50
+ # 或
51
+ npx @amaster.ai/components-templates ls
52
+ ```
53
+
54
+ ### 初始化组件
55
+
56
+ ```bash
57
+ # 交互式选择组件
58
+ npx @amaster.ai/components-templates init
59
+
60
+ # 安装整个组件
61
+ npx @amaster.ai/components-templates init ai-assistant
62
+
63
+ # 指定目标目录
64
+ npx @amaster.ai/components-templates init ai-assistant -d ./src/components/ai-assistant
65
+
66
+ # 强制覆盖(不提示确认)
67
+ npx @amaster.ai/components-templates init ai-assistant --force
68
+
69
+ # 跳过依赖安装
70
+ npx @amaster.ai/components-templates init ai-assistant --skip-install
71
+
72
+ # 只安装组件中的特定文件夹
73
+ npx @amaster.ai/components-templates init ai-assistant/hooks -d ./src/components/ai-assistant/hooks
74
+
75
+ # 只安装组件中的单个文件
76
+ npx @amaster.ai/components-templates init ai-assistant/hooks/useSpeak.ts -d ./src/components/ai-assistant/hooks/useSpeak.ts
77
+ ```
78
+
79
+ ### 查看组件信息
80
+
81
+ ```bash
82
+ npx @amaster.ai/components-templates info ai-assistant
83
+ ```
84
+
85
+ ## 特性
86
+
87
+ ### 1. 依赖自动合并
88
+
89
+ 初始化组件时,CLI 会自动:
90
+
91
+ - 读取组件模板的 `package.json`
92
+ - 合并依赖到目标项目的 `package.json`
93
+ - 自动检测包管理器(pnpm/yarn/npm)并安装依赖
94
+
95
+ ### 2. 灵活的路径支持
96
+
97
+ 支持多种初始化方式:
98
+
99
+ - **整个组件**: `init ai-assistant`
100
+ - **子文件夹**: `init ai-assistant/hooks`
101
+ - **单个文件**: `init ai-assistant/hooks/useSpeak.ts`
102
+
103
+ ### 3. 智能包管理器检测
104
+
105
+ 自动检测目标项目使用的包管理器:
106
+
107
+ - `pnpm-lock.yaml` → pnpm
108
+ - `yarn.lock` → yarn
109
+ - `package-lock.json` → npm
110
+
111
+ ## 开发
112
+
113
+ ### 本地调试
114
+
115
+ ```bash
116
+ # 列出所有组件
117
+ node bin/amaster.js list
118
+
119
+ # 安装组件到目标项目
120
+ node bin/amaster.js init ai-assistant -d ../test/src/components/ai-assistant
121
+
122
+ # 跳过依赖安装
123
+ node bin/amaster.js init ai-assistant -d ../test/src/components/ai-assistant --skip-install
124
+
125
+ # 强制执行
126
+ node bin/amaster.js init ai-assistant -d ../test/src/components/ai-assistant --force
127
+
128
+ # 只复制 hooks 文件夹
129
+ node bin/amaster.js init ai-assistant/hooks -d ../test/src/components/ai-assistant/hooks --force
130
+
131
+ # 只复制单个文件
132
+ node bin/amaster.js init ai-assistant/hooks/useSpeak.ts -d ../test/src/components/ai-assistant/hooks/useSpeak.ts
133
+ ```
134
+
135
+ ### 脚本添加新组件模板
136
+
137
+ ```bash
138
+ # 按提示输入组件名、描述、目标目录
139
+ npm run create:component
140
+ ```
141
+
142
+ ### 手动添加新组件模板
143
+
144
+ 1. 在 `components/` 目录下创建新组件目录
145
+ 2. 添加 `package.json` 配置文件
146
+ 3. 在 `template/` 目录下放置模板文件
147
+
148
+ ```
149
+ components/
150
+ └── your-component/
151
+ ├── package.json # 组件配置
152
+ └── template/ # 模板文件
153
+ ├── index.ts
154
+ └── ...
155
+ ```
156
+
157
+ ### 组件 package.json 配置
158
+
159
+ ```json
160
+ {
161
+ "name": "your-component",
162
+ "version": "1.0.0",
163
+ "description": "组件描述",
164
+ "template": {
165
+ "name": "your-component",
166
+ "targetDir": "src/components/your-component",
167
+ "files": ["**/*"],
168
+ "ignore": ["node_modules", "*.log", ".DS_Store"]
169
+ },
170
+ "dependencies": {
171
+ "some-lib": "^1.0.0"
172
+ },
173
+ "peerDependencies": {
174
+ "react": ">=18.0.0"
175
+ },
176
+ "devDependencies": {
177
+ "@types/react": "^18.0.0"
178
+ }
179
+ }
180
+ ```
181
+
182
+ ## 发布
183
+
184
+ ```bash
185
+ # 一键发布(自动构建、更新版本、同步组件版本)
186
+ npm run publish:all # patch
187
+ npm run publish:minor # minor
188
+ npm run publish:major # major
189
+ ```
190
+
191
+ ## License
192
+
193
+ MIT
package/bin/amaster.js ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import("../packages/cli/dist/index.js");
@@ -0,0 +1,34 @@
1
+ # AIAssistant
2
+ 智能助手,支持对话方式解决用户需求和问题
3
+
4
+ ### 悬浮模式
5
+ ```tsx
6
+ import AIAssistant from "@/components/ai-assistant";
7
+ import { Link } from "react-router-dom";
8
+
9
+ export default function HomePage() {
10
+ return (
11
+ <div className="flex min-h-screen flex-col items-center justify-center">
12
+ <div className="text-center text-2xl font-bold text-foreground">
13
+ Home Page
14
+ </div>
15
+
16
+ <Link to="/ai" className="mt-6 text-primary underline">
17
+ Go to AI Page
18
+ </Link>
19
+
20
+ <AIAssistant />
21
+ </div>
22
+ );
23
+ }
24
+ ```
25
+
26
+ ### 内嵌模式
27
+
28
+ ```tsx
29
+ import { InlineAIAssistant } from "@/components/ai-assistant";
30
+
31
+ export default function AIPage() {
32
+ return <InlineAIAssistant className="min-h-screen" />;
33
+ }
34
+ ```
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "ai-assistant",
3
+ "version": "1.3.0",
4
+ "description": "AI Assistant component template",
5
+ "main": "template/index.ts",
6
+ "template": {
7
+ "name": "ai-assistant",
8
+ "targetDir": "src/components/ai-assistant",
9
+ "files": [
10
+ "**/*"
11
+ ],
12
+ "ignore": [
13
+ "node_modules",
14
+ "*.log",
15
+ ".DS_Store"
16
+ ],
17
+ "variables": {
18
+ "COMPONENT_NAME": {
19
+ "description": "Component name",
20
+ "default": "AIAssistant"
21
+ },
22
+ "TARGET_DIR": {
23
+ "description": "Target directory",
24
+ "default": "src/components/ai-assistant"
25
+ }
26
+ }
27
+ },
28
+ "dependencies": {
29
+ "@amaster.ai/client": "1.1.0-beta.52",
30
+ "react-markdown": "^10.0.0",
31
+ "remark-gfm": "^4.0.0",
32
+ "immer": "^10.0.0"
33
+ }
34
+ }
@@ -0,0 +1,88 @@
1
+ import type React from "react";
2
+ import { useEffect, useState } from "react";
3
+ import ChatFloatingCard from "./components/chat-floating-card";
4
+ import ChatFloatingButton from "./components/chat-floating-button";
5
+ import { useDraggable } from "./hooks/useDraggable";
6
+ import { usePosition } from "./hooks/usePosition";
7
+ import ChatHeader from "./components/chat-header";
8
+ import { getText } from "./i18n";
9
+ import ChatDisplayModeSwitcher from "./components/chat-display-mode-switcher";
10
+ import { useDisplayMode } from "./hooks/useDisplayMode";
11
+ import InlineAIAssistant from "./inline-ai-assistant";
12
+
13
+ const AIAssistant: React.FC = () => {
14
+
15
+ const [isOpen, setIsOpen] = useState(false);
16
+ const [displayMode, setDisplayMode] = useDisplayMode(isOpen);
17
+ const isFullscreen = displayMode === "fullscreen" && isOpen;
18
+ const positionHook = usePosition(isOpen, isFullscreen);
19
+
20
+ const draggableHook = useDraggable({
21
+ ...positionHook,
22
+ isFullscreen,
23
+ });
24
+ const { isDragging, handleDragStart, handleTouchStart } = draggableHook;
25
+
26
+ useEffect(() => {
27
+ const handleOpenAIAssistant = () => {
28
+ setIsOpen(true);
29
+ };
30
+
31
+ window.addEventListener("openAIAssistant", handleOpenAIAssistant);
32
+ return () => {
33
+ window.removeEventListener("openAIAssistant", handleOpenAIAssistant);
34
+ };
35
+ }, []);
36
+
37
+ useEffect(() => {
38
+ if (displayMode === "side-left") {
39
+ positionHook.setPositionTo("left", "bottom");
40
+ } else if (displayMode === "side-right") {
41
+ positionHook.setPositionTo("right", "bottom");
42
+ }
43
+ }, [displayMode]);
44
+
45
+ const handleClose = () => {
46
+ setIsOpen(false);
47
+ };
48
+
49
+ return (
50
+ <>
51
+ {!isOpen && (
52
+ <ChatFloatingButton
53
+ onClick={() => setIsOpen(true)}
54
+ onMouseDown={handleDragStart}
55
+ onTouchStart={handleTouchStart}
56
+ isDragging={isDragging}
57
+ style={positionHook.getPositionStyles()}
58
+ />
59
+ )}
60
+
61
+ {isOpen && (
62
+ <ChatFloatingCard
63
+ displayMode={displayMode}
64
+ isDragging={isDragging}
65
+ style={positionHook.getPositionStyles()}
66
+ >
67
+ <ChatHeader
68
+ disabledDrag={displayMode !== "floating"}
69
+ isDragging={isDragging}
70
+ onMouseDown={handleDragStart}
71
+ onTouchStart={handleTouchStart}
72
+ onClose={handleClose}
73
+ >
74
+ <ChatDisplayModeSwitcher
75
+ displayMode={displayMode}
76
+ onChange={setDisplayMode}
77
+ />
78
+ </ChatHeader>
79
+
80
+ <InlineAIAssistant showBanner={false} className="flex-1" greeting={getText().greeting} />
81
+ </ChatFloatingCard>
82
+ )}
83
+ </>
84
+ );
85
+ };
86
+
87
+ export { AIAssistant };
88
+ export default AIAssistant;
@@ -0,0 +1,70 @@
1
+ import type React from "react";
2
+ import ReactMarkdown from "react-markdown";
3
+ import remarkGfm from "remark-gfm";
4
+ import {cn} from "@/lib/utils";
5
+
6
+ interface MarkdownProps {
7
+ content: string;
8
+ className?: string;
9
+ }
10
+
11
+ const Markdown: React.FC<MarkdownProps> = ({ content, className }) => {
12
+ return (
13
+ <div
14
+ className={cn(
15
+ "text-foreground text-left max-w-none whitespace-normal",
16
+ // 标题样式
17
+ "[&_h1] [&_h1]:my-4 [&_h1]:text-xl [&_h1]:font-medium [&_h1]:tracking-tight",
18
+ "[&_h2] [&_h2]:my-3 [&_h2]:text-lg [&_h2]:font-medium [&_h2]:tracking-tight",
19
+ "[&_h3] [&_h3]:text-md [&_h3]:my-2 [&_h3]:font-medium [&_h3]:tracking-tight",
20
+ "[&_h4] [&_h4]:my-2 [&_h4]:text-base [&_h4]:font-medium [&_h4]:tracking-tight",
21
+ // 段落样式
22
+ "[&_p]:leading-6",
23
+ // 有序列表样式
24
+ "[&_ol]:my-2 [&_ol]:list-decimal [&_ol]:space-y-2 [&_ol]:pl-6",
25
+ // 无序列表样式
26
+ "[&_ul]:my-2 [&_ul]:list-disc [&_ul]:space-y-2 [&_ul]:pl-6",
27
+ // 列表项样式
28
+ "[&_li]:my-1 [&_li]:leading-6",
29
+ "[&_li::marker]:text-slate-500 dark:[&_li::marker]:text-slate-400",
30
+ // 嵌套列表样式
31
+ "[&_li_ul]:mt-2 [&_li_ul]:mb-2",
32
+ "[&_li_ol]:mt-2 [&_li_ol]:mb-2",
33
+ // 代码块样式
34
+ "[&_pre]:bg-slate-100 dark:[&_pre]:bg-slate-800",
35
+ "[&_pre]:my-2 [&_pre]:overflow-x-auto [&_pre]:rounded-lg [&_pre]:p-4",
36
+ "[&_pre_code]:font-mono [&_pre_code]:text-xs",
37
+ // 行内代码样式
38
+ "[&_:not(pre)_code]:bg-slate-100 dark:[&_:not(pre)_code]:bg-slate-800",
39
+ "[&_:not(pre)_code]:rounded [&_:not(pre)_code]:px-1.5 [&_:not(pre)_code]:py-0.5",
40
+ "[&_:not(pre)_code]:font-mono [&_:not(pre)_code]:text-xs",
41
+ "[&_:not(pre)_code]:before:content-none [&_:not(pre)_code]:after:content-none",
42
+ // 引用样式
43
+ "[&_blockquote]:border-l-4 [&_blockquote]:border-slate-300 dark:[&_blockquote]:border-slate-600",
44
+ "[&_blockquote]:my-2 [&_blockquote]:pl-4 [&_blockquote]:italic",
45
+ "[&_blockquote]:text-slate-600 dark:[&_blockquote]:text-slate-400",
46
+ // 链接样式
47
+ "[&_a]:text-blue-600 dark:[&_a]:text-blue-400",
48
+ "[&_a]:no-underline hover:[&_a]:underline",
49
+ // 强调样式
50
+ "[&_strong]:font-medium [&_strong]:text-slate-900 dark:[&_strong]:text-slate-100",
51
+ "[&_em]:italic",
52
+ // 表格样式
53
+ "[&_table]:my-2 [&_table]:w-full [&_table]:border-collapse",
54
+ "[&_th]:border [&_th]:border-slate-300 dark:[&_th]:border-slate-600",
55
+ "[&_th]:bg-slate-50 [&_th]:p-2 [&_th]:font-medium dark:[&_th]:bg-slate-800",
56
+ "[&_td]:border [&_td]:border-slate-300 [&_td]:p-2 dark:[&_td]:border-slate-600",
57
+ // 水平线样式
58
+ "[&_hr]:my-6 [&_hr]:border-slate-300 dark:[&_hr]:border-slate-600",
59
+ className,
60
+ )}
61
+ data-slot="markdown-content"
62
+ >
63
+ <ReactMarkdown remarkPlugins={[remarkGfm]}>{content}</ReactMarkdown>
64
+ </div>
65
+ );
66
+ };
67
+
68
+ Markdown.displayName = 'markdown-render'
69
+
70
+ export default Markdown;
@@ -0,0 +1,190 @@
1
+ import type {
2
+ MessagesItem,
3
+ TextMessage,
4
+ ThoughtMessage,
5
+ ToolMessage,
6
+ UIRenderMessage,
7
+ } from "../types";
8
+ import {
9
+ Check,
10
+ ChevronDown,
11
+ ChevronUp,
12
+ CircleX,
13
+ Info,
14
+ LoaderCircle,
15
+ MessageSquare,
16
+ } from "lucide-react";
17
+ import Markdown from "./Markdown";
18
+ import { cn } from "@/lib/utils";
19
+ import { useState } from "react";
20
+ import { getText } from "../i18n";
21
+ import TTSReader from "./chat-speech-button";
22
+ import { UIRenderer } from "./ui-renderer";
23
+
24
+ interface MessageCommonProps {
25
+ isNewest?: boolean;
26
+ isLoading?: boolean;
27
+ }
28
+
29
+ export const ChatLoading: React.FC<{ className?: string }> = ({
30
+ className,
31
+ }) => {
32
+ return (
33
+ <div className={`inline-flex items-center gap-1`}>
34
+ <div className="h-2 w-2 rounded-full bg-[#7C3AED] animate-pulse" />
35
+ <div
36
+ className="h-2 w-2 rounded-full bg-[#7C3AED] animate-pulse"
37
+ style={{ animationDelay: "150ms" }}
38
+ />
39
+ <div
40
+ className="h-2 w-2 rounded-full bg-[#7C3AED] animate-pulse"
41
+ style={{ animationDelay: "300ms" }}
42
+ />
43
+ </div>
44
+ );
45
+ };
46
+
47
+ const ChatTextMessage: React.FC<
48
+ { message: TextMessage } & MessageCommonProps
49
+ > = ({ message }) => {
50
+ if (!message.content) {
51
+ return <ChatLoading />;
52
+ }
53
+
54
+ return (
55
+ <>
56
+ <div className="prose prose-sm prose-p:my-1 prose-pre:my-2 whitespace-pre-wrap leading-relaxed break-words overflow-hidden min-w-0 max-w-full px-1">
57
+ <Markdown content={message.content} />
58
+ </div>
59
+ <TTSReader
60
+ text={message.content}
61
+ className="opacity-0 group-hover:opacity-100"
62
+ />
63
+ </>
64
+ );
65
+ };
66
+
67
+ const ChatThoughtMessage: React.FC<
68
+ { message: ThoughtMessage } & MessageCommonProps
69
+ > = ({ message, isNewest, isLoading }) => {
70
+ const [expanded, setExpanded] = useState(false);
71
+ const thinking = isLoading && isNewest;
72
+ return (
73
+ <div className="leading-relaxed whitespace-pre-wrap break-words text-xs overflow-hidden text-left">
74
+ <div
75
+ className="hover:border-border border bg-muted px-2 py-1 rounded-md inline-flex items-center gap-1 cursor-pointer"
76
+ onClick={() => setExpanded(!expanded)}
77
+ >
78
+ {thinking ? (
79
+ <LoaderCircle className="text-primary animate-spin size-3" />
80
+ ) : (
81
+ <div>
82
+ {!expanded ? (
83
+ <ChevronDown className="size-3.5" />
84
+ ) : (
85
+ <ChevronUp className="size-3.5" />
86
+ )}
87
+ </div>
88
+ )}
89
+ <span>{thinking ? getText().thinking : getText().thought}</span>
90
+ </div>
91
+ {(expanded || thinking) && (
92
+ <Markdown
93
+ content={message.thought || "..."}
94
+ className="text-xs font-normal opacity-55 pl-4 py-2 border-l-[2px] ml-4"
95
+ />
96
+ )}
97
+ </div>
98
+ );
99
+ };
100
+
101
+ const ChatToolMessage: React.FC<
102
+ { message: ToolMessage } & MessageCommonProps
103
+ > = ({ message, isLoading }) => {
104
+ const status = message.toolStatus || "executing";
105
+ return (
106
+ <div className="leading-relaxed whitespace-pre-wrap break-words flex items-center gap-1 border px-2 p-1 rounded-md bg-muted text-xs max-w-full overflow-hidden">
107
+ {status === "success" ? (
108
+ <Check className="size-3.5 text-success shrink-0" />
109
+ ) : status === "failed" || status === "error" ? (
110
+ <Info className="size-3.5 text-destructive shrink-0" />
111
+ ) : isLoading ? (
112
+ <LoaderCircle className="text-primary animate-spin size-3" />
113
+ ) : (
114
+ <CircleX className="size-3.5 text-muted-foreground shrink-0" />
115
+ )}
116
+ <span className="flex-1 truncate flex-shrink-0">
117
+ {message.toolName || getText().unknownTool}
118
+ </span>
119
+ {/* {message.toolDescription && <div className="flex-1 truncate max-w-1/2">{message.toolDescription}</div>} */}
120
+ </div>
121
+ );
122
+ };
123
+
124
+ const ChatErrorMessage: React.FC<{ message: TextMessage }> = ({ message }) => {
125
+ return (
126
+ <div className="leading-relaxed whitespace-pre-wrap text-left break-words gap-1 border px-2 p-1 rounded-md text-destructive text-xs max-w-full overflow-hidden">
127
+ {message.content || getText().errorMessage}
128
+ </div>
129
+ );
130
+ };
131
+
132
+ const ChatUIRenderMessage: React.FC<{ message: UIRenderMessage }> = ({
133
+ message,
134
+ }) => {
135
+ return (
136
+ <UIRenderer spec={message.spec} className="mb-2" />
137
+ );
138
+ };
139
+
140
+ const MessageContentRenderer: React.FC<
141
+ {
142
+ message: MessagesItem;
143
+ } & MessageCommonProps
144
+ > = ({ message, ...rest }) => {
145
+ switch (message.kind) {
146
+ case "text-content":
147
+ return <ChatTextMessage message={message as TextMessage} {...rest} />;
148
+ case "thought":
149
+ return (
150
+ <ChatThoughtMessage message={message as ThoughtMessage} {...rest} />
151
+ );
152
+ case "tool":
153
+ return <ChatToolMessage message={message as ToolMessage} {...rest} />;
154
+ case "error":
155
+ return <ChatErrorMessage message={message as TextMessage} {...rest} />;
156
+ case "ui-render":
157
+ return <ChatUIRenderMessage message={message as UIRenderMessage} />;
158
+ default:
159
+ return <div>{getText().unknownTool}</div>;
160
+ }
161
+ };
162
+
163
+ const ChatAssistantMessage: React.FC<
164
+ {
165
+ message: MessagesItem;
166
+ showAvatar?: boolean;
167
+ } & MessageCommonProps
168
+ > = ({ message, showAvatar, ...rest }) => {
169
+ return (
170
+ <div
171
+ className={cn(
172
+ "flex gap-3 animate-in fade-in-0 slide-in-from-bottom-2 duration-300 overflow-hidden w-full chat-assistant-message &+.chat-assistant-message:mt-4 group",
173
+ )}
174
+ >
175
+ <div
176
+ className={cn(
177
+ "flex-shrink-0 h-7 w-7 rounded-full bg-gradient-to-br from-[#6366F1] to-[#8B5CF6] flex items-center justify-center text-left",
178
+ !showAvatar && "invisible",
179
+ )}
180
+ >
181
+ {showAvatar && (
182
+ <MessageSquare className="h-3.5 w-3.5 text-white" strokeWidth={2} />
183
+ )}
184
+ </div>
185
+ <MessageContentRenderer message={message} {...rest} />
186
+ </div>
187
+ );
188
+ };
189
+
190
+ export default ChatAssistantMessage;
@@ -0,0 +1,17 @@
1
+ import { getText } from "../i18n";
2
+
3
+ const ChatBanner: React.FC<{
4
+ text?: string;
5
+ hidden?: boolean;
6
+ }> = ({ text, hidden }) => {
7
+ if (hidden) {
8
+ return null;
9
+ }
10
+ return (
11
+ <h1 className="text-center text-3xl text-[#000] font-medium p-4">
12
+ {text || getText().greeting}
13
+ </h1>
14
+ );
15
+ };
16
+
17
+ export default ChatBanner;