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 CHANGED
@@ -1,73 +1,282 @@
1
- # React + TypeScript + Vite
1
+ # ai-dialogue-react
2
2
 
3
- This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
3
+ 一个面向 AI 对话场景的 React UI 组件,当前导出一个核心组件:`DialogueBox`。
4
4
 
5
- Currently, two official plugins are available:
5
+ 它支持这些能力:
6
6
 
7
- - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
8
- - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
7
+ - AI / 用户双角色消息展示
8
+ - 用户实时语音识别文本 `partialText`
9
+ - AI 流式输出文本 `aiPartialText`
10
+ - 打字机逐字显示
11
+ - 头像、气泡背景、文本样式自定义
12
+ - 自动注入基础动画样式,无需额外引入组件 CSS 文件
9
13
 
10
- ## React Compiler
14
+ ## 1. 安装
11
15
 
12
- The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
16
+ ```bash
17
+ npm i ai-dialogue-react
18
+ ```
13
19
 
14
- ## Expanding the ESLint configuration
20
+ 如果你的项目还没有启用 Tailwind CSS,需要一并安装并配置。
15
21
 
16
- If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
22
+ ```bash
23
+ npm i tailwindcss @tailwindcss/vite
24
+ ```
17
25
 
18
- ```js
19
- export default defineConfig([
20
- globalIgnores(['dist']),
21
- {
22
- files: ['**/*.{ts,tsx}'],
23
- extends: [
24
- // Other configs...
25
-
26
- // Remove tseslint.configs.recommended and replace with this
27
- tseslint.configs.recommendedTypeChecked,
28
- // Alternatively, use this for stricter rules
29
- tseslint.configs.strictTypeChecked,
30
- // Optionally, add this for stylistic rules
31
- tseslint.configs.stylisticTypeChecked,
32
-
33
- // Other configs...
34
- ],
35
- languageOptions: {
36
- parserOptions: {
37
- project: ['./tsconfig.node.json', './tsconfig.app.json'],
38
- tsconfigRootDir: import.meta.dirname,
39
- },
40
- // other options...
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
- You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
51
+ 说明:
47
52
 
48
- ```js
49
- // eslint.config.js
50
- import reactX from 'eslint-plugin-react-x'
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
- export default defineConfig([
54
- globalIgnores(['dist']),
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
- files: ['**/*.{ts,tsx}'],
57
- extends: [
58
- // Other configs...
59
- // Enable lint rules for React
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: n, partialText: r = "", aiPartialText: o = "", language: p = "zh", userAvatarUrl: m = c, aiAvatarUrl: h = l, userBubbleBgUrl: g, aiBubbleBgUrl: _, aiBubbleBgLongUrl: v, typewriterSpeed: y = 40, className: b, style: x, avatarClassName: S, avatarStyle: C, nameClassName: w, nameStyle: T, userBubbleClassName: E, userBubbleStyle: D, aiBubbleClassName: O, aiBubbleStyle: k, partialTextColor: A = "#ffd54f" }) {
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 j = t(null);
51
+ let M = t(null), N = t(r), P = t(o), F = t(p), [I, L] = n(/* @__PURE__ */ new Set());
52
52
  e(() => {
53
- j.current?.scrollTo({
54
- top: j.current.scrollHeight,
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 M = n.length + +!!r, N = b ?? "relative w-[1000px] h-[653px] overflow-y-auto flex flex-col gap-8 dlg-no-scrollbar", P = {
63
- user: p === "zh" ? "用户" : "User",
64
- ai: p === "zh" ? "AI助理" : "AI Assistant",
65
- userLive: p === "zh" ? "用户(实时)" : "User (Real-time)",
66
- aiLive: p === "zh" ? "AI助理(实时)" : "AI Assistant (Real-time)"
67
- }, F = d("w-32 h-32 rounded-2xl shrink-0 bg-gray-300", S), I = d("text-[40px] text-[#213140]", w), L = d("p-10.5 min-h-32 text-[40px] leading-tight", E), R = d("p-10.5 min-h-32 text-[40px] leading-tight flex items-center", O);
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: j,
70
- className: N,
71
- style: x,
91
+ ref: M,
92
+ className: z,
93
+ style: S,
72
94
  children: [
73
- n.map((e, t) => {
74
- let n = t < M - 3, r = t === M - 3, o = t === M - 1, s = e.type === "user";
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: F,
100
+ className: V,
79
101
  style: {
80
- backgroundImage: `url(${s ? m : h})`,
102
+ backgroundImage: `url(${s ? h : g})`,
81
103
  backgroundSize: "cover",
82
104
  backgroundPosition: "center",
83
- ...C
105
+ ...w
84
106
  }
85
107
  }), /* @__PURE__ */ a("div", {
86
- className: "flex flex-col gap-2 max-w-[80%]",
108
+ className: "flex max-w-[80%] flex-col items-start gap-2",
87
109
  children: [/* @__PURE__ */ i("div", {
88
- className: I,
89
- style: T,
90
- children: e.name || (s ? P.user : P.ai)
110
+ className: H,
111
+ style: E,
112
+ children: e.name || (s ? B.user : B.ai)
91
113
  }), /* @__PURE__ */ i("div", {
92
- className: s ? L : R,
114
+ className: s ? U : W,
93
115
  style: {
94
- ...u(s ? g : e.content.length <= 5 ? _ : v, s ? "#4A90D9" : "#E8E8E8"),
116
+ ...u(s ? _ : e.content.length <= 5 ? v : y, s ? "#4A90D9" : "#E8E8E8"),
95
117
  color: s ? "#FFFFFF" : "#000",
96
- ...s ? D : k
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: y
122
+ speed: b
101
123
  }, e.id)
102
124
  })]
103
125
  })]
104
126
  }, e.id);
105
127
  }),
106
- r && /* @__PURE__ */ a("div", {
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: F,
131
+ className: V,
110
132
  style: {
111
- backgroundImage: `url(${m})`,
133
+ backgroundImage: `url(${h})`,
112
134
  backgroundSize: "cover",
113
135
  backgroundPosition: "center",
114
- ...C
136
+ ...w
115
137
  }
116
138
  }), /* @__PURE__ */ a("div", {
117
- className: "flex flex-col gap-2 max-w-[80%]",
139
+ className: "flex max-w-[80%] flex-col items-start gap-2",
118
140
  children: [/* @__PURE__ */ i("div", {
119
- className: I,
120
- style: T,
121
- children: P.userLive
141
+ className: H,
142
+ style: E,
143
+ children: B.userLive
122
144
  }), /* @__PURE__ */ i("div", {
123
- className: L,
145
+ className: U,
124
146
  style: {
125
- ...u(g, "#4A90D9"),
147
+ ...u(_, "#4A90D9"),
126
148
  color: "#FFFFFF",
127
- ...D
149
+ ...O
128
150
  },
129
151
  children: /* @__PURE__ */ i("div", {
130
- style: { color: A },
152
+ style: { color: j },
131
153
  children: /* @__PURE__ */ i(f, {
132
- text: r,
133
- speed: y
154
+ text: o,
155
+ speed: b
134
156
  })
135
157
  })
136
158
  })]
137
159
  })]
138
160
  }),
139
- o && /* @__PURE__ */ a("div", {
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: F,
164
+ className: V,
143
165
  style: {
144
- backgroundImage: `url(${h})`,
166
+ backgroundImage: `url(${g})`,
145
167
  backgroundSize: "cover",
146
168
  backgroundPosition: "center",
147
- ...C
169
+ ...w
148
170
  }
149
171
  }), /* @__PURE__ */ a("div", {
150
- className: "flex flex-col gap-2 max-w-[80%]",
172
+ className: "flex max-w-[80%] flex-col items-start gap-2",
151
173
  children: [/* @__PURE__ */ i("div", {
152
- className: I,
153
- style: T,
154
- children: P.aiLive
174
+ className: H,
175
+ style: E,
176
+ children: B.aiLive
155
177
  }), /* @__PURE__ */ i("div", {
156
- className: R,
178
+ className: W,
157
179
  style: {
158
- ...u(o.length <= 5 ? _ : v, "#E8E8E8"),
180
+ ...u(p.length <= 5 ? v : y, "#E8E8E8"),
159
181
  color: "#000",
160
- ...k
182
+ ...A
161
183
  },
162
- children: /* @__PURE__ */ i(f, {
163
- text: o,
164
- speed: y
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,j=v??`relative w-[1000px] h-[653px] overflow-y-auto flex flex-col gap-8 dlg-no-scrollbar`,M={user:d===`zh`?`用户`:`User`,ai:d===`zh`?`AI助理`:`AI Assistant`,userLive:d===`zh`?`用户(实时)`:`User (Real-time)`,aiLive:d===`zh`?`AI助理(实时)`:`AI Assistant (Real-time)`},N=c(`w-32 h-32 rounded-2xl shrink-0 bg-gray-300`,b),P=c(`text-[40px] text-[#213140]`,S),F=c(`p-10.5 min-h-32 text-[40px] leading-tight`,w),I=c(`p-10.5 min-h-32 text-[40px] leading-tight flex items-center`,E);return(0,n.jsxs)(`div`,{ref:k,className:j,style:y,children:[e.map((e,t)=>{let r=t<A-3,i=t===A-3,a=t===A-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:N,style:{backgroundImage:`url(${o?f:p})`,backgroundSize:`cover`,backgroundPosition:`center`,...x}}),(0,n.jsxs)(`div`,{className:`flex flex-col gap-2 max-w-[80%]`,children:[(0,n.jsx)(`div`,{className:P,style:C,children:e.name||(o?M.user:M.ai)}),(0,n.jsx)(`div`,{className:o?F:I,style:{...s(o?m:e.content.length<=5?h:g,o?`#4A90D9`:`#E8E8E8`),color:o?`#FFFFFF`:`#000`,...o?T:D},children:(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:N,style:{backgroundImage:`url(${f})`,backgroundSize:`cover`,backgroundPosition:`center`,...x}}),(0,n.jsxs)(`div`,{className:`flex flex-col gap-2 max-w-[80%]`,children:[(0,n.jsx)(`div`,{className:P,style:C,children:M.userLive}),(0,n.jsx)(`div`,{className:F,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:N,style:{backgroundImage:`url(${p})`,backgroundSize:`cover`,backgroundPosition:`center`,...x}}),(0,n.jsxs)(`div`,{className:`flex flex-col gap-2 max-w-[80%]`,children:[(0,n.jsx)(`div`,{className:P,style:C,children:M.aiLive}),(0,n.jsx)(`div`,{className:I,style:{...s(u.length<=5?h:g,`#E8E8E8`),color:`#000`,...D},children:(0,n.jsx)(l,{text:u,speed:_})})]})]})]})}e.DialogueBox=u});
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
- /** 用户实时 ASR 文本颜色,默认 #ffd54f(黄色) */
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.1",
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": ["react", "dialogue", "chat", "ai", "component", "tailwindcss"],
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": ["dist"],
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"