@amaster.ai/components-templates 1.5.0 → 1.8.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 +12 -8
  2. package/components/ai-assistant/amaster.config.json +3 -0
  3. package/components/ai-assistant/package.json +3 -3
  4. package/components/ai-assistant/template/components/chat-assistant-message.tsx +1 -1
  5. package/components/ai-assistant/template/components/chat-banner.tsx +1 -1
  6. package/components/ai-assistant/template/components/chat-display-mode-switcher.tsx +6 -6
  7. package/components/ai-assistant/template/components/chat-floating-button.tsx +4 -5
  8. package/components/ai-assistant/template/components/chat-floating-card.tsx +3 -3
  9. package/components/ai-assistant/template/components/chat-header.tsx +5 -5
  10. package/components/ai-assistant/template/components/chat-input.tsx +36 -24
  11. package/components/ai-assistant/template/components/chat-messages.tsx +11 -3
  12. package/components/ai-assistant/template/components/chat-recommends.tsx +12 -14
  13. package/components/ai-assistant/template/components/chat-user-message.tsx +1 -1
  14. package/components/ai-assistant/template/components/ui-renderer.tsx +1 -1
  15. package/components/ai-assistant/template/components/voice-input.tsx +10 -2
  16. package/components/ai-assistant/template/hooks/useConversation.ts +0 -23
  17. package/components/ai-assistant/template/hooks/useVoiceInput.ts +18 -20
  18. package/components/ai-assistant/template/inline-ai-assistant.tsx +1 -1
  19. package/components/ai-assistant-taro/amaster.config.json +3 -0
  20. package/components/ai-assistant-taro/package.json +94 -0
  21. package/components/ai-assistant-taro/template/components/ChatAssistantMessage.tsx +176 -0
  22. package/components/ai-assistant-taro/template/components/ChatHeader.tsx +27 -0
  23. package/components/ai-assistant-taro/template/components/ChatInput.tsx +233 -0
  24. package/components/ai-assistant-taro/template/components/ChatMessages.tsx +126 -0
  25. package/components/ai-assistant-taro/template/components/ChatUserMessage.tsx +25 -0
  26. package/components/ai-assistant-taro/template/components/VoiceInput.tsx +169 -0
  27. package/components/ai-assistant-taro/template/components/markdown.tsx +362 -0
  28. package/components/ai-assistant-taro/template/hooks/useConversation.ts +905 -0
  29. package/components/ai-assistant-taro/template/hooks/useSafeArea.ts +20 -0
  30. package/components/ai-assistant-taro/template/hooks/useVoiceInput.ts +204 -0
  31. package/components/ai-assistant-taro/template/i18n.ts +157 -0
  32. package/components/ai-assistant-taro/template/index.config.ts +10 -0
  33. package/components/ai-assistant-taro/template/index.tsx +83 -0
  34. package/components/ai-assistant-taro/template/types.ts +74 -0
  35. package/package.json +5 -2
  36. package/packages/cli/dist/index.js +14 -3
  37. package/packages/cli/dist/index.js.map +1 -1
  38. package/packages/cli/package.json +1 -1
  39. package/components/ai-assistant/example.md +0 -34
  40. package/components/ai-assistant/others.md +0 -16
package/README.md CHANGED
@@ -137,12 +137,14 @@ npm run create:component
137
137
 
138
138
  1. 在 `components/` 目录下创建新组件目录
139
139
  2. 添加 `package.json` 配置文件
140
- 3. `template/` 目录下放置模板文件
140
+ 3. 添加 `amaster.config.json` 配置文件(配置目标目录)
141
+ 4. 在 `template/` 目录下放置模板文件
141
142
 
142
143
  ```
143
144
  components/
144
145
  └── your-component/
145
- ├── package.json # 组件配置
146
+ ├── package.json # 组件依赖配置
147
+ ├── amaster.config.json # 组件模板配置
146
148
  └── template/ # 模板文件
147
149
  ├── index.ts
148
150
  └── ...
@@ -155,12 +157,6 @@ components/
155
157
  "name": "your-component",
156
158
  "version": "1.0.0",
157
159
  "description": "组件描述",
158
- "template": {
159
- "name": "your-component",
160
- "targetDir": "src/components/your-component",
161
- "files": ["**/*"],
162
- "ignore": ["node_modules", "*.log", ".DS_Store"]
163
- },
164
160
  "dependencies": {
165
161
  "some-lib": "^1.0.0"
166
162
  },
@@ -173,6 +169,14 @@ components/
173
169
  }
174
170
  ```
175
171
 
172
+ ### 组件 amaster.config.json 配置
173
+
174
+ ```json
175
+ {
176
+ "targetDir": "src/components/your-component"
177
+ }
178
+ ```
179
+
176
180
  ## 发布
177
181
 
178
182
  ```bash
@@ -0,0 +1,3 @@
1
+ {
2
+ "targetDir": "src/components/ai-assistant"
3
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "amaster-react-project",
3
- "version": "1.5.0",
3
+ "version": "1.8.0",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "vite --force",
@@ -17,8 +17,8 @@
17
17
  },
18
18
  "dependencies": {
19
19
  "@a2a-js/sdk": "^0.3.7",
20
- "@amaster.ai/client": "1.1.0-beta.56",
21
- "@amaster.ai/vite-plugins": "1.1.0-beta.56",
20
+ "@amaster.ai/client": "1.1.0-beta.61",
21
+ "@amaster.ai/vite-plugins": "1.1.0-beta.61",
22
22
  "@fontsource-variable/inter": "^5.2.8",
23
23
  "@fortawesome/fontawesome-free": "^6.1.1",
24
24
  "@hookform/resolvers": "^5.2.2",
@@ -195,7 +195,7 @@ const ChatAssistantMessage: React.FC<
195
195
  )}
196
196
  >
197
197
  {showAvatar && (
198
- <MessageSquare className="h-3.5 w-3.5 text-white" strokeWidth={2} />
198
+ <MessageSquare className="h-3.5 w-3.5 text-primary-foreground" strokeWidth={2} />
199
199
  )}
200
200
  </div>
201
201
  <MessageContentRenderer message={message} {...rest} />
@@ -8,7 +8,7 @@ const ChatBanner: React.FC<{
8
8
  return null;
9
9
  }
10
10
  return (
11
- <h1 className="text-center text-3xl text-[#000] font-medium p-4">
11
+ <h1 className="text-center text-3xl text-foreground font-medium p-4">
12
12
  {text || getText().greeting}
13
13
  </h1>
14
14
  );
@@ -16,22 +16,22 @@ const ChatDisplayModeSwitcher: React.FC<{
16
16
  }[] = [
17
17
  {
18
18
  mode: "floating",
19
- icon: <Layers2 className="h-4 w-4" strokeWidth={1.75} />,
19
+ icon: <Layers2 className="size-4" strokeWidth={1.75} />,
20
20
  title: getText().displayMode.floating,
21
21
  },
22
22
  {
23
23
  mode: "side-right",
24
- icon: <Sidebar className="h-4 w-4 rotate-180" strokeWidth={1.75} />,
24
+ icon: <Sidebar className="size-4 rotate-180" strokeWidth={1.75} />,
25
25
  title: getText().displayMode.sideRight,
26
26
  },
27
27
  {
28
28
  mode: "side-left",
29
- icon: <Sidebar className="h-4 w-4 " strokeWidth={1.75} />,
29
+ icon: <Sidebar className="size-4 " strokeWidth={1.75} />,
30
30
  title: getText().displayMode.sideLeft,
31
31
  },
32
32
  {
33
33
  mode: "fullscreen",
34
- icon: <Maximize className="h-4 w-4" strokeWidth={1.75} />,
34
+ icon: <Maximize className="size-4" strokeWidth={1.75} />,
35
35
  title: getText().displayMode.fullscreen,
36
36
  },
37
37
  ];
@@ -39,7 +39,7 @@ const ChatDisplayModeSwitcher: React.FC<{
39
39
  return (
40
40
  <HoverCard openDelay={0}>
41
41
  <HoverCardTrigger asChild>
42
- <Button variant="ghost" size="icon" className="hover:bg-transparent text-black/50 hover:text-black">
42
+ <Button variant="ghost" size="icon" className="size-8">
43
43
  {modes.find((m) => m.mode === displayMode)?.icon}
44
44
  </Button>
45
45
  </HoverCardTrigger>
@@ -48,7 +48,7 @@ const ChatDisplayModeSwitcher: React.FC<{
48
48
  <Button
49
49
  key={mode}
50
50
  variant="ghost"
51
- className="justify-start hover:bg-gray-200 text-black/80 hover:text-black data-[state=open]:bg-transparent"
51
+ className="justify-start"
52
52
  onClick={() => onChange(mode)}
53
53
  size="sm"
54
54
  disabled={mode === displayMode}
@@ -10,7 +10,6 @@ interface ChatFloatingButtonProps {
10
10
  style: React.CSSProperties;
11
11
  }
12
12
 
13
-
14
13
  const ChatFloatingButton: React.FC<ChatFloatingButtonProps> = ({
15
14
  onClick,
16
15
  onMouseDown,
@@ -32,14 +31,14 @@ const ChatFloatingButton: React.FC<ChatFloatingButtonProps> = ({
32
31
  className={`
33
32
  group relative h-full w-full rounded-full
34
33
  bg-gradient-to-br from-primary to-primary/40
35
- text-white
34
+ text-primary-foreground
36
35
  border-0
37
36
  transition-all duration-300 ease-out select-none
38
37
  cursor-pointer
39
38
  ${
40
39
  isDragging
41
- ? "shadow-[0_8px_30px_rgba(99,102,241,0.5)] scale-105"
42
- : "shadow-[0_4px_20px_rgba(99,102,241,0.35)] hover:shadow-[0_8px_30px_rgba(99,102,241,0.5)] hover:scale-105"
40
+ ? "shadow-2xl scale-105"
41
+ : "shadow-lg hover:shadow-2xl hover:scale-105"
43
42
  }
44
43
  `}
45
44
  >
@@ -53,4 +52,4 @@ const ChatFloatingButton: React.FC<ChatFloatingButtonProps> = ({
53
52
  );
54
53
  };
55
54
 
56
- export default ChatFloatingButton;
55
+ export default ChatFloatingButton;
@@ -23,14 +23,14 @@ const ChatFloatingCard: React.FC<ChatFloatingCardProps> = ({
23
23
  <Card
24
24
  ref={containerRef}
25
25
  className={cn(
26
- `flex flex-col overflow-hidden bg-[#F9FAFB] border border-[#E5E7EB] z-50`,
26
+ `flex flex-col overflow-hidden bg-card border border-border z-50`,
27
27
  {
28
28
  "fixed inset-0 w-auto h-auto animate-in fade-in-0 zoom-in-[0.98] duration-300 origin-top-left rounded-none": displayMode === "fullscreen",
29
29
  "fixed top-0 right-0 w-[420px] h-full rounded-none": displayMode === "side-right",
30
30
  "fixed top-0 left-0 w-[420px] h-full rounded-none": displayMode === "side-left",
31
31
  "rounded-2xl": displayMode === "floating",
32
- "shadow-[0_25px_50px_-12px_rgba(0,0,0,0.25)]": displayMode === "floating" && isDragging,
33
- "shadow-[0_10px_40px_-10px_rgba(0,0,0,0.1),0_4px_6px_-4px_rgba(0,0,0,0.1)]": displayMode === "floating" && !isDragging,
32
+ "shadow-2xl": displayMode === "floating" && isDragging,
33
+ "shadow-lg": displayMode === "floating" && !isDragging,
34
34
  }
35
35
  )}
36
36
  style={displayMode !== "floating" ? {} : style}
@@ -27,14 +27,14 @@ const ChatHeader: React.FC<ChatHeaderProps> = ({
27
27
  return (
28
28
  <div
29
29
  className={cn(
30
- "flex items-center justify-between px-4",
30
+ "flex items-center justify-between px-4 py-1 pr-2",
31
31
  !disabledDrag && onMouseDown
32
32
  ? isDragging
33
33
  ? "cursor-grabbing"
34
34
  : "cursor-grab"
35
35
  : "",
36
36
  {
37
- 'border-b border-[#E5E7EB] bg-white': displayMode === "floating",
37
+ 'border-b border-border': displayMode === "floating",
38
38
  },
39
39
  )}
40
40
  onMouseDown={!disabledDrag ? onMouseDown : undefined}
@@ -42,7 +42,7 @@ const ChatHeader: React.FC<ChatHeaderProps> = ({
42
42
  >
43
43
  <div className="flex items-center gap-2.5">
44
44
  <div className="flex flex-col">
45
- <span className="text-sm font-semibold text-[#111827]">
45
+ <span className="text-sm font-semibold text-foreground">
46
46
  {getText().assistantName}
47
47
  </span>
48
48
  </div>
@@ -55,9 +55,9 @@ const ChatHeader: React.FC<ChatHeaderProps> = ({
55
55
  variant="ghost"
56
56
  size="icon"
57
57
  onClick={onClose}
58
- className="h-8 w-8 text-[#6B7280] hover:text-[#374151] hover:bg-[#F3F4F6] rounded-lg transition-colors duration-200 cursor-pointer"
58
+ className="size-8"
59
59
  >
60
- <X className="h-4 w-4" strokeWidth={1.75} />
60
+ <X className="size-4" strokeWidth={1.75} />
61
61
  </Button>
62
62
  )}
63
63
  </div>
@@ -9,7 +9,7 @@ import type React from "react";
9
9
  import { Button } from "@/components/ui/button";
10
10
  import { getText } from "../i18n";
11
11
  import type { Conversation, IDisplayMode } from "../types";
12
- import { useMemo, useState } from "react";
12
+ import { useMemo, useRef, useState } from "react";
13
13
  import { cn } from "@/lib/utils";
14
14
  import {
15
15
  Tooltip,
@@ -19,7 +19,10 @@ import {
19
19
  } from "@/components/ui/tooltip";
20
20
  import VoiceInputButton from "./voice-input";
21
21
 
22
- const NewConvButton: React.FC<{ onNew?: () => void; disabled?: boolean }> = ({ onNew, disabled }) => {
22
+ const NewConvButton: React.FC<{ onNew?: () => void; disabled?: boolean }> = ({
23
+ onNew,
24
+ disabled,
25
+ }) => {
23
26
  if (!onNew) return null;
24
27
  return (
25
28
  <TooltipProvider>
@@ -32,7 +35,7 @@ const NewConvButton: React.FC<{ onNew?: () => void; disabled?: boolean }> = ({ o
32
35
  size="icon"
33
36
  onClick={onNew}
34
37
  disabled={disabled}
35
- className="h-8 w-8 text-[#6B7280] hover:text-[#374151] hover:bg-[#F3F4F6] rounded-lg transition-colors duration-200 cursor-pointer"
38
+ className="h-8 w-8 cursor-pointer"
36
39
  >
37
40
  <MessageCirclePlus className="size-5" />
38
41
  </Button>
@@ -49,12 +52,11 @@ const SubmitButton: React.FC<{
49
52
  }> = ({ disabled, starting, onClick }) => {
50
53
  return (
51
54
  <Button
52
- type="button"
53
55
  onClick={onClick}
54
56
  disabled={disabled || starting}
55
57
  size="icon"
56
58
  className={cn(
57
- "size-8 text-white rounded-xl transition-all duration-200 cursor-pointer shadow-[0_2px_8px_rgba(99,102,241,0.3)] hover:shadow-[0_4px_12px_rgba(99,102,241,0.4)]",
59
+ "rounded-xl size-8 text-primary-foreground transition-all duration-200 cursor-pointer shadow-sm hover:shadow-2xl",
58
60
  "bg-gradient-to-r from-primary to-primary/80",
59
61
  "hover:from-primary/90 hover:to-primary/70",
60
62
  "disabled:opacity-40 disabled:cursor-not-allowed",
@@ -76,20 +78,10 @@ const StopButton: React.FC<{ onClick: () => void; disabled?: boolean }> = ({
76
78
  }) => {
77
79
  return (
78
80
  <Button
79
- type="button"
80
81
  onClick={onClick}
81
82
  size="icon"
82
83
  disabled={disabled}
83
- className="
84
- size-8
85
- bg-foreground
86
- hover:bg-foreground/50
87
- text-white
88
- rounded-xl
89
- transition-all duration-200
90
- cursor-pointer
91
- shadow-[0_2px_8px_rgba(239,68,68,0.3)]
92
- hover:shadow-[0_4px_12px_rgba(239,68,68,0.4)]"
84
+ className="size-8 bg-success hover:bg-success/50 text-success-foreground rounded-xl transition-all duration-200 cursor-pointer shadow-sm hover:shadow-2xl"
93
85
  >
94
86
  <Square className="h-3 w-3 fill-current" strokeWidth={2} />
95
87
  </Button>
@@ -117,15 +109,17 @@ const ChatInput: React.FC<ChatInputProps> = ({
117
109
  onNew,
118
110
  onCancel,
119
111
  starting,
120
- displayMode
112
+ displayMode,
121
113
  }) => {
122
114
  const hasConversations = conversations.length > 0;
123
115
  const lastConv =
124
116
  conversations.length > 0 ? conversations[conversations.length - 1] : null;
125
117
  const lastIsDivider = useMemo(() => {
126
118
  if (!lastConv) return false;
127
- return (lastConv.system?.level === "newConversation");
119
+ return lastConv.system?.level === "newConversation";
128
120
  }, [lastConv]);
121
+ const textareaRef = useRef<HTMLTextAreaElement>(null);
122
+ const [speaking, setSpeaking] = useState(false);
129
123
 
130
124
  const [focus, setFocus] = useState(false);
131
125
  const handleKeyPress = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
@@ -135,47 +129,65 @@ const ChatInput: React.FC<ChatInputProps> = ({
135
129
  }
136
130
  };
137
131
 
132
+ const textareaScrollToBottom = () => {
133
+ if (textareaRef.current) {
134
+ textareaRef.current.scrollTop = textareaRef.current.scrollHeight;
135
+ }
136
+ };
137
+
138
138
  return (
139
139
  <div className="py-3 px-4">
140
140
  <div
141
141
  className={cn(
142
- "w-full rounded-xl bg-white transition-all duration-200 p-3 border border-primary/80",
142
+ "w-full rounded-xl bg-card transition-all duration-200 p-3 border border-primary/80",
143
143
  {
144
144
  "border-primary ring-2 ring-primary/20": focus,
145
145
  },
146
146
  )}
147
147
  >
148
148
  <textarea
149
+ ref={textareaRef}
149
150
  value={inputValue}
150
151
  onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
151
152
  onInputChange(e.target.value)
152
153
  }
153
- onKeyPress={isLoading ? undefined : handleKeyPress}
154
+ onKeyPress={isLoading || speaking ? undefined : handleKeyPress}
154
155
  placeholder={getText().typePlaceholder}
155
156
  onFocus={() => setFocus(true)}
156
157
  onBlur={() => setFocus(false)}
157
- className="w-full text-sm text-[#111827] placeholder:text-[#9CA3AF] outline-none resize-none leading-5 disabled:bg-transparent"
158
+ className="w-full text-sm text-foreground placeholder:text-muted-foreground outline-none resize-none leading-5 bg-card"
158
159
  rows={4}
160
+ disabled={speaking}
161
+ readOnly={speaking}
159
162
  />
160
163
  <div className="flex items-center justify-between gap-2 mt-1">
161
164
  <div className="flex items-center gap-1">
162
- {onNew && <NewConvButton onNew={onNew} disabled={!hasConversations || lastIsDivider || starting || isLoading} />}
165
+ {onNew && (
166
+ <NewConvButton
167
+ onNew={onNew}
168
+ disabled={
169
+ !hasConversations || lastIsDivider || starting || isLoading
170
+ }
171
+ />
172
+ )}
163
173
  </div>
164
174
  <div className="flex items-center gap-2">
165
175
  <VoiceInputButton
166
176
  onChange={(text) => {
167
177
  onInputChange(text);
178
+ textareaScrollToBottom();
168
179
  setFocus(true);
169
180
  }}
170
181
  value={inputValue}
171
182
  disabled={isLoading || starting}
183
+ onRunningChange={setSpeaking}
172
184
  />
173
185
  {isLoading ? (
174
186
  <StopButton onClick={onCancel || (() => {})} />
175
187
  ) : (
176
188
  <SubmitButton
177
189
  onClick={() => onSendMessage()}
178
- disabled={!inputValue.trim() || isLoading}
190
+ disabled={!inputValue.trim() || isLoading || speaking}
179
191
  starting={starting}
180
192
  />
181
193
  )}
@@ -184,7 +196,7 @@ const ChatInput: React.FC<ChatInputProps> = ({
184
196
  </div>
185
197
 
186
198
  {(conversations.length > 0 || displayMode !== "inline") && (
187
- <p className="text-[10px] text-[#9CA3AF] mt-2 text-center">
199
+ <p className="text-[10px] text-muted-foreground/50 mt-2 text-center">
188
200
  {getText().footerAiWarning}
189
201
  </p>
190
202
  )}
@@ -71,9 +71,16 @@ const ChatMessages: React.FC<ChatMessagesProps> = ({
71
71
  },
72
72
  className,
73
73
  )}
74
+ style={{
75
+ maskImage:
76
+ "linear-gradient(to bottom, transparent, black 10px, black calc(100% - 10px), transparent)",
77
+ WebkitMaskImage:
78
+ "linear-gradient(to bottom, transparent, black 10px, black calc(100% - 10px), transparent)",
79
+ maskSize: "80% 100%",
80
+ }}
74
81
  data-role="chat-messages"
75
82
  >
76
- {isLoadingHistory ? (
83
+ {isLoadingHistory && convLength > 0 ? (
77
84
  <div
78
85
  key="loading-history"
79
86
  className="flex justify-center items-center gap-2 text-center"
@@ -94,7 +101,8 @@ const ChatMessages: React.FC<ChatMessagesProps> = ({
94
101
  const historyId = conversation.historyId || "";
95
102
  const lastHistoryId = conversations[index - 1]?.historyId || "";
96
103
  let addDivider =
97
- index > 0 && historyId !== lastHistoryId || conversation.system?.level === "newConversation";
104
+ (index > 0 && historyId !== lastHistoryId) ||
105
+ conversation.system?.level === "newConversation";
98
106
 
99
107
  return (
100
108
  <div key={conversation.taskId} className="flex flex-col gap-4">
@@ -148,4 +156,4 @@ const ChatMessages: React.FC<ChatMessagesProps> = ({
148
156
  );
149
157
  };
150
158
 
151
- export default ChatMessages;
159
+ export default ChatMessages;
@@ -1,3 +1,4 @@
1
+ import { Button } from "@/components/ui/button";
1
2
  import { getText } from "../i18n";
2
3
 
3
4
  const ChatRecommends: React.FC<{
@@ -5,31 +6,28 @@ const ChatRecommends: React.FC<{
5
6
  data?: string[];
6
7
  onSend: (prompt: string) => void;
7
8
  disabled?: boolean;
8
- }> = ({ hidden, data = getText().defaultRecommendedQuestions, onSend, disabled }) => {
9
+ }> = ({
10
+ hidden,
11
+ data = getText().defaultRecommendedQuestions,
12
+ onSend,
13
+ disabled,
14
+ }) => {
9
15
  if (hidden || !data || data.length === 0) return null;
10
16
  return (
11
17
  <div className="flex flex-wrap gap-2 pt-2 px-4">
12
18
  {data.map((prompt, index) => (
13
- <button
14
- type="button"
19
+ <Button
15
20
  key={prompt}
21
+ variant="outline"
16
22
  onClick={() => onSend(prompt)}
17
23
  disabled={disabled}
18
24
  className="
19
- text-xs px-2 py-1.5
20
- bg-[#F3F4F6] hover:bg-[#E5E7EB]
21
- text-[#374151] hover:text-[#111827]
22
- rounded-full
23
- border border-[#D1D5DB]
24
- transition-all duration-200
25
- cursor-pointer
26
- animate-in fade-in-0 slide-in-from-bottom-1
27
- text-nowrap
28
- "
25
+ text-xs px-2 py-1 h-auto rounded-full cursor-pointer text-nowrap
26
+ transition-all duration-200 animate-in fade-in-0 slide-in-from-bottom-1"
29
27
  style={{ animationDelay: `${index * 50}ms` }}
30
28
  >
31
29
  {prompt}
32
- </button>
30
+ </Button>
33
31
  ))}
34
32
  </div>
35
33
  );
@@ -12,7 +12,7 @@ const ChatUserMessage: React.FC<{
12
12
  className={`
13
13
  px-4 py-2.5 rounded-2xl
14
14
  transition-all duration-200 min-w-0 overflow-hidden max-w-full
15
- bg-[#E0E7FF] text-[#1E1B4B] rounded-br-md
15
+ bg-primary/10 text-foreground rounded-br-md
16
16
  `}
17
17
  >
18
18
  <div className="text-sm leading-relaxed whitespace-pre-wrap break-words">
@@ -29,7 +29,7 @@ export const UIRenderer: React.FC<UIRendererProps> = ({ spec, className }) => {
29
29
  return (
30
30
  <Suspense
31
31
  fallback={
32
- <Skeleton className={cn("w-full h-[120px] bg-gray-200 flex items-center justify-center gap-2", className)}>
32
+ <Skeleton className={cn("w-full h-[120px] bg-primary/20 flex items-center justify-center gap-2", className)}>
33
33
  <LoaderCircle className="size-4 animate-spin" />
34
34
  <span>{getText().loading}...</span>
35
35
  </Skeleton>
@@ -3,12 +3,14 @@ import { Mic, StopCircle } from "lucide-react";
3
3
  import { Button } from "@/components/ui/button";
4
4
  import { cn } from "@/lib/utils";
5
5
  import { useVoiceInput } from "../hooks/useVoiceInput";
6
+ import { useEffect } from "react";
6
7
 
7
8
  const VoiceInputButton: React.FC<{
8
9
  onChange: (text: string) => void;
9
10
  disabled?: boolean;
10
11
  value?: string;
11
- }> = ({ onChange, disabled, value }) => {
12
+ onRunningChange?: (running: boolean) => void;
13
+ }> = ({ onChange, disabled, value, onRunningChange }) => {
12
14
  const {
13
15
  start,
14
16
  stop,
@@ -23,13 +25,19 @@ const VoiceInputButton: React.FC<{
23
25
  value,
24
26
  });
25
27
 
28
+ useEffect(() => {
29
+ if (onRunningChange) {
30
+ onRunningChange(running);
31
+ }
32
+ }, [running, onRunningChange]);
33
+
26
34
  return (
27
35
  <Button
28
36
  variant="ghost"
29
37
  onClick={stoppable ? stop : status === "idle" ? start : undefined}
30
38
  disabled={finalDisabled}
31
39
  className={cn("h-8 w-8 rounded-lg cursor-pointer text-xs ", {
32
- "bg-gradient-to-r from-blue-500 to-blue-800 hover:from-blue-400 hover:to-blue-600 text-white animate-pulse":
40
+ "bg-gradient-to-r from-primary to-primary/80 hover:from-primary/90 hover:to-primary/70 text-primary-foreground animate-pulse":
33
41
  running,
34
42
  "w-auto": statusText,
35
43
  })}
@@ -391,7 +391,6 @@ export function useConversation() {
391
391
  try {
392
392
  const partData = JSON.parse(json);
393
393
  if (partData.root && partData.elements) {
394
- debugger
395
394
  fillData({
396
395
  kind: 'ui-render',
397
396
  taskId,
@@ -768,28 +767,6 @@ export function useConversation() {
768
767
  const controller = abortControllerRef.current;
769
768
  forceStopRef.current = false;
770
769
 
771
- // mock
772
- if (userContent === "MAGA") {
773
- const { generateMockUIStream } = await import("../mock/mock-data");
774
- const cancelMock = generateMockUIStream(
775
- taskId || generateId(),
776
- (chunk) => {
777
- if (forceStopRef.current) return;
778
- hasResponse = true;
779
- processLiveData(chunk as any);
780
- if (chunk.result.final) {
781
- setIsLoading(false);
782
- }
783
- },
784
- );
785
- abortControllerRef.current?.signal.addEventListener(
786
- "abort",
787
- cancelMock,
788
- );
789
- return;
790
- }
791
-
792
- // real chat
793
770
  try {
794
771
  const stream = client.copilot.chat(
795
772
  userContent ? [{ role: "user", content: userContent }] : [],
@@ -8,10 +8,7 @@ type Status =
8
8
  | "idle"
9
9
  | "starting"
10
10
  | "ready"
11
- | "speaking"
12
- | "recording"
13
11
  | "stopping"
14
- | "ended"
15
12
  | "error"
16
13
  | "closed";
17
14
 
@@ -30,10 +27,10 @@ export const useVoiceInput = ({
30
27
  const AsrClientRef = useRef<ASRClient | null>(null);
31
28
  const runningRef = useRef(false);
32
29
  const stoppable = useMemo(() => {
33
- return ["ready", "speaking", "recording"].includes(status);
30
+ return ["ready"].includes(status);
34
31
  }, [status]);
35
32
  const disabledClick = useMemo(() => {
36
- return ["starting", "ended", "closed", "error", "stopping"].includes(
33
+ return ["starting", "stopping", "closed", "error"].includes(
37
34
  status,
38
35
  );
39
36
  }, [status]);
@@ -53,13 +50,6 @@ export const useVoiceInput = ({
53
50
  onReady() {
54
51
  setStatus("ready");
55
52
  },
56
- onSpeechStart() {
57
- setStatus((status) => (status !== "stopping" ? "recording" : status));
58
- },
59
-
60
- onSpeechEnd() {
61
- setStatus((status) => (status !== "stopping" ? "recording" : status));
62
- },
63
53
  onTranscript(text, isFinal) {
64
54
  if (!isFinal) {
65
55
  temp = text;
@@ -70,10 +60,21 @@ export const useVoiceInput = ({
70
60
  onChange(result);
71
61
  }
72
62
  },
73
- onSessionFinished() {
74
- setStatus("ended");
63
+ onAudioBufferCommitted() {
64
+ console.debug("音频缓冲区已提交");
75
65
  },
76
- onError() {
66
+ onError(error) {
67
+ if (error.message === "NO_DEVICES_AVAILABLE") {
68
+ toast({
69
+ title: getText().voiceInputError.microphoneAccessDenied,
70
+ variant: "destructive",
71
+ });
72
+ } else {
73
+ toast({
74
+ title: getText().voiceInputError.unknownError,
75
+ variant: "destructive",
76
+ });
77
+ }
77
78
  reset();
78
79
  },
79
80
  onClose() {
@@ -89,7 +90,7 @@ export const useVoiceInput = ({
89
90
  AsrClientRef.current = asrClient;
90
91
  } catch (error) {
91
92
  const message = error.message;
92
- const showError = (title: string) => toast({title})
93
+ const showError = (title: string) => toast({ title });
93
94
  if (message.includes("Microphone access denied")) {
94
95
  showError(getText().voiceInputError.microphoneAccessDenied);
95
96
  } else if (message.includes("No speech detected")) {
@@ -147,15 +148,12 @@ export const useVoiceInput = ({
147
148
  }
148
149
  }, [status]);
149
150
 
150
- const statusTextMap = {
151
+ const statusTextMap: Record<Status, string> = {
151
152
  // idle: t('translation:voiceInputStatus.idle'),
152
153
  idle: "",
153
154
  starting: getText().voiceInputStatus.starting,
154
155
  ready: getText().voiceInputStatus.ready,
155
- speaking: getText().voiceInputStatus.speaking,
156
- recording: getText().voiceInputStatus.recording,
157
156
  stopping: getText().voiceInputStatus.stopping,
158
- ended: getText().voiceInputStatus.ended,
159
157
  error: getText().voiceInputStatus.error,
160
158
  closed: getText().voiceInputStatus.closed,
161
159
  };
@@ -45,7 +45,7 @@ const InlineAIAssistant: React.FC<InlineAIAssistantProps> = ({
45
45
  return (
46
46
  <div
47
47
  className={cn(
48
- "w-full h-full max-h-screen relative flex flex-col justify-center overflow-hidden max-w-[1000px] mx-auto",
48
+ "w-full h-full max-h-screen relative flex flex-col justify-center overflow-hidden max-w-6xl mx-auto",
49
49
  className,
50
50
  )}
51
51
  style={style}
@@ -0,0 +1,3 @@
1
+ {
2
+ "targetDir": "src/pages/ai-assistant"
3
+ }