ai-dialogue-react 0.0.1 → 0.0.2
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 +267 -58
- package/dist/ai-dialogue-react.js +82 -57
- package/dist/ai-dialogue-react.umd.cjs +1 -1
- package/dist/registry/dialogue-box/dialogue-box.d.ts +1 -1
- package/package.json +13 -3
package/README.md
CHANGED
|
@@ -1,73 +1,282 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ai-dialogue-react
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
一个面向 AI 对话场景的 React UI 组件,当前导出一个核心组件:`DialogueBox`。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
它支持这些能力:
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
7
|
+
- AI / 用户双角色消息展示
|
|
8
|
+
- 用户实时语音识别文本 `partialText`
|
|
9
|
+
- AI 流式输出文本 `aiPartialText`
|
|
10
|
+
- 打字机逐字显示
|
|
11
|
+
- 头像、气泡背景、文本样式自定义
|
|
12
|
+
- 自动注入基础动画样式,无需额外引入组件 CSS 文件
|
|
9
13
|
|
|
10
|
-
##
|
|
14
|
+
## 1. 安装
|
|
11
15
|
|
|
12
|
-
|
|
16
|
+
```bash
|
|
17
|
+
npm i ai-dialogue-react
|
|
18
|
+
```
|
|
13
19
|
|
|
14
|
-
|
|
20
|
+
如果你的项目还没有启用 Tailwind CSS,需要一并安装并配置。
|
|
15
21
|
|
|
16
|
-
|
|
22
|
+
```bash
|
|
23
|
+
npm i tailwindcss @tailwindcss/vite
|
|
24
|
+
```
|
|
17
25
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
])
|
|
26
|
+
## 2. 前置条件:启用 Tailwind CSS
|
|
27
|
+
|
|
28
|
+
这个组件内部使用了 Tailwind 工具类来完成布局和尺寸控制,所以宿主项目必须启用 Tailwind CSS,否则组件结构能渲染出来,但大部分视觉样式不会生效。
|
|
29
|
+
|
|
30
|
+
### Vite 项目最小配置
|
|
31
|
+
|
|
32
|
+
`vite.config.ts`
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { defineConfig } from 'vite'
|
|
36
|
+
import react from '@vitejs/plugin-react'
|
|
37
|
+
import tailwindcss from '@tailwindcss/vite'
|
|
38
|
+
|
|
39
|
+
export default defineConfig({
|
|
40
|
+
plugins: [react(), tailwindcss()],
|
|
41
|
+
})
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`src/index.css`
|
|
45
|
+
|
|
46
|
+
```css
|
|
47
|
+
@import "tailwindcss";
|
|
48
|
+
@source "../node_modules/ai-dialogue-react/dist/**/*.{js,ts,jsx,tsx}";
|
|
44
49
|
```
|
|
45
50
|
|
|
46
|
-
|
|
51
|
+
说明:
|
|
47
52
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
import reactDom from 'eslint-plugin-react-dom'
|
|
53
|
+
- `@import "tailwindcss";` 用于启用 Tailwind CSS。
|
|
54
|
+
- `@source` 很重要,用来让 Tailwind 扫描 `ai-dialogue-react` 包内的类名;不加这一行时,组件里的 Tailwind 类通常不会被生成。
|
|
55
|
+
- 如果你的项目已经接入 Tailwind,只需要补上针对 `ai-dialogue-react` 的扫描配置即可。
|
|
52
56
|
|
|
53
|
-
|
|
54
|
-
|
|
57
|
+
## 3. 最小可运行示例
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
import { useEffect, useRef, useState } from 'react'
|
|
61
|
+
import { DialogueBox, type Message } from 'ai-dialogue-react'
|
|
62
|
+
|
|
63
|
+
const TYPEWRITER_SPEED = 30
|
|
64
|
+
const MESSAGE_SETTLE_DELAY = 300
|
|
65
|
+
const getTypingDuration = (text: string) => Math.max(text.length * TYPEWRITER_SPEED + 200, 900)
|
|
66
|
+
const wait = (ms: number) => new Promise<void>((resolve) => window.setTimeout(resolve, ms))
|
|
67
|
+
|
|
68
|
+
const initialMessages: Message[] = [
|
|
69
|
+
{
|
|
70
|
+
id: 1,
|
|
71
|
+
type: 'user',
|
|
72
|
+
name: '用户',
|
|
73
|
+
content: '你好,请介绍一下你自己。',
|
|
74
|
+
},
|
|
55
75
|
{
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
reactX.configs['recommended-typescript'],
|
|
61
|
-
// Enable lint rules for React DOM
|
|
62
|
-
reactDom.configs.recommended,
|
|
63
|
-
],
|
|
64
|
-
languageOptions: {
|
|
65
|
-
parserOptions: {
|
|
66
|
-
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
|
67
|
-
tsconfigRootDir: import.meta.dirname,
|
|
68
|
-
},
|
|
69
|
-
// other options...
|
|
70
|
-
},
|
|
76
|
+
id: 2,
|
|
77
|
+
type: 'ai',
|
|
78
|
+
name: '数字人',
|
|
79
|
+
content: '你好,我是 AI 助理,可以帮助你完成问答与讲解。',
|
|
71
80
|
},
|
|
72
|
-
]
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
export default function App() {
|
|
84
|
+
const [messages, setMessages] = useState<Message[]>(initialMessages)
|
|
85
|
+
const [partialText, setPartialText] = useState('')
|
|
86
|
+
const [aiPartialText, setAiPartialText] = useState('')
|
|
87
|
+
const [isSimulating, setIsSimulating] = useState(false)
|
|
88
|
+
const simulationIdRef = useRef(0)
|
|
89
|
+
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
return () => {
|
|
92
|
+
simulationIdRef.current += 1
|
|
93
|
+
}
|
|
94
|
+
}, [])
|
|
95
|
+
|
|
96
|
+
const mockUserSpeaking = async () => {
|
|
97
|
+
if (isSimulating) return
|
|
98
|
+
|
|
99
|
+
const userText = '我想了解一下这个组件怎么接入项目'
|
|
100
|
+
const aiText = '你可以先安装 npm 包,然后在页面中引入 DialogueBox 组件。'
|
|
101
|
+
const simulationId = simulationIdRef.current + 1
|
|
102
|
+
simulationIdRef.current = simulationId
|
|
103
|
+
setIsSimulating(true)
|
|
104
|
+
setAiPartialText('')
|
|
105
|
+
setPartialText(userText)
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
await wait(getTypingDuration(userText))
|
|
109
|
+
if (simulationIdRef.current !== simulationId) return
|
|
110
|
+
|
|
111
|
+
setMessages((prev) => [
|
|
112
|
+
...prev,
|
|
113
|
+
{
|
|
114
|
+
id: Date.now(),
|
|
115
|
+
type: 'user',
|
|
116
|
+
name: '用户',
|
|
117
|
+
content: userText,
|
|
118
|
+
},
|
|
119
|
+
])
|
|
120
|
+
setPartialText('')
|
|
121
|
+
|
|
122
|
+
await wait(MESSAGE_SETTLE_DELAY)
|
|
123
|
+
if (simulationIdRef.current !== simulationId) return
|
|
124
|
+
|
|
125
|
+
setAiPartialText(aiText)
|
|
126
|
+
|
|
127
|
+
await wait(getTypingDuration(aiText))
|
|
128
|
+
if (simulationIdRef.current !== simulationId) return
|
|
129
|
+
|
|
130
|
+
setMessages((prev) => [
|
|
131
|
+
...prev,
|
|
132
|
+
{
|
|
133
|
+
id: Date.now() + 1,
|
|
134
|
+
type: 'ai',
|
|
135
|
+
name: '数字人',
|
|
136
|
+
content: aiText,
|
|
137
|
+
},
|
|
138
|
+
])
|
|
139
|
+
setAiPartialText('')
|
|
140
|
+
} finally {
|
|
141
|
+
if (simulationIdRef.current === simulationId) {
|
|
142
|
+
setIsSimulating(false)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return (
|
|
148
|
+
<div className="min-h-screen bg-slate-100 p-6">
|
|
149
|
+
<button
|
|
150
|
+
type="button"
|
|
151
|
+
onClick={mockUserSpeaking}
|
|
152
|
+
disabled={isSimulating}
|
|
153
|
+
className="mb-6 rounded-lg bg-slate-900 px-4 py-2 text-white disabled:cursor-not-allowed disabled:opacity-60"
|
|
154
|
+
>
|
|
155
|
+
模拟一轮对话
|
|
156
|
+
</button>
|
|
157
|
+
|
|
158
|
+
<DialogueBox
|
|
159
|
+
messages={messages}
|
|
160
|
+
partialText={partialText}
|
|
161
|
+
aiPartialText={aiPartialText}
|
|
162
|
+
language="zh"
|
|
163
|
+
className="h-[70vh] w-full overflow-y-auto rounded-3xl bg-white/70 p-6 shadow-xl dlg-no-scrollbar"
|
|
164
|
+
typewriterSpeed={TYPEWRITER_SPEED}
|
|
165
|
+
/>
|
|
166
|
+
</div>
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## 4. 基本使用方式
|
|
172
|
+
|
|
173
|
+
最关键的是维护三个数据源:
|
|
174
|
+
|
|
175
|
+
- `messages`:已经完成的正式消息列表
|
|
176
|
+
- `partialText`:用户正在说话时的实时文本
|
|
177
|
+
- `aiPartialText`:AI 正在流式生成时的实时文本
|
|
178
|
+
|
|
179
|
+
推荐的更新时机:
|
|
180
|
+
|
|
181
|
+
1. 用户说话过程中,持续更新 `partialText`
|
|
182
|
+
2. 用户一句话结束后,把最终文本 push 到 `messages`,然后清空 `partialText`
|
|
183
|
+
3. AI 返回过程中,持续更新 `aiPartialText`
|
|
184
|
+
4. AI 返回结束后,把最终文本 push 到 `messages`,然后清空 `aiPartialText`
|
|
185
|
+
|
|
186
|
+
## 5. 数据结构
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
export interface Message {
|
|
190
|
+
id: number
|
|
191
|
+
type: 'ai' | 'user'
|
|
192
|
+
name: string
|
|
193
|
+
content: string
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
字段说明:
|
|
198
|
+
|
|
199
|
+
- `id`:消息唯一标识,建议始终保持唯一
|
|
200
|
+
- `type`:消息角色,只能是 `user` 或 `ai`
|
|
201
|
+
- `name`:显示名称;如果传空值,组件会根据 `language` 自动回退为默认名称
|
|
202
|
+
- `content`:消息正文
|
|
203
|
+
|
|
204
|
+
## 6. 常用 Props
|
|
205
|
+
|
|
206
|
+
| Prop | 类型 | 说明 |
|
|
207
|
+
| --- | --- | --- |
|
|
208
|
+
| `messages` | `Message[]` | 已完成的正式消息列表 |
|
|
209
|
+
| `partialText` | `string` | 用户实时 ASR 文本 |
|
|
210
|
+
| `aiPartialText` | `string` | AI 实时流式文本 |
|
|
211
|
+
| `language` | `'zh' \| 'en'` | 名称标签语言 |
|
|
212
|
+
| `typewriterSpeed` | `number` | 打字机速度,单位 ms/字,默认 `40` |
|
|
213
|
+
| `userAvatarUrl` | `string` | 用户头像图片地址 |
|
|
214
|
+
| `aiAvatarUrl` | `string` | AI 头像图片地址 |
|
|
215
|
+
| `userBubbleBgUrl` | `string` | 用户气泡背景图 |
|
|
216
|
+
| `aiBubbleBgUrl` | `string` | AI 短文本气泡背景图 |
|
|
217
|
+
| `aiBubbleBgLongUrl` | `string` | AI 长文本气泡背景图 |
|
|
218
|
+
| `className` | `string` | 外层滚动容器类名 |
|
|
219
|
+
| `style` | `CSSProperties` | 外层滚动容器样式 |
|
|
220
|
+
| `avatarClassName` | `string` | 头像类名 |
|
|
221
|
+
| `avatarStyle` | `CSSProperties` | 头像样式 |
|
|
222
|
+
| `nameClassName` | `string` | 名称文本类名 |
|
|
223
|
+
| `nameStyle` | `CSSProperties` | 名称文本样式 |
|
|
224
|
+
| `userBubbleClassName` | `string` | 用户气泡类名 |
|
|
225
|
+
| `userBubbleStyle` | `CSSProperties` | 用户气泡样式 |
|
|
226
|
+
| `aiBubbleClassName` | `string` | AI 气泡类名 |
|
|
227
|
+
| `aiBubbleStyle` | `CSSProperties` | AI 气泡样式 |
|
|
228
|
+
| `partialTextColor` | `string` | 实时文本颜色,用户与 AI 流式阶段共用,默认 `#ffd54f` |
|
|
229
|
+
|
|
230
|
+
## 7. 自定义头像和背景图
|
|
231
|
+
|
|
232
|
+
```tsx
|
|
233
|
+
<DialogueBox
|
|
234
|
+
messages={messages}
|
|
235
|
+
userAvatarUrl="/image/user-avatar.png"
|
|
236
|
+
aiAvatarUrl="/image/ai-avatar.png"
|
|
237
|
+
userBubbleBgUrl="/image/user-bubble.png"
|
|
238
|
+
aiBubbleBgUrl="/image/ai-bubble-short.png"
|
|
239
|
+
aiBubbleBgLongUrl="/image/ai-bubble-long.png"
|
|
240
|
+
/>
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
如果不传头像,组件会使用内置的默认 SVG 头像。
|
|
244
|
+
|
|
245
|
+
## 8. 使用注意事项
|
|
246
|
+
|
|
247
|
+
### 默认尺寸偏大
|
|
248
|
+
|
|
249
|
+
组件默认容器类名是:
|
|
250
|
+
|
|
251
|
+
```txt
|
|
252
|
+
relative w-[1000px] h-[653px] overflow-y-auto flex flex-col gap-10 dlg-no-scrollbar
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
这更适合大屏、数字人展示页,不太适合直接放进移动端或普通表单页。实际项目里通常建议你传入自己的 `className` 或 `style` 覆盖默认尺寸。
|
|
256
|
+
|
|
257
|
+
### 旧消息会自动弱化
|
|
258
|
+
|
|
259
|
+
这个组件当前并不是传统 IM 样式的“完整聊天记录”组件。它会重点突出最近几条对话:
|
|
260
|
+
|
|
261
|
+
- 更早的消息会被隐藏
|
|
262
|
+
- 倒数第三条消息会半透明显示
|
|
263
|
+
- 最新消息会带入场动画
|
|
264
|
+
|
|
265
|
+
所以它更适合数字人讲解、大屏播报、实时对话展示,而不是客服聊天窗口那种完整历史列表。
|
|
266
|
+
|
|
267
|
+
### 不需要额外引入组件 CSS 文件
|
|
268
|
+
|
|
269
|
+
组件内部会自动注入滚动条隐藏和入场动画相关样式,但 Tailwind 工具类仍然必须由你的项目来生成。
|
|
270
|
+
|
|
271
|
+
## 9. 导出内容
|
|
272
|
+
|
|
273
|
+
```ts
|
|
274
|
+
export { DialogueBox } from 'ai-dialogue-react'
|
|
275
|
+
export type { DialogueBoxProps, Message } from 'ai-dialogue-react'
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
## 10. 构建本库
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
pnpm run build:lib
|
|
73
282
|
```
|
|
@@ -46,122 +46,147 @@ function f({ text: a, speed: o = 40 }) {
|
|
|
46
46
|
return () => clearTimeout(t);
|
|
47
47
|
}, [s, o]), /* @__PURE__ */ i(r, { children: s });
|
|
48
48
|
}
|
|
49
|
-
function p({ messages:
|
|
49
|
+
function p({ messages: r, partialText: o = "", aiPartialText: p = "", language: m = "zh", userAvatarUrl: h = c, aiAvatarUrl: g = l, userBubbleBgUrl: _, aiBubbleBgUrl: v, aiBubbleBgLongUrl: y, typewriterSpeed: b = 40, className: x, style: S, avatarClassName: C, avatarStyle: w, nameClassName: T, nameStyle: E, userBubbleClassName: D, userBubbleStyle: O, aiBubbleClassName: k, aiBubbleStyle: A, partialTextColor: j = "#ffd54f" }) {
|
|
50
50
|
s();
|
|
51
|
-
let
|
|
51
|
+
let M = t(null), N = t(r), P = t(o), F = t(p), [I, L] = n(/* @__PURE__ */ new Set());
|
|
52
52
|
e(() => {
|
|
53
|
-
|
|
54
|
-
top:
|
|
53
|
+
M.current?.scrollTo({
|
|
54
|
+
top: M.current.scrollHeight,
|
|
55
55
|
behavior: "smooth"
|
|
56
56
|
});
|
|
57
57
|
}, [
|
|
58
|
-
n,
|
|
59
58
|
r,
|
|
60
|
-
o
|
|
59
|
+
o,
|
|
60
|
+
p
|
|
61
|
+
]), e(() => {
|
|
62
|
+
let e = N.current, t = P.current, n = F.current;
|
|
63
|
+
if (r.length > e.length) {
|
|
64
|
+
let i = r.slice(e.length), a = [];
|
|
65
|
+
if (t && !o) {
|
|
66
|
+
let e = i.find((e) => e.type === "user" && e.content === t);
|
|
67
|
+
e && a.push(e.id);
|
|
68
|
+
}
|
|
69
|
+
if (n && !p) {
|
|
70
|
+
let e = i.find((e) => e.type === "ai" && e.content === n);
|
|
71
|
+
e && a.push(e.id);
|
|
72
|
+
}
|
|
73
|
+
a.length > 0 && L((e) => {
|
|
74
|
+
let t = new Set(e);
|
|
75
|
+
return a.forEach((e) => t.add(e)), t;
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
N.current = r, P.current = o, F.current = p;
|
|
79
|
+
}, [
|
|
80
|
+
r,
|
|
81
|
+
o,
|
|
82
|
+
p
|
|
61
83
|
]);
|
|
62
|
-
let
|
|
63
|
-
user:
|
|
64
|
-
ai:
|
|
65
|
-
userLive:
|
|
66
|
-
aiLive:
|
|
67
|
-
},
|
|
84
|
+
let R = r.length + +!!o, z = d("relative flex h-[653px] w-[1000px] flex-col gap-10 overflow-y-auto dlg-no-scrollbar", x), B = {
|
|
85
|
+
user: m === "zh" ? "用户" : "User",
|
|
86
|
+
ai: m === "zh" ? "AI助理" : "AI Assistant",
|
|
87
|
+
userLive: m === "zh" ? "用户(实时)" : "User (Real-time)",
|
|
88
|
+
aiLive: m === "zh" ? "AI助理(实时)" : "AI Assistant (Real-time)"
|
|
89
|
+
}, V = d("w-32 h-32 rounded-2xl shrink-0 bg-gray-300", C), H = d("ml-5 text-[40px] text-[#213140]", T), U = d("p-10.5 min-h-32 text-[40px] leading-tight", D), W = d("p-10.5 min-h-32 text-[40px] leading-tight flex items-center", k);
|
|
68
90
|
return /* @__PURE__ */ a("div", {
|
|
69
|
-
ref:
|
|
70
|
-
className:
|
|
71
|
-
style:
|
|
91
|
+
ref: M,
|
|
92
|
+
className: z,
|
|
93
|
+
style: S,
|
|
72
94
|
children: [
|
|
73
|
-
|
|
74
|
-
let n = t <
|
|
95
|
+
r.map((e, t) => {
|
|
96
|
+
let n = t < R - 3, r = t === R - 3, o = t === R - 1, s = e.type === "user";
|
|
75
97
|
return /* @__PURE__ */ a("div", {
|
|
76
98
|
className: d("flex gap-4 w-full transition-opacity duration-500 ease-out shrink-0", n ? "opacity-0" : r ? "opacity-50" : "opacity-100", o ? "dlg-slide-in-up" : ""),
|
|
77
99
|
children: [/* @__PURE__ */ i("div", {
|
|
78
|
-
className:
|
|
100
|
+
className: V,
|
|
79
101
|
style: {
|
|
80
|
-
backgroundImage: `url(${s ?
|
|
102
|
+
backgroundImage: `url(${s ? h : g})`,
|
|
81
103
|
backgroundSize: "cover",
|
|
82
104
|
backgroundPosition: "center",
|
|
83
|
-
...
|
|
105
|
+
...w
|
|
84
106
|
}
|
|
85
107
|
}), /* @__PURE__ */ a("div", {
|
|
86
|
-
className: "flex
|
|
108
|
+
className: "flex max-w-[80%] flex-col items-start gap-2",
|
|
87
109
|
children: [/* @__PURE__ */ i("div", {
|
|
88
|
-
className:
|
|
89
|
-
style:
|
|
90
|
-
children: e.name || (s ?
|
|
110
|
+
className: H,
|
|
111
|
+
style: E,
|
|
112
|
+
children: e.name || (s ? B.user : B.ai)
|
|
91
113
|
}), /* @__PURE__ */ i("div", {
|
|
92
|
-
className: s ?
|
|
114
|
+
className: s ? U : W,
|
|
93
115
|
style: {
|
|
94
|
-
...u(s ?
|
|
116
|
+
...u(s ? _ : e.content.length <= 5 ? v : y, s ? "#4A90D9" : "#E8E8E8"),
|
|
95
117
|
color: s ? "#FFFFFF" : "#000",
|
|
96
|
-
...s ?
|
|
118
|
+
...s ? O : A
|
|
97
119
|
},
|
|
98
|
-
children: /* @__PURE__ */ i(f, {
|
|
120
|
+
children: I.has(e.id) ? e.content : /* @__PURE__ */ i(f, {
|
|
99
121
|
text: e.content,
|
|
100
|
-
speed:
|
|
122
|
+
speed: b
|
|
101
123
|
}, e.id)
|
|
102
124
|
})]
|
|
103
125
|
})]
|
|
104
126
|
}, e.id);
|
|
105
127
|
}),
|
|
106
|
-
|
|
128
|
+
o && /* @__PURE__ */ a("div", {
|
|
107
129
|
className: "flex gap-4 w-full transition-opacity duration-500 ease-out shrink-0 opacity-100 dlg-slide-in-up",
|
|
108
130
|
children: [/* @__PURE__ */ i("div", {
|
|
109
|
-
className:
|
|
131
|
+
className: V,
|
|
110
132
|
style: {
|
|
111
|
-
backgroundImage: `url(${
|
|
133
|
+
backgroundImage: `url(${h})`,
|
|
112
134
|
backgroundSize: "cover",
|
|
113
135
|
backgroundPosition: "center",
|
|
114
|
-
...
|
|
136
|
+
...w
|
|
115
137
|
}
|
|
116
138
|
}), /* @__PURE__ */ a("div", {
|
|
117
|
-
className: "flex
|
|
139
|
+
className: "flex max-w-[80%] flex-col items-start gap-2",
|
|
118
140
|
children: [/* @__PURE__ */ i("div", {
|
|
119
|
-
className:
|
|
120
|
-
style:
|
|
121
|
-
children:
|
|
141
|
+
className: H,
|
|
142
|
+
style: E,
|
|
143
|
+
children: B.userLive
|
|
122
144
|
}), /* @__PURE__ */ i("div", {
|
|
123
|
-
className:
|
|
145
|
+
className: U,
|
|
124
146
|
style: {
|
|
125
|
-
...u(
|
|
147
|
+
...u(_, "#4A90D9"),
|
|
126
148
|
color: "#FFFFFF",
|
|
127
|
-
...
|
|
149
|
+
...O
|
|
128
150
|
},
|
|
129
151
|
children: /* @__PURE__ */ i("div", {
|
|
130
|
-
style: { color:
|
|
152
|
+
style: { color: j },
|
|
131
153
|
children: /* @__PURE__ */ i(f, {
|
|
132
|
-
text:
|
|
133
|
-
speed:
|
|
154
|
+
text: o,
|
|
155
|
+
speed: b
|
|
134
156
|
})
|
|
135
157
|
})
|
|
136
158
|
})]
|
|
137
159
|
})]
|
|
138
160
|
}),
|
|
139
|
-
|
|
161
|
+
p && /* @__PURE__ */ a("div", {
|
|
140
162
|
className: "flex gap-4 w-full transition-opacity duration-500 ease-out shrink-0 opacity-100 dlg-slide-in-up",
|
|
141
163
|
children: [/* @__PURE__ */ i("div", {
|
|
142
|
-
className:
|
|
164
|
+
className: V,
|
|
143
165
|
style: {
|
|
144
|
-
backgroundImage: `url(${
|
|
166
|
+
backgroundImage: `url(${g})`,
|
|
145
167
|
backgroundSize: "cover",
|
|
146
168
|
backgroundPosition: "center",
|
|
147
|
-
...
|
|
169
|
+
...w
|
|
148
170
|
}
|
|
149
171
|
}), /* @__PURE__ */ a("div", {
|
|
150
|
-
className: "flex
|
|
172
|
+
className: "flex max-w-[80%] flex-col items-start gap-2",
|
|
151
173
|
children: [/* @__PURE__ */ i("div", {
|
|
152
|
-
className:
|
|
153
|
-
style:
|
|
154
|
-
children:
|
|
174
|
+
className: H,
|
|
175
|
+
style: E,
|
|
176
|
+
children: B.aiLive
|
|
155
177
|
}), /* @__PURE__ */ i("div", {
|
|
156
|
-
className:
|
|
178
|
+
className: W,
|
|
157
179
|
style: {
|
|
158
|
-
...u(
|
|
180
|
+
...u(p.length <= 5 ? v : y, "#E8E8E8"),
|
|
159
181
|
color: "#000",
|
|
160
|
-
...
|
|
182
|
+
...A
|
|
161
183
|
},
|
|
162
|
-
children: /* @__PURE__ */ i(
|
|
163
|
-
|
|
164
|
-
|
|
184
|
+
children: /* @__PURE__ */ i("div", {
|
|
185
|
+
style: { color: j },
|
|
186
|
+
children: /* @__PURE__ */ i(f, {
|
|
187
|
+
text: p,
|
|
188
|
+
speed: b
|
|
189
|
+
})
|
|
165
190
|
})
|
|
166
191
|
})]
|
|
167
192
|
})]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require(`react`),require(`react/jsx-runtime`)):typeof define==`function`&&define.amd?define([`exports`,`react`,`react/jsx-runtime`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.AiDialogueReact={},e.React,e.ReactJsxRuntime))})(this,function(e,t,n){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var r=[`.dlg-no-scrollbar{-ms-overflow-style:none;scrollbar-width:none}`,`.dlg-no-scrollbar::-webkit-scrollbar{display:none}`,`@keyframes dlg-slide-in-up{from{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}`,`.dlg-slide-in-up{animation:.5s ease-out forwards dlg-slide-in-up}`].join(``);function i(){(0,t.useEffect)(()=>{if(typeof document>`u`)return;let e=`__dialogue-box-styles__`;if(document.getElementById(e))return;let t=document.createElement(`style`);t.id=e,t.textContent=r,document.head.appendChild(t)},[])}var a=`data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128"><rect width="128" height="128" rx="16" fill="%234A90D9"/><circle cx="64" cy="48" r="24" fill="white"/><ellipse cx="64" cy="100" rx="36" ry="22" fill="white"/></svg>`,o=`data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128"><rect width="128" height="128" rx="16" fill="%23A855F7"/><circle cx="64" cy="48" r="24" fill="white"/><ellipse cx="64" cy="100" rx="36" ry="22" fill="white"/></svg>`;function s(e,t){return e?{backgroundImage:`url(${e})`,backgroundSize:`100% 100%`}:{background:t}}function c(...e){return e.filter(Boolean).join(` `)}function l({text:e,speed:r=40}){let[i,a]=(0,t.useState)(``),o=(0,t.useRef)({target:e,pos:0});return(0,t.useEffect)(()=>{let t=o.current;e!==t.target&&(e.startsWith(t.target)?t.target=e:(o.current={target:e,pos:0},a(``)))},[e]),(0,t.useEffect)(()=>{let e=o.current;if(e.pos>=e.target.length)return;let t=setTimeout(()=>{e.pos+=1,a(e.target.slice(0,e.pos))},r);return()=>clearTimeout(t)},[i,r]),(0,n.jsx)(n.Fragment,{children:i})}function u({messages:e,partialText:r=``,aiPartialText:u=``,language:d=`zh`,userAvatarUrl:f=a,aiAvatarUrl:p=o,userBubbleBgUrl:m,aiBubbleBgUrl:h,aiBubbleBgLongUrl:g,typewriterSpeed:_=40,className:v,style:y,avatarClassName:b,avatarStyle:x,nameClassName:S,nameStyle:C,userBubbleClassName:w,userBubbleStyle:T,aiBubbleClassName:E,aiBubbleStyle:D,partialTextColor:O=`#ffd54f`}){i();let k=(0,t.useRef)(null);(0,t.useEffect)(()=>{k.current?.scrollTo({top:k.current.scrollHeight,behavior:`smooth`})},[e,r,u]);let A=e.length+ +!!r,
|
|
1
|
+
(function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require(`react`),require(`react/jsx-runtime`)):typeof define==`function`&&define.amd?define([`exports`,`react`,`react/jsx-runtime`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.AiDialogueReact={},e.React,e.ReactJsxRuntime))})(this,function(e,t,n){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var r=[`.dlg-no-scrollbar{-ms-overflow-style:none;scrollbar-width:none}`,`.dlg-no-scrollbar::-webkit-scrollbar{display:none}`,`@keyframes dlg-slide-in-up{from{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}`,`.dlg-slide-in-up{animation:.5s ease-out forwards dlg-slide-in-up}`].join(``);function i(){(0,t.useEffect)(()=>{if(typeof document>`u`)return;let e=`__dialogue-box-styles__`;if(document.getElementById(e))return;let t=document.createElement(`style`);t.id=e,t.textContent=r,document.head.appendChild(t)},[])}var a=`data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128"><rect width="128" height="128" rx="16" fill="%234A90D9"/><circle cx="64" cy="48" r="24" fill="white"/><ellipse cx="64" cy="100" rx="36" ry="22" fill="white"/></svg>`,o=`data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128"><rect width="128" height="128" rx="16" fill="%23A855F7"/><circle cx="64" cy="48" r="24" fill="white"/><ellipse cx="64" cy="100" rx="36" ry="22" fill="white"/></svg>`;function s(e,t){return e?{backgroundImage:`url(${e})`,backgroundSize:`100% 100%`}:{background:t}}function c(...e){return e.filter(Boolean).join(` `)}function l({text:e,speed:r=40}){let[i,a]=(0,t.useState)(``),o=(0,t.useRef)({target:e,pos:0});return(0,t.useEffect)(()=>{let t=o.current;e!==t.target&&(e.startsWith(t.target)?t.target=e:(o.current={target:e,pos:0},a(``)))},[e]),(0,t.useEffect)(()=>{let e=o.current;if(e.pos>=e.target.length)return;let t=setTimeout(()=>{e.pos+=1,a(e.target.slice(0,e.pos))},r);return()=>clearTimeout(t)},[i,r]),(0,n.jsx)(n.Fragment,{children:i})}function u({messages:e,partialText:r=``,aiPartialText:u=``,language:d=`zh`,userAvatarUrl:f=a,aiAvatarUrl:p=o,userBubbleBgUrl:m,aiBubbleBgUrl:h,aiBubbleBgLongUrl:g,typewriterSpeed:_=40,className:v,style:y,avatarClassName:b,avatarStyle:x,nameClassName:S,nameStyle:C,userBubbleClassName:w,userBubbleStyle:T,aiBubbleClassName:E,aiBubbleStyle:D,partialTextColor:O=`#ffd54f`}){i();let k=(0,t.useRef)(null),A=(0,t.useRef)(e),j=(0,t.useRef)(r),M=(0,t.useRef)(u),[N,P]=(0,t.useState)(new Set);(0,t.useEffect)(()=>{k.current?.scrollTo({top:k.current.scrollHeight,behavior:`smooth`})},[e,r,u]),(0,t.useEffect)(()=>{let t=A.current,n=j.current,i=M.current;if(e.length>t.length){let a=e.slice(t.length),o=[];if(n&&!r){let e=a.find(e=>e.type===`user`&&e.content===n);e&&o.push(e.id)}if(i&&!u){let e=a.find(e=>e.type===`ai`&&e.content===i);e&&o.push(e.id)}o.length>0&&P(e=>{let t=new Set(e);return o.forEach(e=>t.add(e)),t})}A.current=e,j.current=r,M.current=u},[e,r,u]);let F=e.length+ +!!r,I=c(`relative flex h-[653px] w-[1000px] flex-col gap-10 overflow-y-auto dlg-no-scrollbar`,v),L={user:d===`zh`?`用户`:`User`,ai:d===`zh`?`AI助理`:`AI Assistant`,userLive:d===`zh`?`用户(实时)`:`User (Real-time)`,aiLive:d===`zh`?`AI助理(实时)`:`AI Assistant (Real-time)`},R=c(`w-32 h-32 rounded-2xl shrink-0 bg-gray-300`,b),z=c(`ml-5 text-[40px] text-[#213140]`,S),B=c(`p-10.5 min-h-32 text-[40px] leading-tight`,w),V=c(`p-10.5 min-h-32 text-[40px] leading-tight flex items-center`,E);return(0,n.jsxs)(`div`,{ref:k,className:I,style:y,children:[e.map((e,t)=>{let r=t<F-3,i=t===F-3,a=t===F-1,o=e.type===`user`;return(0,n.jsxs)(`div`,{className:c(`flex gap-4 w-full transition-opacity duration-500 ease-out shrink-0`,r?`opacity-0`:i?`opacity-50`:`opacity-100`,a?`dlg-slide-in-up`:``),children:[(0,n.jsx)(`div`,{className:R,style:{backgroundImage:`url(${o?f:p})`,backgroundSize:`cover`,backgroundPosition:`center`,...x}}),(0,n.jsxs)(`div`,{className:`flex max-w-[80%] flex-col items-start gap-2`,children:[(0,n.jsx)(`div`,{className:z,style:C,children:e.name||(o?L.user:L.ai)}),(0,n.jsx)(`div`,{className:o?B:V,style:{...s(o?m:e.content.length<=5?h:g,o?`#4A90D9`:`#E8E8E8`),color:o?`#FFFFFF`:`#000`,...o?T:D},children:N.has(e.id)?e.content:(0,n.jsx)(l,{text:e.content,speed:_},e.id)})]})]},e.id)}),r&&(0,n.jsxs)(`div`,{className:`flex gap-4 w-full transition-opacity duration-500 ease-out shrink-0 opacity-100 dlg-slide-in-up`,children:[(0,n.jsx)(`div`,{className:R,style:{backgroundImage:`url(${f})`,backgroundSize:`cover`,backgroundPosition:`center`,...x}}),(0,n.jsxs)(`div`,{className:`flex max-w-[80%] flex-col items-start gap-2`,children:[(0,n.jsx)(`div`,{className:z,style:C,children:L.userLive}),(0,n.jsx)(`div`,{className:B,style:{...s(m,`#4A90D9`),color:`#FFFFFF`,...T},children:(0,n.jsx)(`div`,{style:{color:O},children:(0,n.jsx)(l,{text:r,speed:_})})})]})]}),u&&(0,n.jsxs)(`div`,{className:`flex gap-4 w-full transition-opacity duration-500 ease-out shrink-0 opacity-100 dlg-slide-in-up`,children:[(0,n.jsx)(`div`,{className:R,style:{backgroundImage:`url(${p})`,backgroundSize:`cover`,backgroundPosition:`center`,...x}}),(0,n.jsxs)(`div`,{className:`flex max-w-[80%] flex-col items-start gap-2`,children:[(0,n.jsx)(`div`,{className:z,style:C,children:L.aiLive}),(0,n.jsx)(`div`,{className:V,style:{...s(u.length<=5?h:g,`#E8E8E8`),color:`#000`,...D},children:(0,n.jsx)(`div`,{style:{color:O},children:(0,n.jsx)(l,{text:u,speed:_})})})]})]})]})}e.DialogueBox=u});
|
|
@@ -46,7 +46,7 @@ export interface DialogueBoxProps {
|
|
|
46
46
|
aiBubbleClassName?: string;
|
|
47
47
|
/** AI 气泡 style(会覆盖默认文字颜色 #000) */
|
|
48
48
|
aiBubbleStyle?: CSSProperties;
|
|
49
|
-
/**
|
|
49
|
+
/** 实时文本颜色,用户与 AI 流式阶段共用,默认 #ffd54f(黄色) */
|
|
50
50
|
partialTextColor?: string;
|
|
51
51
|
}
|
|
52
52
|
export declare function DialogueBox({ messages, partialText, aiPartialText, language, userAvatarUrl, aiAvatarUrl, userBubbleBgUrl, aiBubbleBgUrl, aiBubbleBgLongUrl, typewriterSpeed, className, style, avatarClassName, avatarStyle, nameClassName, nameStyle, userBubbleClassName, userBubbleStyle, aiBubbleClassName, aiBubbleStyle, partialTextColor, }: DialogueBoxProps): import("react/jsx-runtime").JSX.Element;
|
package/package.json
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-dialogue-react",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A pure UI dialogue box component for AI chat applications, built with React and Tailwind CSS.",
|
|
6
|
-
"keywords": [
|
|
6
|
+
"keywords": [
|
|
7
|
+
"react",
|
|
8
|
+
"dialogue",
|
|
9
|
+
"chat",
|
|
10
|
+
"ai",
|
|
11
|
+
"component",
|
|
12
|
+
"tailwindcss"
|
|
13
|
+
],
|
|
7
14
|
"author": "huangzusheng",
|
|
8
15
|
"license": "MIT",
|
|
9
16
|
"main": "./dist/ai-dialogue-react.umd.cjs",
|
|
@@ -16,7 +23,9 @@
|
|
|
16
23
|
"types": "./dist/index.d.ts"
|
|
17
24
|
}
|
|
18
25
|
},
|
|
19
|
-
"files": [
|
|
26
|
+
"files": [
|
|
27
|
+
"dist"
|
|
28
|
+
],
|
|
20
29
|
"scripts": {
|
|
21
30
|
"dev": "vite",
|
|
22
31
|
"build": "tsc -b && vite build",
|
|
@@ -28,6 +37,7 @@
|
|
|
28
37
|
},
|
|
29
38
|
"dependencies": {
|
|
30
39
|
"@tailwindcss/vite": "^4.2.2",
|
|
40
|
+
"ai-dialogue-react": "^0.0.1",
|
|
31
41
|
"react": "^19.2.4",
|
|
32
42
|
"react-dom": "^19.2.4",
|
|
33
43
|
"tailwindcss": "^4.2.2"
|