@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.
- package/README.md +193 -0
- package/bin/amaster.js +2 -0
- package/components/ai-assistant/example.md +34 -0
- package/components/ai-assistant/package.json +34 -0
- package/components/ai-assistant/template/ai-assistant.tsx +88 -0
- package/components/ai-assistant/template/components/Markdown.tsx +70 -0
- package/components/ai-assistant/template/components/chat-assistant-message.tsx +190 -0
- package/components/ai-assistant/template/components/chat-banner.tsx +17 -0
- package/components/ai-assistant/template/components/chat-display-mode-switcher.tsx +70 -0
- package/components/ai-assistant/template/components/chat-floating-button.tsx +56 -0
- package/components/ai-assistant/template/components/chat-floating-card.tsx +43 -0
- package/components/ai-assistant/template/components/chat-header.tsx +66 -0
- package/components/ai-assistant/template/components/chat-input.tsx +143 -0
- package/components/ai-assistant/template/components/chat-messages.tsx +81 -0
- package/components/ai-assistant/template/components/chat-recommends.tsx +36 -0
- package/components/ai-assistant/template/components/chat-speech-button.tsx +43 -0
- package/components/ai-assistant/template/components/chat-user-message.tsx +26 -0
- package/components/ai-assistant/template/components/ui-renderer-lazy.tsx +307 -0
- package/components/ai-assistant/template/components/ui-renderer.tsx +34 -0
- package/components/ai-assistant/template/components/voice-input.tsx +43 -0
- package/components/ai-assistant/template/hooks/useAssistantStore.tsx +36 -0
- package/components/ai-assistant/template/hooks/useAutoScroll.ts +90 -0
- package/components/ai-assistant/template/hooks/useConversationProcessor.ts +649 -0
- package/components/ai-assistant/template/hooks/useDisplayMode.tsx +74 -0
- package/components/ai-assistant/template/hooks/useDraggable.ts +125 -0
- package/components/ai-assistant/template/hooks/usePosition.ts +206 -0
- package/components/ai-assistant/template/hooks/useSpeak.ts +50 -0
- package/components/ai-assistant/template/hooks/useVoiceInput.ts +172 -0
- package/components/ai-assistant/template/i18n.ts +114 -0
- package/components/ai-assistant/template/index.ts +6 -0
- package/components/ai-assistant/template/inline-ai-assistant.tsx +78 -0
- package/components/ai-assistant/template/mock/mock-data.ts +643 -0
- package/components/ai-assistant/template/types.ts +72 -0
- package/index.js +13 -0
- package/package.json +67 -0
- package/packages/cli/dist/index.d.ts +3 -0
- package/packages/cli/dist/index.d.ts.map +1 -0
- package/packages/cli/dist/index.js +335 -0
- package/packages/cli/dist/index.js.map +1 -0
- 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,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;
|