vzcode 1.33.0 → 1.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/index-ajiBaTmx.js +263 -0
- package/dist/assets/{index-DnunmWjy.css → index-lvLw6dCW.css} +1 -1
- package/dist/index.html +2 -2
- package/package.json +7 -2
- package/src/client/VZSidebar/AIChat/ChatInput.tsx +65 -0
- package/src/client/VZSidebar/AIChat/Message.tsx +36 -0
- package/src/client/VZSidebar/AIChat/MessageList.tsx +62 -0
- package/src/client/VZSidebar/AIChat/StreamingMessage.tsx +25 -0
- package/src/client/VZSidebar/AIChat/TypingIndicator.tsx +13 -0
- package/src/client/VZSidebar/AIChat/index.tsx +75 -0
- package/src/client/VZSidebar/AIChat/styles.scss +256 -0
- package/src/client/VZSidebar/index.tsx +1 -1
- package/src/client/VZSidebar/styles.scss +0 -170
- package/src/client/diff.js +1 -1
- package/src/client/featureFlags.ts +1 -1
- package/src/ot.js +10 -2
- package/src/server/aiChatHandler/aiEditing.js +42 -0
- package/src/server/aiChatHandler/chatOperations.js +195 -0
- package/src/server/aiChatHandler/errorHandling.js +53 -0
- package/src/server/aiChatHandler/index.js +91 -0
- package/src/server/aiChatHandler/llmStreaming.js +105 -0
- package/src/server/aiChatHandler/validation.js +24 -0
- package/src/server/handleAIChatMessage.js +2 -0
- package/src/server/index.js +8 -0
- package/dist/assets/index-DPIrco_g.js +0 -235
- package/src/client/VZSidebar/AIChat.tsx +0 -142
package/dist/index.html
CHANGED
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap"
|
|
21
21
|
rel="stylesheet"
|
|
22
22
|
/>
|
|
23
|
-
<script type="module" crossorigin src="/assets/index-
|
|
24
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
23
|
+
<script type="module" crossorigin src="/assets/index-ajiBaTmx.js"></script>
|
|
24
|
+
<link rel="stylesheet" crossorigin href="/assets/index-lvLw6dCW.css">
|
|
25
25
|
</head>
|
|
26
26
|
<body>
|
|
27
27
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vzcode",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.35.0",
|
|
4
4
|
"description": "Multiplayer code editor system",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -134,7 +134,8 @@
|
|
|
134
134
|
"@uiw/codemirror-theme-xcode": "^4.24.1",
|
|
135
135
|
"@uiw/codemirror-themes": "^4.24.1",
|
|
136
136
|
"@valtown/codemirror-ts": "^2.3.1",
|
|
137
|
-
"@vizhub/viz-types": "^0.
|
|
137
|
+
"@vizhub/viz-types": "^0.3.0",
|
|
138
|
+
"@vizhub/viz-utils": "^1.3.0",
|
|
138
139
|
"body-parser": "^2.2.0",
|
|
139
140
|
"codemirror": "^6.0.2",
|
|
140
141
|
"codemirror-copilot": "^0.0.7",
|
|
@@ -144,6 +145,7 @@
|
|
|
144
145
|
"d3-array": "^3.2.4",
|
|
145
146
|
"diff-match-patch": "^1.0.5",
|
|
146
147
|
"dotenv": "^17.2.0",
|
|
148
|
+
"editcodewithai": "^2.0.0",
|
|
147
149
|
"eslint": "^9.31.0",
|
|
148
150
|
"eslint-linter-browserify": "^9.31.0",
|
|
149
151
|
"express": "^5.1.0",
|
|
@@ -151,6 +153,7 @@
|
|
|
151
153
|
"ignore": "^7.0.5",
|
|
152
154
|
"json0-ot-diff": "^1.1.2",
|
|
153
155
|
"livekit-server-sdk": "^2.13.1",
|
|
156
|
+
"llm-code-format": "^2.0.1",
|
|
154
157
|
"ngrok": "^5.0.0-beta.2",
|
|
155
158
|
"npm": "^11.4.2",
|
|
156
159
|
"open": "^10.2.0",
|
|
@@ -158,7 +161,9 @@
|
|
|
158
161
|
"react": "^18",
|
|
159
162
|
"react-bootstrap": "^2.10.10",
|
|
160
163
|
"react-dom": "^18",
|
|
164
|
+
"react-markdown": "^10.1.0",
|
|
161
165
|
"react-router-dom": "^7.7.0",
|
|
166
|
+
"remark-gfm": "^4.0.1",
|
|
162
167
|
"sharedb": "^5.2.2",
|
|
163
168
|
"sharedb-client-browser": "^5.2.2",
|
|
164
169
|
"uuid": "^11.1.0",
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { useRef, useEffect, useCallback } from 'react';
|
|
2
|
+
import { Form, Button } from '../../bootstrap';
|
|
3
|
+
|
|
4
|
+
interface ChatInputProps {
|
|
5
|
+
message: string;
|
|
6
|
+
setMessage: (message: string) => void;
|
|
7
|
+
onSendMessage: () => void;
|
|
8
|
+
isLoading: boolean;
|
|
9
|
+
focused: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const ChatInput = ({
|
|
13
|
+
message,
|
|
14
|
+
setMessage,
|
|
15
|
+
onSendMessage,
|
|
16
|
+
isLoading,
|
|
17
|
+
focused,
|
|
18
|
+
}: ChatInputProps) => {
|
|
19
|
+
const inputRef = useRef<HTMLTextAreaElement>(null);
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
// Focus the input when the AI chat is focused
|
|
23
|
+
if (focused && inputRef.current) {
|
|
24
|
+
inputRef.current.focus();
|
|
25
|
+
}
|
|
26
|
+
}, [focused]);
|
|
27
|
+
|
|
28
|
+
const handleKeyDown = useCallback(
|
|
29
|
+
(event: React.KeyboardEvent) => {
|
|
30
|
+
if (event.key === 'Enter' && !event.shiftKey) {
|
|
31
|
+
event.preventDefault();
|
|
32
|
+
onSendMessage();
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
[onSendMessage],
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div className="ai-chat-input-container">
|
|
40
|
+
<Form.Group className="ai-chat-input-group">
|
|
41
|
+
<Form.Control
|
|
42
|
+
as="textarea"
|
|
43
|
+
rows={2}
|
|
44
|
+
value={message}
|
|
45
|
+
onChange={(event) =>
|
|
46
|
+
setMessage(event.target.value)
|
|
47
|
+
}
|
|
48
|
+
onKeyDown={handleKeyDown}
|
|
49
|
+
ref={inputRef}
|
|
50
|
+
placeholder="Describe what you'd like me to change in your code..."
|
|
51
|
+
spellCheck="false"
|
|
52
|
+
disabled={isLoading}
|
|
53
|
+
/>
|
|
54
|
+
<Button
|
|
55
|
+
variant="primary"
|
|
56
|
+
onClick={onSendMessage}
|
|
57
|
+
disabled={!message.trim() || isLoading}
|
|
58
|
+
className="ai-chat-send-button"
|
|
59
|
+
>
|
|
60
|
+
Send
|
|
61
|
+
</Button>
|
|
62
|
+
</Form.Group>
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { timestampToDate } from '@vizhub/viz-utils';
|
|
2
|
+
import Markdown from 'react-markdown';
|
|
3
|
+
import remarkGfm from 'remark-gfm';
|
|
4
|
+
|
|
5
|
+
interface MessageProps {
|
|
6
|
+
id: string;
|
|
7
|
+
role: 'user' | 'assistant';
|
|
8
|
+
content: string;
|
|
9
|
+
timestamp: number;
|
|
10
|
+
isStreaming?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const Message = ({
|
|
14
|
+
role,
|
|
15
|
+
content,
|
|
16
|
+
timestamp,
|
|
17
|
+
isStreaming,
|
|
18
|
+
}: MessageProps) => {
|
|
19
|
+
return (
|
|
20
|
+
<div
|
|
21
|
+
className={`ai-chat-message ${role}${isStreaming ? ' streaming' : ''}`}
|
|
22
|
+
>
|
|
23
|
+
<div className="ai-chat-message-content">
|
|
24
|
+
<Markdown remarkPlugins={[remarkGfm]}>
|
|
25
|
+
{content}
|
|
26
|
+
</Markdown>
|
|
27
|
+
</div>
|
|
28
|
+
<div className="ai-chat-message-time">
|
|
29
|
+
{timestampToDate(timestamp).toLocaleTimeString([], {
|
|
30
|
+
hour: '2-digit',
|
|
31
|
+
minute: '2-digit',
|
|
32
|
+
})}
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { useRef, useEffect } from 'react';
|
|
2
|
+
import { Message } from './Message';
|
|
3
|
+
import { StreamingMessage } from './StreamingMessage';
|
|
4
|
+
import { TypingIndicator } from './TypingIndicator';
|
|
5
|
+
|
|
6
|
+
interface MessageData {
|
|
7
|
+
id: string;
|
|
8
|
+
role: 'user' | 'assistant';
|
|
9
|
+
content: string;
|
|
10
|
+
timestamp: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface MessageListProps {
|
|
14
|
+
messages: MessageData[];
|
|
15
|
+
aiScratchpad?: string;
|
|
16
|
+
aiStatus?: string;
|
|
17
|
+
isLoading: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const MessageList = ({
|
|
21
|
+
messages,
|
|
22
|
+
aiScratchpad,
|
|
23
|
+
aiStatus,
|
|
24
|
+
isLoading,
|
|
25
|
+
}: MessageListProps) => {
|
|
26
|
+
const messagesEndRef = useRef<HTMLDivElement>(null);
|
|
27
|
+
|
|
28
|
+
const scrollToBottom = () => {
|
|
29
|
+
messagesEndRef.current?.scrollIntoView({
|
|
30
|
+
behavior: 'smooth',
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
scrollToBottom();
|
|
36
|
+
}, [messages, aiScratchpad]);
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div className="ai-chat-messages">
|
|
40
|
+
{messages.map((msg) => (
|
|
41
|
+
<Message
|
|
42
|
+
key={msg.id}
|
|
43
|
+
id={msg.id}
|
|
44
|
+
role={msg.role}
|
|
45
|
+
content={msg.content}
|
|
46
|
+
timestamp={msg.timestamp}
|
|
47
|
+
/>
|
|
48
|
+
))}
|
|
49
|
+
|
|
50
|
+
{/* Show streaming content if available */}
|
|
51
|
+
{aiScratchpad && (
|
|
52
|
+
<StreamingMessage
|
|
53
|
+
content={aiScratchpad}
|
|
54
|
+
status={aiStatus}
|
|
55
|
+
/>
|
|
56
|
+
)}
|
|
57
|
+
|
|
58
|
+
{isLoading && !aiScratchpad && <TypingIndicator />}
|
|
59
|
+
<div ref={messagesEndRef} />
|
|
60
|
+
</div>
|
|
61
|
+
);
|
|
62
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import Markdown from 'react-markdown';
|
|
2
|
+
import remarkGfm from 'remark-gfm';
|
|
3
|
+
|
|
4
|
+
interface StreamingMessageProps {
|
|
5
|
+
content: string;
|
|
6
|
+
status?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const StreamingMessage = ({
|
|
10
|
+
content,
|
|
11
|
+
status,
|
|
12
|
+
}: StreamingMessageProps) => {
|
|
13
|
+
return (
|
|
14
|
+
<div className="ai-chat-message assistant streaming">
|
|
15
|
+
{status && (
|
|
16
|
+
<div className="ai-chat-status">{status}</div>
|
|
17
|
+
)}
|
|
18
|
+
<div className="ai-chat-message-content">
|
|
19
|
+
<Markdown remarkPlugins={[remarkGfm]}>
|
|
20
|
+
{content}
|
|
21
|
+
</Markdown>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const TypingIndicator = () => {
|
|
2
|
+
return (
|
|
3
|
+
<div className="ai-chat-message assistant">
|
|
4
|
+
<div className="ai-chat-message-content">
|
|
5
|
+
<div className="ai-chat-typing">
|
|
6
|
+
<span></span>
|
|
7
|
+
<span></span>
|
|
8
|
+
<span></span>
|
|
9
|
+
</div>
|
|
10
|
+
</div>
|
|
11
|
+
</div>
|
|
12
|
+
);
|
|
13
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { useContext, useState, useCallback } from 'react';
|
|
2
|
+
import { VZCodeContext } from '../../VZCodeContext';
|
|
3
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
4
|
+
import { MessageList } from './MessageList';
|
|
5
|
+
import { ChatInput } from './ChatInput';
|
|
6
|
+
import './styles.scss';
|
|
7
|
+
|
|
8
|
+
export const AIChat = () => {
|
|
9
|
+
const [message, setMessage] = useState('');
|
|
10
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
11
|
+
const [currentChatId] = useState(() => uuidv4());
|
|
12
|
+
|
|
13
|
+
const { aiChatFocused, content } =
|
|
14
|
+
useContext(VZCodeContext);
|
|
15
|
+
|
|
16
|
+
// Get current chat data from content
|
|
17
|
+
const currentChat = content?.chats?.[currentChatId];
|
|
18
|
+
const messages = currentChat?.messages || [];
|
|
19
|
+
const aiScratchpad = currentChat?.aiScratchpad;
|
|
20
|
+
const aiStatus = currentChat?.aiStatus;
|
|
21
|
+
|
|
22
|
+
const handleSendMessage = useCallback(async () => {
|
|
23
|
+
if (!message.trim() || isLoading) return;
|
|
24
|
+
|
|
25
|
+
setMessage('');
|
|
26
|
+
setIsLoading(true);
|
|
27
|
+
|
|
28
|
+
// Call backend endpoint for AI response
|
|
29
|
+
// The server will handle all ShareDB operations including adding the user message
|
|
30
|
+
try {
|
|
31
|
+
const response = await fetch('/ai-chat-message', {
|
|
32
|
+
method: 'POST',
|
|
33
|
+
headers: {
|
|
34
|
+
'Content-Type': 'application/json',
|
|
35
|
+
},
|
|
36
|
+
body: JSON.stringify({
|
|
37
|
+
content: message.trim(),
|
|
38
|
+
chatId: currentChatId,
|
|
39
|
+
}),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
if (!response.ok) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
`HTTP error! status: ${response.status}`,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// The backend handles all ShareDB operations
|
|
49
|
+
await response.json();
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error('Error getting AI response:', error);
|
|
52
|
+
// Server will handle error messages too
|
|
53
|
+
} finally {
|
|
54
|
+
setIsLoading(false);
|
|
55
|
+
}
|
|
56
|
+
}, [message, isLoading, currentChatId]);
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<div className="ai-chat-container">
|
|
60
|
+
<MessageList
|
|
61
|
+
messages={messages}
|
|
62
|
+
aiScratchpad={aiScratchpad}
|
|
63
|
+
aiStatus={aiStatus}
|
|
64
|
+
isLoading={isLoading}
|
|
65
|
+
/>
|
|
66
|
+
<ChatInput
|
|
67
|
+
message={message}
|
|
68
|
+
setMessage={setMessage}
|
|
69
|
+
onSendMessage={handleSendMessage}
|
|
70
|
+
isLoading={isLoading}
|
|
71
|
+
focused={aiChatFocused}
|
|
72
|
+
/>
|
|
73
|
+
</div>
|
|
74
|
+
);
|
|
75
|
+
};
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
.ai-chat-container {
|
|
2
|
+
height: 100%;
|
|
3
|
+
display: flex;
|
|
4
|
+
flex-direction: column;
|
|
5
|
+
padding: 10px;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.ai-chat-messages {
|
|
9
|
+
flex: 1;
|
|
10
|
+
overflow-y: auto;
|
|
11
|
+
margin-bottom: 10px;
|
|
12
|
+
padding-right: 5px;
|
|
13
|
+
|
|
14
|
+
&::-webkit-scrollbar {
|
|
15
|
+
width: 6px;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
&::-webkit-scrollbar-track {
|
|
19
|
+
background: var(--vh-color-neutral-02);
|
|
20
|
+
border-radius: 3px;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
&::-webkit-scrollbar-thumb {
|
|
24
|
+
background: var(--vh-color-neutral-04);
|
|
25
|
+
border-radius: 3px;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.ai-chat-message {
|
|
30
|
+
margin-bottom: 15px;
|
|
31
|
+
|
|
32
|
+
&.user {
|
|
33
|
+
.ai-chat-message-content {
|
|
34
|
+
background: var(--vh-color-primary-01);
|
|
35
|
+
color: white;
|
|
36
|
+
margin-left: 20px;
|
|
37
|
+
border-radius: 12px 12px 4px 12px;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.ai-chat-message-time {
|
|
41
|
+
text-align: right;
|
|
42
|
+
margin-right: 5px;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
&.assistant {
|
|
47
|
+
.ai-chat-message-content {
|
|
48
|
+
background: var(--vh-color-neutral-02);
|
|
49
|
+
color: white;
|
|
50
|
+
margin-right: 20px;
|
|
51
|
+
border-radius: 12px 12px 12px 4px;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.ai-chat-message-time {
|
|
55
|
+
text-align: left;
|
|
56
|
+
margin-left: 5px;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
&.streaming {
|
|
60
|
+
.ai-chat-message-content {
|
|
61
|
+
background: var(--vh-color-neutral-02);
|
|
62
|
+
color: white;
|
|
63
|
+
border-radius: 12px 12px 12px 4px;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.ai-chat-message-content {
|
|
70
|
+
padding: 10px 12px;
|
|
71
|
+
font-size: 14px;
|
|
72
|
+
line-height: 1.4;
|
|
73
|
+
word-wrap: break-word;
|
|
74
|
+
|
|
75
|
+
// Markdown styling for dark backgrounds
|
|
76
|
+
pre {
|
|
77
|
+
background-color: rgba(0, 0, 0, 0.3);
|
|
78
|
+
color: #e6e6e6;
|
|
79
|
+
padding: 0.5rem;
|
|
80
|
+
border-radius: 0.25rem;
|
|
81
|
+
overflow-x: auto;
|
|
82
|
+
font-size: 0.875rem;
|
|
83
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
code {
|
|
87
|
+
background-color: rgba(0, 0, 0, 0.3);
|
|
88
|
+
color: #e6e6e6;
|
|
89
|
+
padding: 0.125rem 0.25rem;
|
|
90
|
+
border-radius: 0.25rem;
|
|
91
|
+
font-size: 0.875rem;
|
|
92
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
pre code {
|
|
96
|
+
background-color: transparent;
|
|
97
|
+
padding: 0;
|
|
98
|
+
border: none;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
blockquote {
|
|
102
|
+
border-left: 3px solid var(--vh-color-neutral-03);
|
|
103
|
+
padding-left: 1rem;
|
|
104
|
+
margin: 0.5rem 0;
|
|
105
|
+
color: var(--vh-color-neutral-03);
|
|
106
|
+
font-style: italic;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
h1,
|
|
110
|
+
h2,
|
|
111
|
+
h3,
|
|
112
|
+
h4,
|
|
113
|
+
h5,
|
|
114
|
+
h6 {
|
|
115
|
+
margin-top: 1rem;
|
|
116
|
+
margin-bottom: 0.5rem;
|
|
117
|
+
color: inherit;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
ul,
|
|
121
|
+
ol {
|
|
122
|
+
padding-left: 1.5rem;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
p {
|
|
126
|
+
margin-bottom: 0.5rem;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Ensure good contrast for assistant messages
|
|
130
|
+
.ai-chat-message.assistant & {
|
|
131
|
+
color: white;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Ensure user message text stays white
|
|
135
|
+
.ai-chat-message.user & {
|
|
136
|
+
color: white;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.ai-chat-message-time {
|
|
141
|
+
font-size: 11px;
|
|
142
|
+
color: var(--vh-color-neutral-03);
|
|
143
|
+
margin-top: 4px;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.ai-chat-status {
|
|
147
|
+
font-size: 0.75rem;
|
|
148
|
+
font-style: italic;
|
|
149
|
+
color: var(--vh-color-neutral-03);
|
|
150
|
+
margin-bottom: 0.5rem;
|
|
151
|
+
margin-left: 20px;
|
|
152
|
+
margin-right: 20px;
|
|
153
|
+
padding: 0.25rem 0.5rem;
|
|
154
|
+
background-color: var(--vh-color-neutral-02);
|
|
155
|
+
border-radius: 0.25rem;
|
|
156
|
+
border: 1px solid var(--vh-color-neutral-03);
|
|
157
|
+
text-align: left;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.ai-chat-typing {
|
|
161
|
+
display: flex;
|
|
162
|
+
gap: 4px;
|
|
163
|
+
align-items: center;
|
|
164
|
+
|
|
165
|
+
span {
|
|
166
|
+
width: 6px;
|
|
167
|
+
height: 6px;
|
|
168
|
+
background: var(--vh-color-neutral-03);
|
|
169
|
+
border-radius: 50%;
|
|
170
|
+
animation: typing 1.4s infinite ease-in-out;
|
|
171
|
+
|
|
172
|
+
&:nth-child(1) {
|
|
173
|
+
animation-delay: -0.32s;
|
|
174
|
+
}
|
|
175
|
+
&:nth-child(2) {
|
|
176
|
+
animation-delay: -0.16s;
|
|
177
|
+
}
|
|
178
|
+
&:nth-child(3) {
|
|
179
|
+
animation-delay: 0s;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
@keyframes typing {
|
|
185
|
+
0%,
|
|
186
|
+
80%,
|
|
187
|
+
100% {
|
|
188
|
+
transform: scale(0.8);
|
|
189
|
+
opacity: 0.5;
|
|
190
|
+
}
|
|
191
|
+
40% {
|
|
192
|
+
transform: scale(1);
|
|
193
|
+
opacity: 1;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.ai-chat-input-container {
|
|
198
|
+
border-top: 1px solid var(--vh-color-neutral-02);
|
|
199
|
+
padding-top: 10px;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.ai-chat-input-group {
|
|
203
|
+
display: flex;
|
|
204
|
+
gap: 8px;
|
|
205
|
+
margin-bottom: 0;
|
|
206
|
+
|
|
207
|
+
textarea {
|
|
208
|
+
flex: 1;
|
|
209
|
+
background: var(--vh-color-neutral-02) !important;
|
|
210
|
+
border: 1px solid var(--vh-color-neutral-03) !important;
|
|
211
|
+
color: var(--vh-color-neutral-04) !important;
|
|
212
|
+
border-radius: 8px;
|
|
213
|
+
padding: 8px 10px;
|
|
214
|
+
font-size: 14px;
|
|
215
|
+
font-family: var(--vzcode-font-family);
|
|
216
|
+
resize: none;
|
|
217
|
+
min-height: 36px;
|
|
218
|
+
|
|
219
|
+
&:focus {
|
|
220
|
+
outline: none !important;
|
|
221
|
+
border-color: var(--vh-color-primary-01) !important;
|
|
222
|
+
box-shadow: 0 0 0 2px rgba(38, 67, 153, 0.2) !important;
|
|
223
|
+
color: var(--vh-color-neutral-04) !important;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
&::placeholder {
|
|
227
|
+
color: var(--vh-color-neutral-03) !important;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
&:disabled {
|
|
231
|
+
opacity: 0.6;
|
|
232
|
+
cursor: not-allowed;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.ai-chat-send-button {
|
|
237
|
+
background: var(--vh-color-primary-01);
|
|
238
|
+
border: none;
|
|
239
|
+
color: white;
|
|
240
|
+
border-radius: 8px;
|
|
241
|
+
padding: 8px 16px;
|
|
242
|
+
font-size: 14px;
|
|
243
|
+
font-weight: 500;
|
|
244
|
+
cursor: pointer;
|
|
245
|
+
transition: background-color 0.2s ease;
|
|
246
|
+
|
|
247
|
+
&:hover:not(:disabled) {
|
|
248
|
+
background: var(--vh-color-primary-02);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
&:disabled {
|
|
252
|
+
background: var(--vh-color-neutral-03);
|
|
253
|
+
cursor: not-allowed;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|