@amaster.ai/components-templates 1.4.1 → 1.4.3
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/components/ai-assistant/others.md +13 -3
- package/components/ai-assistant/package.json +112 -30
- package/components/ai-assistant/template/ai-assistant.tsx +12 -3
- package/components/ai-assistant/template/components/chat-assistant-message.tsx +25 -9
- package/components/ai-assistant/template/components/chat-display-mode-switcher.tsx +1 -6
- package/components/ai-assistant/template/components/chat-floating-button.tsx +2 -2
- package/components/ai-assistant/template/components/chat-floating-card.tsx +1 -1
- package/components/ai-assistant/template/components/chat-header.tsx +16 -14
- package/components/ai-assistant/template/components/chat-input.tsx +80 -28
- package/components/ai-assistant/template/components/chat-messages.tsx +75 -5
- package/components/ai-assistant/template/components/chat-recommends.tsx +3 -1
- package/components/ai-assistant/template/components/chat-speech-button.tsx +25 -7
- package/components/ai-assistant/template/components/ui-renderer.tsx +10 -1
- package/components/ai-assistant/template/hooks/useAssistantStore.tsx +35 -6
- package/components/ai-assistant/template/hooks/useConversation.ts +898 -0
- package/components/ai-assistant/template/hooks/useDisplayMode.tsx +1 -1
- package/components/ai-assistant/template/hooks/useSpeak.ts +89 -6
- package/components/ai-assistant/template/hooks/useVoiceInput.ts +1 -1
- package/components/ai-assistant/template/i18n.ts +21 -3
- package/components/ai-assistant/template/inline-ai-assistant.tsx +21 -9
- package/components/ai-assistant/template/types.ts +16 -0
- package/package.json +4 -2
- package/packages/cli/dist/index.js +26 -3
- package/packages/cli/dist/index.js.map +1 -1
- package/packages/cli/package.json +1 -1
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
## tailwind.config.js
|
|
2
2
|
|
|
3
|
-
content 添加
|
|
3
|
+
content 添加 "./node_modules/@json-render/shadcn/dist/**/*.js"
|
|
4
|
+
```
|
|
5
|
+
{
|
|
6
|
+
content: [
|
|
7
|
+
"./node_modules/@json-render/shadcn/dist/**/*.js"
|
|
8
|
+
]
|
|
9
|
+
}
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## src/lib/client 保证是新的
|
|
13
|
+
```
|
|
14
|
+
import { createClient } from "@amaster.ai/client";
|
|
15
|
+
export const client = createClient({});
|
|
4
16
|
```
|
|
5
|
-
"./node_modules/@json-render/shadcn/dist/**/*.js"
|
|
6
|
-
```
|
|
@@ -1,37 +1,119 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "
|
|
3
|
-
"version": "1.4.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
}
|
|
2
|
+
"name": "amaster-react-project",
|
|
3
|
+
"version": "1.4.3",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "vite --force",
|
|
7
|
+
"dev:local": "vite --config vite.config.local.ts",
|
|
8
|
+
"dev:restart": "bun scripts/dev-restart.mjs",
|
|
9
|
+
"build": "vite build",
|
|
10
|
+
"lint": "biome check .",
|
|
11
|
+
"lint:fix": "biome check --write .",
|
|
12
|
+
"type-check": "bun scripts/type-check-filter.mjs",
|
|
13
|
+
"check:build": "bun scripts/check-build.mjs",
|
|
14
|
+
"check:ast": "bun scripts/ast-grep-scan.mjs",
|
|
15
|
+
"preview": "vite preview",
|
|
16
|
+
"prepare": "bun scripts/prepare.mjs || true"
|
|
27
17
|
},
|
|
28
18
|
"dependencies": {
|
|
29
|
-
"@
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
19
|
+
"@a2a-js/sdk": "^0.3.7",
|
|
20
|
+
"@amaster.ai/client": "1.1.0-beta.55",
|
|
21
|
+
"@amaster.ai/vite-plugins": "1.1.0-beta.55",
|
|
22
|
+
"@fontsource-variable/inter": "^5.2.8",
|
|
23
|
+
"@fortawesome/fontawesome-free": "^6.1.1",
|
|
24
|
+
"@hookform/resolvers": "^5.2.2",
|
|
33
25
|
"@json-render/core": "^0.12.0",
|
|
34
26
|
"@json-render/react": "^0.12.0",
|
|
35
|
-
"@json-render/shadcn": "^0.12.0"
|
|
27
|
+
"@json-render/shadcn": "^0.12.0",
|
|
28
|
+
"@radix-ui/react-accordion": "^1.2.8",
|
|
29
|
+
"@radix-ui/react-alert-dialog": "^1.1.11",
|
|
30
|
+
"@radix-ui/react-aspect-ratio": "^1.1.4",
|
|
31
|
+
"@radix-ui/react-avatar": "^1.1.7",
|
|
32
|
+
"@radix-ui/react-checkbox": "^1.2.3",
|
|
33
|
+
"@radix-ui/react-collapsible": "^1.1.8",
|
|
34
|
+
"@radix-ui/react-context-menu": "^2.2.15",
|
|
35
|
+
"@radix-ui/react-dialog": "^1.1.11",
|
|
36
|
+
"@radix-ui/react-dropdown-menu": "^2.1.12",
|
|
37
|
+
"@radix-ui/react-hover-card": "^1.1.14",
|
|
38
|
+
"@radix-ui/react-icons": "^1.3.2",
|
|
39
|
+
"@radix-ui/react-label": "^2.1.4",
|
|
40
|
+
"@radix-ui/react-menubar": "^1.1.12",
|
|
41
|
+
"@radix-ui/react-navigation-menu": "^1.2.10",
|
|
42
|
+
"@radix-ui/react-popover": "^1.1.7",
|
|
43
|
+
"@radix-ui/react-progress": "^1.1.3",
|
|
44
|
+
"@radix-ui/react-radio-group": "^1.3.4",
|
|
45
|
+
"@radix-ui/react-scroll-area": "^1.2.6",
|
|
46
|
+
"@radix-ui/react-select": "^2.1.7",
|
|
47
|
+
"@radix-ui/react-separator": "^1.1.4",
|
|
48
|
+
"@radix-ui/react-slider": "^1.3.2",
|
|
49
|
+
"@radix-ui/react-slot": "^1.2.0",
|
|
50
|
+
"@radix-ui/react-switch": "^1.2.2",
|
|
51
|
+
"@radix-ui/react-tabs": "^1.1.9",
|
|
52
|
+
"@radix-ui/react-toast": "^1.1.5",
|
|
53
|
+
"@radix-ui/react-toggle": "^1.1.6",
|
|
54
|
+
"@radix-ui/react-toggle-group": "^1.1.7",
|
|
55
|
+
"@radix-ui/react-tooltip": "^1.2.4",
|
|
56
|
+
"@tanstack/react-table": "^8.21.3",
|
|
57
|
+
"bpmn-auto-layout": "^1.2.0",
|
|
58
|
+
"bpmn-js": "18.1.1",
|
|
59
|
+
"class-variance-authority": "^0.7.1",
|
|
60
|
+
"clsx": "^2.1.1",
|
|
61
|
+
"cmdk": "^1.1.1",
|
|
62
|
+
"date-fns": "^3.6.0",
|
|
63
|
+
"embla-carousel-react": "^8.6.0",
|
|
64
|
+
"eventsource-parser": "^3.0.6",
|
|
65
|
+
"immer": "^11.1.4",
|
|
66
|
+
"input-otp": "^1.4.2",
|
|
67
|
+
"ky": "^1.9.1",
|
|
68
|
+
"lucide-react": "^0.544.0",
|
|
69
|
+
"next-themes": "^0.4.6",
|
|
70
|
+
"qrcode": "^1.5.4",
|
|
71
|
+
"qs": "6.9.7",
|
|
72
|
+
"react": "^18.3.1",
|
|
73
|
+
"react-day-picker": "^8.10.1",
|
|
74
|
+
"react-dom": "^18.3.1",
|
|
75
|
+
"react-dropzone": "^14.3.8",
|
|
76
|
+
"react-helmet-async": "^2.0.5",
|
|
77
|
+
"react-hook-form": "^7.71.1",
|
|
78
|
+
"react-markdown": "^10.1.0",
|
|
79
|
+
"react-resizable-panels": "^2.1.8",
|
|
80
|
+
"react-router": "^7.13.0",
|
|
81
|
+
"react-router-dom": "^6.30.1",
|
|
82
|
+
"recharts": "^2.15.3",
|
|
83
|
+
"remark-gfm": "^4.0.1",
|
|
84
|
+
"sonner": "^2.0.3",
|
|
85
|
+
"streamdown": "^1.1.6",
|
|
86
|
+
"tailwind-merge": "^3.2.0",
|
|
87
|
+
"tailwindcss-animate": "^1.0.7",
|
|
88
|
+
"vaul": "^1.1.2",
|
|
89
|
+
"video-react": "^0.16.0",
|
|
90
|
+
"xss": "^1.0.15",
|
|
91
|
+
"zod": "^3.24.3"
|
|
92
|
+
},
|
|
93
|
+
"devDependencies": {
|
|
94
|
+
"@ast-grep/napi": "^0.40.5",
|
|
95
|
+
"@ast-grep/napi-linux-arm64-musl": "^0.40.5",
|
|
96
|
+
"@ast-grep/napi-linux-x64-musl": "^0.40.5",
|
|
97
|
+
"@biomejs/biome": "^2.3.4",
|
|
98
|
+
"@types/glob": "^9.0.0",
|
|
99
|
+
"@types/http-proxy": "^1.17.15",
|
|
100
|
+
"@types/lodash": "^4.17.16",
|
|
101
|
+
"@types/node": "^22.10.2",
|
|
102
|
+
"@types/qs": "^6.14.0",
|
|
103
|
+
"@types/react": "^18.3.23",
|
|
104
|
+
"@types/react-dom": "^18.3.7",
|
|
105
|
+
"@types/video-react": "^0.15.8",
|
|
106
|
+
"@typescript/native-preview": "7.0.0-dev.20250819.1",
|
|
107
|
+
"@vitejs/plugin-react-swc": "^4.2.2",
|
|
108
|
+
"autoprefixer": "^10.4.20",
|
|
109
|
+
"glob": "^11.0.0",
|
|
110
|
+
"http-proxy": "^1.18.1",
|
|
111
|
+
"husky": "^9.1.7",
|
|
112
|
+
"postcss": "^8.5.2",
|
|
113
|
+
"prettier": "^3.8.1",
|
|
114
|
+
"tailwindcss": "^3.4.11",
|
|
115
|
+
"typescript": "~5.7.2",
|
|
116
|
+
"vite": "^5.4.21",
|
|
117
|
+
"vite-plugin-svgr": "^4.3.0"
|
|
36
118
|
}
|
|
37
119
|
}
|
|
@@ -10,8 +10,10 @@ import ChatDisplayModeSwitcher from "./components/chat-display-mode-switcher";
|
|
|
10
10
|
import { useDisplayMode } from "./hooks/useDisplayMode";
|
|
11
11
|
import InlineAIAssistant from "./inline-ai-assistant";
|
|
12
12
|
|
|
13
|
-
const AIAssistant: React.FC
|
|
14
|
-
|
|
13
|
+
const AIAssistant: React.FC<{
|
|
14
|
+
greeting?: string;
|
|
15
|
+
recommends?: string[];
|
|
16
|
+
}> = ({ greeting, recommends }) => {
|
|
15
17
|
const [isOpen, setIsOpen] = useState(false);
|
|
16
18
|
const [displayMode, setDisplayMode] = useDisplayMode(isOpen);
|
|
17
19
|
const isFullscreen = displayMode === "fullscreen" && isOpen;
|
|
@@ -67,6 +69,7 @@ const AIAssistant: React.FC = () => {
|
|
|
67
69
|
<ChatHeader
|
|
68
70
|
disabledDrag={displayMode !== "floating"}
|
|
69
71
|
isDragging={isDragging}
|
|
72
|
+
displayMode={displayMode}
|
|
70
73
|
onMouseDown={handleDragStart}
|
|
71
74
|
onTouchStart={handleTouchStart}
|
|
72
75
|
onClose={handleClose}
|
|
@@ -77,7 +80,13 @@ const AIAssistant: React.FC = () => {
|
|
|
77
80
|
/>
|
|
78
81
|
</ChatHeader>
|
|
79
82
|
|
|
80
|
-
<InlineAIAssistant
|
|
83
|
+
<InlineAIAssistant
|
|
84
|
+
showBanner={false}
|
|
85
|
+
className="flex-1"
|
|
86
|
+
greeting={greeting || getText().greeting}
|
|
87
|
+
recommends={recommends}
|
|
88
|
+
displayMode={displayMode}
|
|
89
|
+
/>
|
|
81
90
|
</ChatFloatingCard>
|
|
82
91
|
)}
|
|
83
92
|
</>
|
|
@@ -30,14 +30,14 @@ export const ChatLoading: React.FC<{ className?: string }> = ({
|
|
|
30
30
|
className,
|
|
31
31
|
}) => {
|
|
32
32
|
return (
|
|
33
|
-
<div className={`inline-flex items-center gap-1`}>
|
|
34
|
-
<div className="h-2 w-2 rounded-full bg-
|
|
33
|
+
<div className={`inline-flex items-center gap-1 animate-in fade-in-0 slide-in-from-bottom-2 duration-300 ${className}`}>
|
|
34
|
+
<div className="h-2 w-2 rounded-full bg-primary animate-pulse" />
|
|
35
35
|
<div
|
|
36
|
-
className="h-2 w-2 rounded-full bg-
|
|
36
|
+
className="h-2 w-2 rounded-full bg-primary animate-pulse"
|
|
37
37
|
style={{ animationDelay: "150ms" }}
|
|
38
38
|
/>
|
|
39
39
|
<div
|
|
40
|
-
className="h-2 w-2 rounded-full bg-
|
|
40
|
+
className="h-2 w-2 rounded-full bg-primary animate-pulse"
|
|
41
41
|
style={{ animationDelay: "300ms" }}
|
|
42
42
|
/>
|
|
43
43
|
</div>
|
|
@@ -58,7 +58,6 @@ const ChatTextMessage: React.FC<
|
|
|
58
58
|
</div>
|
|
59
59
|
<TTSReader
|
|
60
60
|
text={message.content}
|
|
61
|
-
className="opacity-0 group-hover:opacity-100"
|
|
62
61
|
/>
|
|
63
62
|
</>
|
|
64
63
|
);
|
|
@@ -116,7 +115,6 @@ const ChatToolMessage: React.FC<
|
|
|
116
115
|
<span className="flex-1 truncate flex-shrink-0">
|
|
117
116
|
{message.toolName || getText().unknownTool}
|
|
118
117
|
</span>
|
|
119
|
-
{/* {message.toolDescription && <div className="flex-1 truncate max-w-1/2">{message.toolDescription}</div>} */}
|
|
120
118
|
</div>
|
|
121
119
|
);
|
|
122
120
|
};
|
|
@@ -129,11 +127,25 @@ const ChatErrorMessage: React.FC<{ message: TextMessage }> = ({ message }) => {
|
|
|
129
127
|
);
|
|
130
128
|
};
|
|
131
129
|
|
|
130
|
+
export const ChatDivider = () => {
|
|
131
|
+
return (
|
|
132
|
+
<div className="flex items-center justify-center gap-4 w-full my-4">
|
|
133
|
+
<div className="h-px bg-border flex-1" />
|
|
134
|
+
<span className="text-xs text-muted-foreground">
|
|
135
|
+
{getText().newConversation}
|
|
136
|
+
</span>
|
|
137
|
+
<div className="h-px bg-border flex-1" />
|
|
138
|
+
</div>
|
|
139
|
+
);
|
|
140
|
+
};
|
|
141
|
+
|
|
132
142
|
const ChatUIRenderMessage: React.FC<{ message: UIRenderMessage }> = ({
|
|
133
143
|
message,
|
|
134
144
|
}) => {
|
|
135
145
|
return (
|
|
136
|
-
<
|
|
146
|
+
<div className="flex-1 overflow-hidden">
|
|
147
|
+
<UIRenderer spec={message.spec} className="mb-2" />
|
|
148
|
+
</div>
|
|
137
149
|
);
|
|
138
150
|
};
|
|
139
151
|
|
|
@@ -154,7 +166,9 @@ const MessageContentRenderer: React.FC<
|
|
|
154
166
|
case "error":
|
|
155
167
|
return <ChatErrorMessage message={message as TextMessage} {...rest} />;
|
|
156
168
|
case "ui-render":
|
|
157
|
-
return
|
|
169
|
+
return (
|
|
170
|
+
<ChatUIRenderMessage message={message as UIRenderMessage} {...rest} />
|
|
171
|
+
);
|
|
158
172
|
default:
|
|
159
173
|
return <div>{getText().unknownTool}</div>;
|
|
160
174
|
}
|
|
@@ -166,15 +180,17 @@ const ChatAssistantMessage: React.FC<
|
|
|
166
180
|
showAvatar?: boolean;
|
|
167
181
|
} & MessageCommonProps
|
|
168
182
|
> = ({ message, showAvatar, ...rest }) => {
|
|
183
|
+
|
|
169
184
|
return (
|
|
170
185
|
<div
|
|
186
|
+
data-conversation-message-id={(message as any).messageId}
|
|
171
187
|
className={cn(
|
|
172
188
|
"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
189
|
)}
|
|
174
190
|
>
|
|
175
191
|
<div
|
|
176
192
|
className={cn(
|
|
177
|
-
"flex-shrink-0 h-7 w-7 rounded-full bg-gradient-to-br from-
|
|
193
|
+
"flex-shrink-0 h-7 w-7 rounded-full bg-gradient-to-br from-primary to-primary/40 flex items-center justify-center text-left",
|
|
178
194
|
!showAvatar && "invisible",
|
|
179
195
|
)}
|
|
180
196
|
>
|
|
@@ -3,12 +3,7 @@ import { getText } from "../i18n";
|
|
|
3
3
|
import { HoverCard } from "@radix-ui/react-hover-card";
|
|
4
4
|
import { HoverCardContent, HoverCardTrigger } from "@/components/ui/hover-card";
|
|
5
5
|
import { Button } from "@/components/ui/button";
|
|
6
|
-
|
|
7
|
-
export type IDisplayMode =
|
|
8
|
-
| "fullscreen"
|
|
9
|
-
| "floating"
|
|
10
|
-
| "side-left"
|
|
11
|
-
| "side-right";
|
|
6
|
+
import type { IDisplayMode } from "../types";
|
|
12
7
|
|
|
13
8
|
const ChatDisplayModeSwitcher: React.FC<{
|
|
14
9
|
displayMode: IDisplayMode;
|
|
@@ -31,7 +31,7 @@ const ChatFloatingButton: React.FC<ChatFloatingButtonProps> = ({
|
|
|
31
31
|
size="lg"
|
|
32
32
|
className={`
|
|
33
33
|
group relative h-full w-full rounded-full
|
|
34
|
-
bg-gradient-to-br from-
|
|
34
|
+
bg-gradient-to-br from-primary to-primary/40
|
|
35
35
|
text-white
|
|
36
36
|
border-0
|
|
37
37
|
transition-all duration-300 ease-out select-none
|
|
@@ -43,7 +43,7 @@ const ChatFloatingButton: React.FC<ChatFloatingButtonProps> = ({
|
|
|
43
43
|
}
|
|
44
44
|
`}
|
|
45
45
|
>
|
|
46
|
-
<span className="absolute inset-0 rounded-full bg-
|
|
46
|
+
<span className="absolute inset-0 rounded-full bg-primary animate-ping opacity-20" />
|
|
47
47
|
<MessageSquare
|
|
48
48
|
className="relative h-5 w-5 pointer-events-none"
|
|
49
49
|
strokeWidth={1.75}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type React from "react";
|
|
2
|
+
import type { IDisplayMode } from "../types";
|
|
2
3
|
import { useRef } from "react";
|
|
3
4
|
import { Card } from "@/components/ui/card";
|
|
4
5
|
import { cn } from "@/lib/utils";
|
|
5
|
-
import { IDisplayMode } from "./chat-display-mode-switcher";
|
|
6
6
|
|
|
7
7
|
interface ChatFloatingCardProps {
|
|
8
8
|
displayMode: IDisplayMode;
|
|
@@ -2,8 +2,11 @@ import { MessageSquare, X } from "lucide-react";
|
|
|
2
2
|
import type React from "react";
|
|
3
3
|
import { Button } from "@/components/ui/button";
|
|
4
4
|
import { getText } from "../i18n";
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
import type { IDisplayMode } from "../types";
|
|
5
7
|
|
|
6
8
|
interface ChatHeaderProps {
|
|
9
|
+
displayMode?: IDisplayMode;
|
|
7
10
|
disabledDrag?: boolean;
|
|
8
11
|
isDragging?: boolean;
|
|
9
12
|
onMouseDown?: (e: React.MouseEvent) => void;
|
|
@@ -13,6 +16,7 @@ interface ChatHeaderProps {
|
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
const ChatHeader: React.FC<ChatHeaderProps> = ({
|
|
19
|
+
displayMode,
|
|
16
20
|
disabledDrag,
|
|
17
21
|
isDragging,
|
|
18
22
|
onMouseDown,
|
|
@@ -22,27 +26,25 @@ const ChatHeader: React.FC<ChatHeaderProps> = ({
|
|
|
22
26
|
}) => {
|
|
23
27
|
return (
|
|
24
28
|
<div
|
|
25
|
-
className={
|
|
26
|
-
flex items-center justify-between px-4
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
className={cn(
|
|
30
|
+
"flex items-center justify-between px-4",
|
|
31
|
+
!disabledDrag && onMouseDown
|
|
32
|
+
? isDragging
|
|
33
|
+
? "cursor-grabbing"
|
|
34
|
+
: "cursor-grab"
|
|
35
|
+
: "",
|
|
36
|
+
{
|
|
37
|
+
'border-b border-[#E5E7EB] bg-white': displayMode === "floating",
|
|
38
|
+
},
|
|
39
|
+
)}
|
|
31
40
|
onMouseDown={!disabledDrag ? onMouseDown : undefined}
|
|
32
41
|
onTouchStart={!disabledDrag ? onTouchStart : undefined}
|
|
33
42
|
>
|
|
34
43
|
<div className="flex items-center gap-2.5">
|
|
35
|
-
<div className="flex items-center justify-center h-8 w-8 rounded-full bg-gradient-to-br from-[#6366F1] to-[#8B5CF6]">
|
|
36
|
-
<MessageSquare className="h-4 w-4 text-white" strokeWidth={2} />
|
|
37
|
-
</div>
|
|
38
44
|
<div className="flex flex-col">
|
|
39
45
|
<span className="text-sm font-semibold text-[#111827]">
|
|
40
|
-
|
|
46
|
+
{getText().assistantName}
|
|
41
47
|
</span>
|
|
42
|
-
<div className="flex items-center gap-1.5">
|
|
43
|
-
<span className="h-1.5 w-1.5 rounded-full bg-[#10B981] animate-pulse" />
|
|
44
|
-
<span className="text-xs text-[#6B7280]">{getText().online}</span>
|
|
45
|
-
</div>
|
|
46
48
|
</div>
|
|
47
49
|
</div>
|
|
48
50
|
<div className="flex items-center gap-1">
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
ArrowUp,
|
|
3
|
+
LoaderCircle,
|
|
4
|
+
MessageCirclePlus,
|
|
5
|
+
RotateCw,
|
|
6
|
+
Square,
|
|
7
|
+
} from "lucide-react";
|
|
2
8
|
import type React from "react";
|
|
3
9
|
import { Button } from "@/components/ui/button";
|
|
4
10
|
import { getText } from "../i18n";
|
|
5
|
-
import type { Conversation } from "../types";
|
|
6
|
-
import { useState } from "react";
|
|
11
|
+
import type { Conversation, IDisplayMode } from "../types";
|
|
12
|
+
import { useMemo, useState } from "react";
|
|
7
13
|
import { cn } from "@/lib/utils";
|
|
8
14
|
import {
|
|
9
15
|
Tooltip,
|
|
@@ -13,18 +19,19 @@ import {
|
|
|
13
19
|
} from "@/components/ui/tooltip";
|
|
14
20
|
import VoiceInputButton from "./voice-input";
|
|
15
21
|
|
|
16
|
-
const
|
|
17
|
-
if (!
|
|
22
|
+
const NewConvButton: React.FC<{ onNew?: () => void; disabled?: boolean }> = ({ onNew, disabled }) => {
|
|
23
|
+
if (!onNew) return null;
|
|
18
24
|
return (
|
|
19
25
|
<TooltipProvider>
|
|
20
26
|
<Tooltip delayDuration={0}>
|
|
21
|
-
<TooltipContent>{getText().
|
|
27
|
+
<TooltipContent>{getText().newConversation}</TooltipContent>
|
|
22
28
|
<TooltipTrigger asChild>
|
|
23
29
|
<Button
|
|
24
30
|
type="button"
|
|
25
31
|
variant="ghost"
|
|
26
32
|
size="icon"
|
|
27
|
-
onClick={
|
|
33
|
+
onClick={onNew}
|
|
34
|
+
disabled={disabled}
|
|
28
35
|
className="h-8 w-8 text-[#6B7280] hover:text-[#374151] hover:bg-[#F3F4F6] rounded-lg transition-colors duration-200 cursor-pointer"
|
|
29
36
|
>
|
|
30
37
|
<MessageCirclePlus className="size-5" />
|
|
@@ -35,29 +42,56 @@ const ResetButton: React.FC<{ onReset?: () => void }> = ({ onReset }) => {
|
|
|
35
42
|
);
|
|
36
43
|
};
|
|
37
44
|
|
|
38
|
-
const SubmitButton: React.FC<{
|
|
39
|
-
disabled
|
|
45
|
+
const SubmitButton: React.FC<{
|
|
46
|
+
disabled: boolean;
|
|
47
|
+
starting?: boolean;
|
|
48
|
+
onClick: () => void;
|
|
49
|
+
}> = ({ disabled, starting, onClick }) => {
|
|
50
|
+
return (
|
|
51
|
+
<Button
|
|
52
|
+
type="button"
|
|
53
|
+
onClick={onClick}
|
|
54
|
+
disabled={disabled || starting}
|
|
55
|
+
size="icon"
|
|
56
|
+
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)]",
|
|
58
|
+
"bg-gradient-to-r from-primary to-primary/80",
|
|
59
|
+
"hover:from-primary/90 hover:to-primary/70",
|
|
60
|
+
"disabled:opacity-40 disabled:cursor-not-allowed",
|
|
61
|
+
starting && "bg-gray-600",
|
|
62
|
+
)}
|
|
63
|
+
>
|
|
64
|
+
{starting ? (
|
|
65
|
+
<LoaderCircle className="h-4 w-4 animate-spin" strokeWidth={2} />
|
|
66
|
+
) : (
|
|
67
|
+
<ArrowUp className="h-4 w-4" strokeWidth={2} />
|
|
68
|
+
)}
|
|
69
|
+
</Button>
|
|
70
|
+
);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const StopButton: React.FC<{ onClick: () => void; disabled?: boolean }> = ({
|
|
40
74
|
onClick,
|
|
75
|
+
disabled,
|
|
41
76
|
}) => {
|
|
42
77
|
return (
|
|
43
78
|
<Button
|
|
44
79
|
type="button"
|
|
45
80
|
onClick={onClick}
|
|
46
|
-
disabled={disabled}
|
|
47
81
|
size="icon"
|
|
82
|
+
disabled={disabled}
|
|
48
83
|
className="
|
|
49
84
|
size-8
|
|
50
|
-
bg-
|
|
51
|
-
hover:
|
|
85
|
+
bg-foreground
|
|
86
|
+
hover:bg-foreground/50
|
|
52
87
|
text-white
|
|
53
88
|
rounded-xl
|
|
54
89
|
transition-all duration-200
|
|
55
90
|
cursor-pointer
|
|
56
|
-
|
|
57
|
-
shadow-[
|
|
58
|
-
hover:shadow-[0_4px_12px_rgba(99,102,241,0.4)]"
|
|
91
|
+
shadow-[0_2px_8px_rgba(239,68,68,0.3)]
|
|
92
|
+
hover:shadow-[0_4px_12px_rgba(239,68,68,0.4)]"
|
|
59
93
|
>
|
|
60
|
-
<
|
|
94
|
+
<Square className="h-3 w-3 fill-current" strokeWidth={2} />
|
|
61
95
|
</Button>
|
|
62
96
|
);
|
|
63
97
|
};
|
|
@@ -68,7 +102,10 @@ interface ChatInputProps {
|
|
|
68
102
|
inputValue: string;
|
|
69
103
|
onInputChange: (value: string) => void;
|
|
70
104
|
onSendMessage: () => void;
|
|
71
|
-
|
|
105
|
+
onNew?: () => void;
|
|
106
|
+
onCancel?: () => void;
|
|
107
|
+
starting?: boolean;
|
|
108
|
+
displayMode?: IDisplayMode;
|
|
72
109
|
}
|
|
73
110
|
|
|
74
111
|
const ChatInput: React.FC<ChatInputProps> = ({
|
|
@@ -77,9 +114,19 @@ const ChatInput: React.FC<ChatInputProps> = ({
|
|
|
77
114
|
inputValue,
|
|
78
115
|
onInputChange,
|
|
79
116
|
onSendMessage,
|
|
80
|
-
|
|
117
|
+
onNew,
|
|
118
|
+
onCancel,
|
|
119
|
+
starting,
|
|
120
|
+
displayMode
|
|
81
121
|
}) => {
|
|
82
|
-
const hasConversations = conversations.length >
|
|
122
|
+
const hasConversations = conversations.length > 0;
|
|
123
|
+
const lastConv =
|
|
124
|
+
conversations.length > 0 ? conversations[conversations.length - 1] : null;
|
|
125
|
+
const lastIsDivider = useMemo(() => {
|
|
126
|
+
if (!lastConv) return false;
|
|
127
|
+
return (lastConv.system?.level === "newConversation");
|
|
128
|
+
}, [lastConv]);
|
|
129
|
+
|
|
83
130
|
const [focus, setFocus] = useState(false);
|
|
84
131
|
const handleKeyPress = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
|
85
132
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
@@ -92,9 +139,9 @@ const ChatInput: React.FC<ChatInputProps> = ({
|
|
|
92
139
|
<div className="py-3 px-4">
|
|
93
140
|
<div
|
|
94
141
|
className={cn(
|
|
95
|
-
"w-full rounded-xl bg-white transition-all duration-200 p-3 border border-
|
|
142
|
+
"w-full rounded-xl bg-white transition-all duration-200 p-3 border border-primary/80",
|
|
96
143
|
{
|
|
97
|
-
"border-
|
|
144
|
+
"border-primary ring-2 ring-primary/20": focus,
|
|
98
145
|
},
|
|
99
146
|
)}
|
|
100
147
|
>
|
|
@@ -112,7 +159,7 @@ const ChatInput: React.FC<ChatInputProps> = ({
|
|
|
112
159
|
/>
|
|
113
160
|
<div className="flex items-center justify-between gap-2 mt-1">
|
|
114
161
|
<div className="flex items-center gap-1">
|
|
115
|
-
<
|
|
162
|
+
{onNew && <NewConvButton onNew={onNew} disabled={!hasConversations || lastIsDivider || starting || isLoading} />}
|
|
116
163
|
</div>
|
|
117
164
|
<div className="flex items-center gap-2">
|
|
118
165
|
<VoiceInputButton
|
|
@@ -121,17 +168,22 @@ const ChatInput: React.FC<ChatInputProps> = ({
|
|
|
121
168
|
setFocus(true);
|
|
122
169
|
}}
|
|
123
170
|
value={inputValue}
|
|
124
|
-
disabled={isLoading}
|
|
125
|
-
/>
|
|
126
|
-
<SubmitButton
|
|
127
|
-
onClick={() => onSendMessage()}
|
|
128
|
-
disabled={!inputValue.trim() || isLoading}
|
|
171
|
+
disabled={isLoading || starting}
|
|
129
172
|
/>
|
|
173
|
+
{isLoading ? (
|
|
174
|
+
<StopButton onClick={onCancel || (() => {})} />
|
|
175
|
+
) : (
|
|
176
|
+
<SubmitButton
|
|
177
|
+
onClick={() => onSendMessage()}
|
|
178
|
+
disabled={!inputValue.trim() || isLoading}
|
|
179
|
+
starting={starting}
|
|
180
|
+
/>
|
|
181
|
+
)}
|
|
130
182
|
</div>
|
|
131
183
|
</div>
|
|
132
184
|
</div>
|
|
133
185
|
|
|
134
|
-
{conversations.length > 0 && (
|
|
186
|
+
{(conversations.length > 0 || displayMode !== "inline") && (
|
|
135
187
|
<p className="text-[10px] text-[#9CA3AF] mt-2 text-center">
|
|
136
188
|
{getText().footerAiWarning}
|
|
137
189
|
</p>
|