codex-linux 1.0.0 → 1.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/.github/workflows/ci.yml +0 -27
- package/README.md +40 -39
- package/abyss-teal-design-system.html +1449 -0
- package/dist/renderer/assets/main-AJwWHWV7.js +304 -0
- package/dist/renderer/assets/main-ua9RiJ9-.css +1 -0
- package/dist/renderer/index.html +2 -2
- package/package.json +4 -3
- package/scripts/install.sh +1 -1
- package/src/renderer/App.tsx +45 -15
- package/src/renderer/components/AgentPanel.tsx +94 -125
- package/src/renderer/components/AutomationPanel.tsx +39 -34
- package/src/renderer/components/ChatInterface.tsx +81 -123
- package/src/renderer/components/Header.tsx +24 -38
- package/src/renderer/components/SettingsPanel.tsx +89 -96
- package/src/renderer/components/Sidebar.tsx +33 -51
- package/src/renderer/components/SkillsPanel.tsx +54 -56
- package/src/renderer/components/WelcomeChat.tsx +199 -0
- package/src/renderer/components/WorktreePanel.tsx +32 -27
- package/src/renderer/components/ui/Button.tsx +17 -19
- package/src/renderer/components/ui/Card.tsx +14 -15
- package/src/renderer/components/ui/Input.tsx +12 -13
- package/src/renderer/index.css +37 -59
- package/src/renderer/styles/abyss-teal.css +405 -0
- package/dist/renderer/assets/main-DJlZQBCA.js +0 -304
- package/dist/renderer/assets/main-N33ZXEr8.css +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useState, useRef, useEffect } from 'react';
|
|
2
|
-
import { Agent, AgentMessage,
|
|
3
|
-
import { Send, Bot, User, Loader2, Paperclip, MoreVertical, Copy, Check, ChevronDown, Sparkles
|
|
2
|
+
import { Agent, AgentMessage, PermissionMode } from '../../shared/types';
|
|
3
|
+
import { Send, Bot, User, Loader2, Paperclip, MoreVertical, Copy, Check, ChevronDown, Sparkles } from 'lucide-react';
|
|
4
4
|
import { format } from 'date-fns';
|
|
5
5
|
import { PermissionSelector } from './PermissionSelector';
|
|
6
6
|
|
|
@@ -29,34 +29,17 @@ const FREE_MODELS: ModelOption[] = [
|
|
|
29
29
|
{ id: 'meta-llama/llama-3.3-70b-instruct:free', name: 'Llama 3.3 70B', backend: 'openrouter', isFree: true, contextWindow: 128000 },
|
|
30
30
|
{ id: 'deepseek/deepseek-r1-0528:free', name: 'DeepSeek R1', backend: 'openrouter', isFree: true, contextWindow: 128000 },
|
|
31
31
|
{ id: 'google/gemma-3-27b-it:free', name: 'Gemma 3 27B', backend: 'openrouter', isFree: true, contextWindow: 32768 },
|
|
32
|
-
{ id: 'google/gemma-3-12b-it:free', name: 'Gemma 3 12B', backend: 'openrouter', isFree: true, contextWindow: 32768 },
|
|
33
32
|
{ id: 'mistralai/mistral-small-3.1-24b-instruct:free', name: 'Mistral Small 3.1', backend: 'openrouter', isFree: true, contextWindow: 128000, supportsVision: true },
|
|
34
33
|
{ id: 'qwen/qwen3-coder:free', name: 'Qwen 3 Coder', backend: 'openrouter', isFree: true, contextWindow: 32768 },
|
|
35
|
-
{ id: 'nousresearch/hermes-3-llama-3.1-405b:free', name: 'Hermes 3 405B', backend: 'openrouter', isFree: true, contextWindow: 128000 },
|
|
36
34
|
{ id: 'openai/gpt-oss-120b:free', name: 'GPT-OSS 120B', backend: 'openrouter', isFree: true, contextWindow: 128000 },
|
|
37
|
-
{ id: 'nvidia/nemotron-nano-12b-v2-vl:free', name: 'Nemotron 12B VL', backend: 'openrouter', isFree: true, contextWindow: 32768, supportsVision: true },
|
|
38
35
|
{ id: 'z-ai/glm-4.5-air:free', name: 'GLM 4.5 Air', backend: 'openrouter', isFree: true, contextWindow: 128000 },
|
|
39
|
-
{ id: 'liquid/lfm-2.5-1.2b-instruct:free', name: 'LFM 2.5 1.2B', backend: 'openrouter', isFree: true, contextWindow: 8192 },
|
|
40
|
-
{ id: 'upstage/solar-pro-3:free', name: 'Solar Pro 3', backend: 'openrouter', isFree: true, contextWindow: 32768 },
|
|
41
|
-
{ id: 'moonshotai/kimi-k2.5', name: 'Kimi K2.5', backend: 'nvidia', isFree: true, contextWindow: 128000 },
|
|
42
|
-
{ id: 'meta/llama-3.3-70b-instruct', name: 'Llama 3.3 70B (NVIDIA)', backend: 'nvidia', isFree: true, contextWindow: 128000 },
|
|
43
|
-
{ id: 'google/gemma-2-27b-it', name: 'Gemma 2 27B (NVIDIA)', backend: 'nvidia', isFree: true, contextWindow: 8192 },
|
|
44
|
-
{ id: 'microsoft/phi-3-medium-128k-instruct', name: 'Phi-3 Medium 128K', backend: 'nvidia', isFree: true, contextWindow: 128000 },
|
|
45
36
|
{ id: 'llama-3.3-70b-versatile', name: 'Llama 3.3 70B (Groq)', backend: 'groq', isFree: true, contextWindow: 128000 },
|
|
46
|
-
{ id: 'llama-3.1-8b-instant', name: 'Llama 3.1 8B (Groq)', backend: 'groq', isFree: true, contextWindow: 128000 },
|
|
47
|
-
{ id: 'mixtral-8x7b-32768', name: 'Mixtral 8x7B (Groq)', backend: 'groq', isFree: true, contextWindow: 32768 },
|
|
48
|
-
{ id: 'gemma2-9b-it', name: 'Gemma 2 9B (Groq)', backend: 'groq', isFree: true, contextWindow: 8192 },
|
|
49
|
-
{ id: 'minimax-m2.5:cloud', name: 'Minimax M2.5 (Ollama)', backend: 'ollama', isFree: true, contextWindow: 128000 },
|
|
50
37
|
{ id: 'llama3.2:latest', name: 'Llama 3.2 (Ollama)', backend: 'ollama', isFree: true, contextWindow: 128000, supportsVision: true },
|
|
51
38
|
{ id: 'llama3.3:latest', name: 'Llama 3.3 70B (Ollama)', backend: 'ollama', isFree: true, contextWindow: 128000 },
|
|
52
39
|
{ id: 'mistral:latest', name: 'Mistral 7B (Ollama)', backend: 'ollama', isFree: true, contextWindow: 32768 },
|
|
53
40
|
{ id: 'deepseek-r1:latest', name: 'DeepSeek R1 (Ollama)', backend: 'ollama', isFree: true, contextWindow: 128000 },
|
|
54
41
|
{ id: 'qwen2.5:latest', name: 'Qwen 2.5 (Ollama)', backend: 'ollama', isFree: true, contextWindow: 128000 },
|
|
55
|
-
{ id: 'qwen2.5-coder:latest', name: 'Qwen 2.5 Coder (Ollama)', backend: 'ollama', isFree: true, contextWindow: 32768 },
|
|
56
|
-
{ id: 'gemma3:latest', name: 'Gemma 3 (Ollama)', backend: 'ollama', isFree: true, contextWindow: 32768, supportsVision: true },
|
|
57
|
-
{ id: 'phi3:latest', name: 'Phi-3 (Ollama)', backend: 'ollama', isFree: true, contextWindow: 128000 },
|
|
58
42
|
{ id: 'codellama:latest', name: 'Code Llama (Ollama)', backend: 'ollama', isFree: true, contextWindow: 16384 },
|
|
59
|
-
{ id: 'deepseek-coder:latest', name: 'DeepSeek Coder (Ollama)', backend: 'ollama', isFree: true, contextWindow: 16384 },
|
|
60
43
|
];
|
|
61
44
|
|
|
62
45
|
const BACKEND_LABELS: Record<string, string> = {
|
|
@@ -159,21 +142,6 @@ export const ChatInterface: React.FC<ChatInterfaceProps> = ({
|
|
|
159
142
|
}
|
|
160
143
|
};
|
|
161
144
|
|
|
162
|
-
const handleImageUpload = async (file: File) => {
|
|
163
|
-
if (isLoading) return;
|
|
164
|
-
|
|
165
|
-
try {
|
|
166
|
-
const reader = new FileReader();
|
|
167
|
-
reader.onloadend = async () => {
|
|
168
|
-
const base64 = reader.result as string;
|
|
169
|
-
await onSendMessage(`[IMAGE] ${base64}`);
|
|
170
|
-
};
|
|
171
|
-
reader.readAsDataURL(file);
|
|
172
|
-
} catch (error) {
|
|
173
|
-
console.error('Failed to upload image:', error);
|
|
174
|
-
}
|
|
175
|
-
};
|
|
176
|
-
|
|
177
145
|
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
178
146
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
179
147
|
e.preventDefault();
|
|
@@ -212,7 +180,7 @@ export const ChatInterface: React.FC<ChatInterfaceProps> = ({
|
|
|
212
180
|
<p key={index} className="my-1">
|
|
213
181
|
{parts.map((part, i) =>
|
|
214
182
|
part.startsWith('`') && part.endsWith('`') ? (
|
|
215
|
-
<code key={i} className="bg-
|
|
183
|
+
<code key={i} className="bg-[var(--bg-hover)] px-1 py-0.5 rounded text-[11px] font-[var(--font-mono)] text-[var(--teal-300)]">
|
|
216
184
|
{part.slice(1, -1)}
|
|
217
185
|
</code>
|
|
218
186
|
) : (
|
|
@@ -224,24 +192,24 @@ export const ChatInterface: React.FC<ChatInterfaceProps> = ({
|
|
|
224
192
|
}
|
|
225
193
|
|
|
226
194
|
if (line.startsWith('### ')) {
|
|
227
|
-
return <h3 key={index} className="text-
|
|
195
|
+
return <h3 key={index} className="text-[15px] font-semibold mt-4 mb-2 text-[var(--text-primary)]">{line.slice(4)}</h3>;
|
|
228
196
|
}
|
|
229
197
|
if (line.startsWith('## ')) {
|
|
230
|
-
return <h2 key={index} className="text-
|
|
198
|
+
return <h2 key={index} className="text-[17px] font-semibold mt-4 mb-2 text-[var(--text-primary)]">{line.slice(3)}</h2>;
|
|
231
199
|
}
|
|
232
200
|
if (line.startsWith('# ')) {
|
|
233
|
-
return <h1 key={index} className="text-
|
|
201
|
+
return <h1 key={index} className="text-[19px] font-bold mt-4 mb-2 text-[var(--text-primary)]">{line.slice(2)}</h1>;
|
|
234
202
|
}
|
|
235
203
|
|
|
236
204
|
if (line.startsWith('- ') || line.startsWith('* ')) {
|
|
237
|
-
return <li key={index} className="ml-4 my-1">{line.slice(2)}</li>;
|
|
205
|
+
return <li key={index} className="ml-4 my-1 text-[var(--text-secondary)]">{line.slice(2)}</li>;
|
|
238
206
|
}
|
|
239
207
|
|
|
240
208
|
if (line.trim() === '') {
|
|
241
209
|
return <br key={index} />;
|
|
242
210
|
}
|
|
243
211
|
|
|
244
|
-
return <p key={index} className="my-1">{line}</p>;
|
|
212
|
+
return <p key={index} className="my-1 text-[var(--text-secondary)]">{line}</p>;
|
|
245
213
|
});
|
|
246
214
|
};
|
|
247
215
|
|
|
@@ -261,50 +229,48 @@ export const ChatInterface: React.FC<ChatInterfaceProps> = ({
|
|
|
261
229
|
const currentModelInfo = getCurrentModelInfo();
|
|
262
230
|
|
|
263
231
|
return (
|
|
264
|
-
<div className="flex flex-col h-full bg-
|
|
265
|
-
|
|
266
|
-
<div className="px-4 py-3 border-b border-border flex items-center justify-between bg-background/50">
|
|
232
|
+
<div className="flex flex-col h-full bg-[var(--bg-primary)]">
|
|
233
|
+
<div className="px-4 py-3 border-b border-[var(--border-subtle)] flex items-center justify-between bg-[var(--bg-surface)]">
|
|
267
234
|
<div className="flex items-center gap-3">
|
|
268
235
|
<div className={`w-2 h-2 rounded-full ${
|
|
269
|
-
agent.status === 'running' ? 'bg-
|
|
270
|
-
agent.status === 'error' ? 'bg-
|
|
271
|
-
agent.status === 'paused' ? 'bg-
|
|
272
|
-
'bg-
|
|
236
|
+
agent.status === 'running' ? 'bg-[var(--success)] animate-pulse' :
|
|
237
|
+
agent.status === 'error' ? 'bg-[var(--error)]' :
|
|
238
|
+
agent.status === 'paused' ? 'bg-[var(--warning)]' :
|
|
239
|
+
'bg-[var(--text-muted)]'
|
|
273
240
|
}`} />
|
|
274
241
|
<div>
|
|
275
|
-
<h3 className="font-medium">{agent.name}</h3>
|
|
242
|
+
<h3 className="font-medium text-[13px] text-[var(--text-primary)]">{agent.name}</h3>
|
|
276
243
|
</div>
|
|
277
244
|
</div>
|
|
278
245
|
|
|
279
|
-
{/* Model Selector */}
|
|
280
246
|
<div className="relative" ref={modelSelectorRef}>
|
|
281
247
|
<button
|
|
282
248
|
onClick={() => setShowModelSelector(!showModelSelector)}
|
|
283
|
-
className="flex items-center gap-2 px-3 py-1.5 bg-
|
|
249
|
+
className="flex items-center gap-2 px-3 py-1.5 bg-[var(--bg-elevated)] hover:bg-[var(--bg-hover)] rounded-lg transition-colors text-[12px] border border-[var(--border-subtle)]"
|
|
284
250
|
>
|
|
285
|
-
<Sparkles className="w-
|
|
286
|
-
<span className="font-medium truncate max-w-[150px]">{currentModelInfo.name}</span>
|
|
287
|
-
<span className="text-
|
|
251
|
+
<Sparkles className="w-3.5 h-3.5 text-[var(--success)]" />
|
|
252
|
+
<span className="font-medium truncate max-w-[150px] text-[var(--text-primary)]">{currentModelInfo.name}</span>
|
|
253
|
+
<span className="text-[10px] text-[var(--text-muted)] px-1.5 py-0.5 bg-[var(--bg-hover)] rounded">
|
|
288
254
|
{BACKEND_LABELS[currentModelInfo.backend || 'unknown'] || currentModelInfo.backend}
|
|
289
255
|
</span>
|
|
290
|
-
<ChevronDown className={`w-
|
|
256
|
+
<ChevronDown className={`w-3.5 h-3.5 text-[var(--text-muted)] transition-transform ${showModelSelector ? 'rotate-180' : ''}`} />
|
|
291
257
|
</button>
|
|
292
258
|
|
|
293
259
|
{showModelSelector && (
|
|
294
|
-
<div className="absolute top-full right-0 mt-2 w-80 bg-
|
|
295
|
-
<div className="p-3 border-b border-border">
|
|
260
|
+
<div className="absolute top-full right-0 mt-2 w-80 bg-[var(--bg-card)] border border-[var(--border-subtle)] rounded-[var(--radius-lg)] shadow-xl z-50 max-h-[70vh] overflow-hidden">
|
|
261
|
+
<div className="p-3 border-b border-[var(--border-subtle)]">
|
|
296
262
|
<input
|
|
297
263
|
type="text"
|
|
298
264
|
placeholder="Search models..."
|
|
299
265
|
value={searchQuery}
|
|
300
266
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
301
|
-
className="w-full px-3 py-2 bg-
|
|
267
|
+
className="w-full px-3 py-2 bg-[var(--bg-elevated)] rounded-[var(--radius-md)] text-[12px] text-[var(--text-primary)] placeholder:text-[var(--text-muted)] focus:outline-none focus:border-[var(--teal-500)] border border-[var(--border-subtle)]"
|
|
302
268
|
/>
|
|
303
269
|
<div className="flex gap-1 mt-2 flex-wrap">
|
|
304
270
|
<button
|
|
305
271
|
onClick={() => setSelectedBackend('all')}
|
|
306
|
-
className={`px-2 py-1 text-
|
|
307
|
-
selectedBackend === 'all' ? 'bg-
|
|
272
|
+
className={`px-2 py-1 text-[10px] rounded-md transition-colors ${
|
|
273
|
+
selectedBackend === 'all' ? 'bg-[var(--teal-500)] text-[var(--bg-void)]' : 'bg-[var(--bg-hover)] hover:bg-[var(--bg-active)] text-[var(--text-secondary)]'
|
|
308
274
|
}`}
|
|
309
275
|
>
|
|
310
276
|
All
|
|
@@ -313,8 +279,8 @@ export const ChatInterface: React.FC<ChatInterfaceProps> = ({
|
|
|
313
279
|
<button
|
|
314
280
|
key={key}
|
|
315
281
|
onClick={() => setSelectedBackend(key)}
|
|
316
|
-
className={`px-2 py-1 text-
|
|
317
|
-
selectedBackend === key ? 'bg-
|
|
282
|
+
className={`px-2 py-1 text-[10px] rounded-md transition-colors ${
|
|
283
|
+
selectedBackend === key ? 'bg-[var(--teal-500)] text-[var(--bg-void)]' : 'bg-[var(--bg-hover)] hover:bg-[var(--bg-active)] text-[var(--text-secondary)]'
|
|
318
284
|
}`}
|
|
319
285
|
>
|
|
320
286
|
{label}
|
|
@@ -326,40 +292,40 @@ export const ChatInterface: React.FC<ChatInterfaceProps> = ({
|
|
|
326
292
|
<div className="overflow-y-auto max-h-[50vh]">
|
|
327
293
|
{Object.entries(groupedModels).map(([backend, backendModels]) => (
|
|
328
294
|
<div key={backend}>
|
|
329
|
-
<div className="px-3 py-2 bg-
|
|
295
|
+
<div className="px-3 py-2 bg-[var(--bg-elevated)] text-[10px] font-medium text-[var(--text-muted)] sticky top-0">
|
|
330
296
|
{BACKEND_LABELS[backend] || backend} ({backendModels.length})
|
|
331
297
|
</div>
|
|
332
298
|
{backendModels.map((model) => (
|
|
333
299
|
<button
|
|
334
300
|
key={model.id}
|
|
335
301
|
onClick={() => handleModelSelect(model.id)}
|
|
336
|
-
className={`w-full px-3 py-2 text-left hover:bg-
|
|
337
|
-
selectedModel === model.id ? 'bg-
|
|
302
|
+
className={`w-full px-3 py-2 text-left hover:bg-[var(--bg-hover)] transition-colors flex items-center justify-between ${
|
|
303
|
+
selectedModel === model.id ? 'bg-[rgba(0,200,168,0.08)]' : ''
|
|
338
304
|
}`}
|
|
339
305
|
>
|
|
340
306
|
<div className="flex-1 min-w-0">
|
|
341
307
|
<div className="flex items-center gap-2">
|
|
342
|
-
<span className="font-medium text-
|
|
308
|
+
<span className="font-medium text-[12px] truncate text-[var(--text-primary)]">{model.name}</span>
|
|
343
309
|
{model.isFree && (
|
|
344
|
-
<span className="px-1.5 py-0.5 text-[
|
|
310
|
+
<span className="px-1.5 py-0.5 text-[9px] bg-[rgba(60,200,120,0.1)] text-[var(--success)] rounded">
|
|
345
311
|
FREE
|
|
346
312
|
</span>
|
|
347
313
|
)}
|
|
348
314
|
{model.supportsVision && (
|
|
349
|
-
<span className="px-1.5 py-0.5 text-[
|
|
315
|
+
<span className="px-1.5 py-0.5 text-[9px] bg-[rgba(104,144,244,0.1)] text-[var(--info)] rounded">
|
|
350
316
|
VISION
|
|
351
317
|
</span>
|
|
352
318
|
)}
|
|
353
319
|
</div>
|
|
354
|
-
<div className="text-
|
|
320
|
+
<div className="text-[10px] text-[var(--text-muted)] truncate">{model.id}</div>
|
|
355
321
|
</div>
|
|
356
322
|
{model.contextWindow && model.contextWindow >= 100000 && (
|
|
357
|
-
<span className="text-
|
|
323
|
+
<span className="text-[10px] text-[var(--text-muted)] ml-2">
|
|
358
324
|
{model.contextWindow >= 1000000 ? '1M' : `${model.contextWindow / 1000}K`}
|
|
359
325
|
</span>
|
|
360
326
|
)}
|
|
361
327
|
{selectedModel === model.id && (
|
|
362
|
-
<Check className="w-
|
|
328
|
+
<Check className="w-3.5 h-3.5 text-[var(--teal-400)] ml-2" />
|
|
363
329
|
)}
|
|
364
330
|
</button>
|
|
365
331
|
))}
|
|
@@ -378,41 +344,34 @@ export const ChatInterface: React.FC<ChatInterfaceProps> = ({
|
|
|
378
344
|
allowBypass={allowBypassMode}
|
|
379
345
|
/>
|
|
380
346
|
)}
|
|
381
|
-
<span className="text-
|
|
347
|
+
<span className="text-[11px] text-[var(--text-muted)]">
|
|
382
348
|
{agent.messages.length} messages
|
|
383
349
|
</span>
|
|
384
|
-
<button className="p-2 hover:bg-
|
|
385
|
-
<MoreVertical className="w-
|
|
350
|
+
<button className="p-2 hover:bg-[var(--bg-hover)] rounded-[var(--radius-sm)] text-[var(--text-muted)] transition-colors">
|
|
351
|
+
<MoreVertical className="w-3.5 h-3.5" />
|
|
386
352
|
</button>
|
|
387
353
|
</div>
|
|
388
354
|
</div>
|
|
389
355
|
|
|
390
|
-
{/* Messages */}
|
|
391
356
|
<div className="flex-1 overflow-y-auto p-4 space-y-4">
|
|
392
357
|
{agent.messages.length === 0 ? (
|
|
393
|
-
<div className="flex flex-col items-center justify-center h-full text-muted
|
|
358
|
+
<div className="flex flex-col items-center justify-center h-full text-[var(--text-muted)]">
|
|
394
359
|
<Bot className="w-16 h-16 mb-4 opacity-30" />
|
|
395
|
-
<p className="text-
|
|
396
|
-
<p className="text-
|
|
397
|
-
<div className="mt-6 space-y-2 text-
|
|
360
|
+
<p className="text-[15px] font-medium text-[var(--text-primary)]">Start a conversation</p>
|
|
361
|
+
<p className="text-[12px] mt-2">Send a message or use /task to assign a task</p>
|
|
362
|
+
<div className="mt-6 space-y-2 text-[12px]">
|
|
398
363
|
<button
|
|
399
364
|
onClick={() => setInput('Can you help me refactor this code?')}
|
|
400
|
-
className="block px-4 py-2 bg-
|
|
365
|
+
className="block px-4 py-2 bg-[var(--bg-card)] border border-[var(--border-subtle)] rounded-[var(--radius-md)] hover:border-[var(--border-accent)] transition-colors text-[var(--text-secondary)]"
|
|
401
366
|
>
|
|
402
367
|
"Can you help me refactor this code?"
|
|
403
368
|
</button>
|
|
404
369
|
<button
|
|
405
370
|
onClick={() => setInput('/task Review all JavaScript files for potential bugs')}
|
|
406
|
-
className="block px-4 py-2 bg-
|
|
371
|
+
className="block px-4 py-2 bg-[var(--bg-card)] border border-[var(--border-subtle)] rounded-[var(--radius-md)] hover:border-[var(--border-accent)] transition-colors text-[var(--text-secondary)]"
|
|
407
372
|
>
|
|
408
373
|
"/task Review all JavaScript files..."
|
|
409
374
|
</button>
|
|
410
|
-
<button
|
|
411
|
-
onClick={() => setInput('Explain how this function works')}
|
|
412
|
-
className="block px-4 py-2 bg-muted rounded-lg hover:bg-muted/80 transition-colors"
|
|
413
|
-
>
|
|
414
|
-
"Explain how this function works"
|
|
415
|
-
</button>
|
|
416
375
|
</div>
|
|
417
376
|
</div>
|
|
418
377
|
) : (
|
|
@@ -425,15 +384,15 @@ export const ChatInterface: React.FC<ChatInterfaceProps> = ({
|
|
|
425
384
|
>
|
|
426
385
|
<div className={`w-8 h-8 rounded-lg flex items-center justify-center flex-shrink-0 ${
|
|
427
386
|
message.role === 'user'
|
|
428
|
-
? 'bg-
|
|
387
|
+
? 'bg-[var(--teal-500)] text-[var(--bg-void)]'
|
|
429
388
|
: message.role === 'system'
|
|
430
|
-
? 'bg-
|
|
431
|
-
: 'bg-
|
|
389
|
+
? 'bg-[var(--bg-elevated)] text-[var(--text-muted)]'
|
|
390
|
+
: 'bg-[rgba(0,200,168,0.1)] text-[var(--teal-400)]'
|
|
432
391
|
}`}>
|
|
433
392
|
{message.role === 'user' ? (
|
|
434
393
|
<User className="w-4 h-4" />
|
|
435
394
|
) : message.role === 'system' ? (
|
|
436
|
-
<span className="text-
|
|
395
|
+
<span className="text-[10px] font-bold">S</span>
|
|
437
396
|
) : (
|
|
438
397
|
<Bot className="w-4 h-4" />
|
|
439
398
|
)}
|
|
@@ -444,19 +403,19 @@ export const ChatInterface: React.FC<ChatInterfaceProps> = ({
|
|
|
444
403
|
}`}>
|
|
445
404
|
<div className={`relative rounded-2xl px-4 py-3 ${
|
|
446
405
|
message.role === 'user'
|
|
447
|
-
? 'bg-
|
|
406
|
+
? 'bg-[var(--teal-500)] text-[var(--bg-void)]'
|
|
448
407
|
: message.role === 'system'
|
|
449
|
-
? 'bg-
|
|
450
|
-
: 'bg-
|
|
408
|
+
? 'bg-[var(--bg-elevated)] text-[var(--text-muted)] text-[12px] italic'
|
|
409
|
+
: 'bg-[var(--bg-card)] border border-[var(--border-subtle)]'
|
|
451
410
|
}`}>
|
|
452
411
|
<button
|
|
453
412
|
onClick={() => copyToClipboard(message.content, message.id)}
|
|
454
|
-
className="absolute top-2 right-2 p-1.5 opacity-0 group-hover:opacity-100 transition-opacity bg-
|
|
413
|
+
className="absolute top-2 right-2 p-1.5 opacity-0 group-hover:opacity-100 transition-opacity bg-[var(--bg-surface)] rounded-md"
|
|
455
414
|
>
|
|
456
415
|
{copiedId === message.id ? (
|
|
457
|
-
<Check className="w-3 h-3 text-
|
|
416
|
+
<Check className="w-3 h-3 text-[var(--success)]" />
|
|
458
417
|
) : (
|
|
459
|
-
<Copy className="w-3 h-3" />
|
|
418
|
+
<Copy className="w-3 h-3 text-[var(--text-muted)]" />
|
|
460
419
|
)}
|
|
461
420
|
</button>
|
|
462
421
|
|
|
@@ -469,7 +428,7 @@ export const ChatInterface: React.FC<ChatInterfaceProps> = ({
|
|
|
469
428
|
<div className="mb-3">
|
|
470
429
|
<button
|
|
471
430
|
onClick={() => setExpandedAutoContext(prev => ({ ...prev, [message.id]: !isExpanded }))}
|
|
472
|
-
className="text-
|
|
431
|
+
className="text-[10px] px-2 py-1 rounded-md bg-[var(--bg-elevated)] hover:bg-[var(--bg-hover)] border border-[var(--border-subtle)] text-[var(--text-muted)]"
|
|
473
432
|
type="button"
|
|
474
433
|
>
|
|
475
434
|
Auto-context: {ac.files.length} files{typeof ac.totalChars === 'number' ? ` ${ac.totalChars} chars` : ''}
|
|
@@ -477,12 +436,12 @@ export const ChatInterface: React.FC<ChatInterfaceProps> = ({
|
|
|
477
436
|
</button>
|
|
478
437
|
|
|
479
438
|
{isExpanded && (
|
|
480
|
-
<div className="mt-2 rounded-md border border-border bg-
|
|
439
|
+
<div className="mt-2 rounded-md border border-[var(--border-subtle)] bg-[var(--bg-elevated)] p-2 text-[10px]">
|
|
481
440
|
<div className="space-y-1">
|
|
482
441
|
{ac.files.map((f, idx) => (
|
|
483
442
|
<div key={`${message.id}-ac-${idx}`} className="flex gap-2">
|
|
484
|
-
<span className="font-mono text-
|
|
485
|
-
<span className="text-muted
|
|
443
|
+
<span className="font-[var(--font-mono)] text-[var(--text-primary)] truncate max-w-[260px]">{f.path}</span>
|
|
444
|
+
<span className="text-[var(--text-muted)]">- {f.reason}</span>
|
|
486
445
|
</div>
|
|
487
446
|
))}
|
|
488
447
|
</div>
|
|
@@ -496,30 +455,30 @@ export const ChatInterface: React.FC<ChatInterfaceProps> = ({
|
|
|
496
455
|
</div>
|
|
497
456
|
|
|
498
457
|
{extractCodeBlocks(message.content).map((block, idx) => (
|
|
499
|
-
<div key={idx} className="mt-4 rounded-lg overflow-hidden border border-border">
|
|
500
|
-
<div className="flex items-center justify-between px-3 py-2 bg-
|
|
501
|
-
<span className="text-
|
|
458
|
+
<div key={idx} className="mt-4 rounded-lg overflow-hidden border border-[var(--border-subtle)]">
|
|
459
|
+
<div className="flex items-center justify-between px-3 py-2 bg-[var(--bg-elevated)] border-b border-[var(--border-subtle)]">
|
|
460
|
+
<span className="text-[10px] font-medium text-[var(--text-muted)]">{block.language}</span>
|
|
502
461
|
<button
|
|
503
462
|
onClick={() => copyToClipboard(block.code, `code-${idx}`)}
|
|
504
|
-
className="p-1 hover:bg-
|
|
463
|
+
className="p-1 hover:bg-[var(--bg-hover)] rounded"
|
|
505
464
|
>
|
|
506
465
|
{copiedId === `code-${idx}` ? (
|
|
507
|
-
<Check className="w-3 h-3 text-
|
|
466
|
+
<Check className="w-3 h-3 text-[var(--success)]" />
|
|
508
467
|
) : (
|
|
509
|
-
<Copy className="w-3 h-3" />
|
|
468
|
+
<Copy className="w-3 h-3 text-[var(--text-muted)]" />
|
|
510
469
|
)}
|
|
511
470
|
</button>
|
|
512
471
|
</div>
|
|
513
|
-
<pre className="bg-
|
|
514
|
-
<code className="text-
|
|
472
|
+
<pre className="bg-[var(--bg-void)] p-3 rounded-b-lg overflow-x-auto">
|
|
473
|
+
<code className="text-[12px] font-[var(--font-mono)] text-[var(--teal-300)]">{block.code}</code>
|
|
515
474
|
</pre>
|
|
516
475
|
</div>
|
|
517
476
|
))}
|
|
518
477
|
|
|
519
|
-
<div className={`text-
|
|
478
|
+
<div className={`text-[10px] mt-2 ${
|
|
520
479
|
message.role === 'user'
|
|
521
|
-
? 'text-
|
|
522
|
-
: 'text-muted
|
|
480
|
+
? 'text-[var(--bg-void)] opacity-60'
|
|
481
|
+
: 'text-[var(--text-muted)]'
|
|
523
482
|
}`}>
|
|
524
483
|
{format(new Date(message.timestamp), 'HH:mm')}
|
|
525
484
|
</div>
|
|
@@ -531,12 +490,12 @@ export const ChatInterface: React.FC<ChatInterfaceProps> = ({
|
|
|
531
490
|
|
|
532
491
|
{isLoading && (
|
|
533
492
|
<div className="flex gap-3">
|
|
534
|
-
<div className="w-8 h-8 rounded-lg bg-
|
|
493
|
+
<div className="w-8 h-8 rounded-lg bg-[rgba(0,200,168,0.1)] text-[var(--teal-400)] flex items-center justify-center">
|
|
535
494
|
<Bot className="w-4 h-4" />
|
|
536
495
|
</div>
|
|
537
|
-
<div className="bg-
|
|
538
|
-
<Loader2 className="w-4 h-4 animate-spin" />
|
|
539
|
-
<span className="text-
|
|
496
|
+
<div className="bg-[var(--bg-card)] border border-[var(--border-subtle)] rounded-2xl px-4 py-3 flex items-center gap-2">
|
|
497
|
+
<Loader2 className="w-4 h-4 animate-spin text-[var(--teal-400)]" />
|
|
498
|
+
<span className="text-[12px] text-[var(--text-muted)]">Thinking...</span>
|
|
540
499
|
</div>
|
|
541
500
|
</div>
|
|
542
501
|
)}
|
|
@@ -544,10 +503,9 @@ export const ChatInterface: React.FC<ChatInterfaceProps> = ({
|
|
|
544
503
|
<div ref={messagesEndRef} />
|
|
545
504
|
</div>
|
|
546
505
|
|
|
547
|
-
|
|
548
|
-
<div className="p-4 border-t border-border bg-background/50">
|
|
506
|
+
<div className="p-4 border-t border-[var(--border-subtle)] bg-[var(--bg-surface)]">
|
|
549
507
|
<div className="relative flex items-end gap-2">
|
|
550
|
-
<button className="p-2 text-muted
|
|
508
|
+
<button className="p-2 text-[var(--text-muted)] hover:text-[var(--text-secondary)] transition-colors">
|
|
551
509
|
<Paperclip className="w-5 h-5" />
|
|
552
510
|
</button>
|
|
553
511
|
|
|
@@ -558,7 +516,7 @@ export const ChatInterface: React.FC<ChatInterfaceProps> = ({
|
|
|
558
516
|
onChange={e => setInput(e.target.value)}
|
|
559
517
|
onKeyDown={handleKeyDown}
|
|
560
518
|
placeholder="Type a message... (Shift+Enter for new line, /task for tasks)"
|
|
561
|
-
className="w-full px-4 py-3 pr-12 bg-
|
|
519
|
+
className="w-full px-4 py-3 pr-12 bg-[var(--bg-card)] border border-[var(--border-default)] rounded-[var(--radius-lg)] resize-none focus:outline-none focus:border-[var(--teal-500)] text-[13px] text-[var(--text-primary)] placeholder:text-[var(--text-disabled)] min-h-[56px] max-h-[200px]"
|
|
562
520
|
rows={1}
|
|
563
521
|
style={{ height: 'auto' }}
|
|
564
522
|
onInput={(e) => {
|
|
@@ -572,19 +530,19 @@ export const ChatInterface: React.FC<ChatInterfaceProps> = ({
|
|
|
572
530
|
<button
|
|
573
531
|
onClick={handleSend}
|
|
574
532
|
disabled={!input.trim() || isLoading}
|
|
575
|
-
className="p-3 bg-
|
|
533
|
+
className="p-3 bg-[var(--teal-500)] text-[var(--bg-void)] rounded-[var(--radius-lg)] hover:bg-[var(--teal-400)] disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
|
576
534
|
>
|
|
577
535
|
<Send className="w-5 h-5" />
|
|
578
536
|
</button>
|
|
579
537
|
</div>
|
|
580
538
|
|
|
581
|
-
<div className="flex items-center justify-between mt-2 text-
|
|
539
|
+
<div className="flex items-center justify-between mt-2 text-[10px] text-[var(--text-muted)]">
|
|
582
540
|
<div className="flex gap-4">
|
|
583
541
|
<span className="flex items-center gap-1">
|
|
584
|
-
<kbd className="px-1.5 py-0.5 bg-
|
|
542
|
+
<kbd className="px-1.5 py-0.5 bg-[var(--bg-elevated)] rounded text-[9px]">Enter</kbd> to send
|
|
585
543
|
</span>
|
|
586
544
|
<span className="flex items-center gap-1">
|
|
587
|
-
<kbd className="px-1.5 py-0.5 bg-
|
|
545
|
+
<kbd className="px-1.5 py-0.5 bg-[var(--bg-elevated)] rounded text-[9px]">Shift</kbd> + <kbd className="px-1.5 py-0.5 bg-[var(--bg-elevated)] rounded text-[9px]">Enter</kbd> for new line
|
|
588
546
|
</span>
|
|
589
547
|
</div>
|
|
590
548
|
<span>{input.length} characters</span>
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Agent } from '../../shared/types';
|
|
3
|
-
import {
|
|
4
|
-
import { cn } from '@/lib/utils';
|
|
5
|
-
import { VoiceCommand } from './VoiceCommand';
|
|
3
|
+
import { Minus, Square, X, Search } from 'lucide-react';
|
|
6
4
|
|
|
7
5
|
interface HeaderProps {
|
|
8
6
|
activeTab: string;
|
|
@@ -10,13 +8,9 @@ interface HeaderProps {
|
|
|
10
8
|
onSettingsClick: () => void;
|
|
11
9
|
}
|
|
12
10
|
|
|
13
|
-
export const Header: React.FC<HeaderProps> = ({ activeTab, agents
|
|
11
|
+
export const Header: React.FC<HeaderProps> = ({ activeTab, agents }) => {
|
|
14
12
|
const runningAgents = agents.filter(a => a.status === 'running').length;
|
|
15
13
|
|
|
16
|
-
const handleVoiceCommand = (transcript: string) => {
|
|
17
|
-
console.log('Voice command:', transcript);
|
|
18
|
-
};
|
|
19
|
-
|
|
20
14
|
const getTitle = () => {
|
|
21
15
|
switch (activeTab) {
|
|
22
16
|
case 'agents': return 'Agents';
|
|
@@ -28,17 +22,6 @@ export const Header: React.FC<HeaderProps> = ({ activeTab, agents, onSettingsCli
|
|
|
28
22
|
}
|
|
29
23
|
};
|
|
30
24
|
|
|
31
|
-
const getDescription = () => {
|
|
32
|
-
switch (activeTab) {
|
|
33
|
-
case 'agents': return 'Manage your AI coding agents';
|
|
34
|
-
case 'worktrees': return 'Isolated Git workspaces';
|
|
35
|
-
case 'skills': return 'Reusable AI capabilities';
|
|
36
|
-
case 'automations': return 'Scheduled tasks and workflows';
|
|
37
|
-
case 'settings': return 'Configure your preferences';
|
|
38
|
-
default: return '';
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
|
|
42
25
|
const handleMinimize = () => {
|
|
43
26
|
window.electronAPI.window.minimize();
|
|
44
27
|
};
|
|
@@ -52,34 +35,37 @@ export const Header: React.FC<HeaderProps> = ({ activeTab, agents, onSettingsCli
|
|
|
52
35
|
};
|
|
53
36
|
|
|
54
37
|
return (
|
|
55
|
-
<header
|
|
56
|
-
|
|
38
|
+
<header
|
|
39
|
+
className="h-12 border-b border-[var(--border-subtle)] bg-[var(--bg-surface)] flex items-center justify-between px-5"
|
|
40
|
+
data-testid="app-header"
|
|
41
|
+
>
|
|
42
|
+
<div className="flex items-center gap-4">
|
|
57
43
|
<div>
|
|
58
|
-
<h1
|
|
44
|
+
<h1
|
|
45
|
+
className="text-[18px] font-medium text-[var(--text-primary)] tracking-tight"
|
|
46
|
+
data-testid="page-title"
|
|
47
|
+
style={{ fontFamily: 'var(--font-display)', fontStyle: 'italic', fontWeight: 300 }}
|
|
48
|
+
>
|
|
59
49
|
{getTitle()}
|
|
60
50
|
</h1>
|
|
61
|
-
<p className="text-xs text-[var(--color-text-tertiary)]">
|
|
62
|
-
{getDescription()}
|
|
63
|
-
</p>
|
|
64
51
|
</div>
|
|
65
52
|
|
|
66
53
|
{activeTab === 'agents' && runningAgents > 0 && (
|
|
67
|
-
<div className="flex items-center gap-2 px-3 py-1
|
|
54
|
+
<div className="flex items-center gap-2 px-3 py-1 bg-[rgba(60,200,120,0.1)] border border-[rgba(60,200,120,0.2)] rounded-full text-[11px] font-medium text-[var(--success)]">
|
|
68
55
|
<span className="relative flex h-2 w-2">
|
|
69
|
-
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-
|
|
70
|
-
<span className="relative inline-flex rounded-full h-2 w-2 bg-
|
|
56
|
+
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-[var(--success)] opacity-75"></span>
|
|
57
|
+
<span className="relative inline-flex rounded-full h-2 w-2 bg-[var(--success)]"></span>
|
|
71
58
|
</span>
|
|
72
59
|
{runningAgents} running
|
|
73
60
|
</div>
|
|
74
61
|
)}
|
|
75
62
|
|
|
76
|
-
{/* Search */}
|
|
77
63
|
<div className="relative hidden md:block">
|
|
78
|
-
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-
|
|
64
|
+
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-[var(--text-muted)]" />
|
|
79
65
|
<input
|
|
80
66
|
type="text"
|
|
81
67
|
placeholder="Search..."
|
|
82
|
-
className="pl-
|
|
68
|
+
className="pl-8 pr-3 py-1.5 bg-[var(--bg-elevated)] border border-[var(--border-subtle)] rounded-[var(--radius-sm)] text-[12px] text-[var(--text-primary)] placeholder:text-[var(--text-muted)] w-56 focus:outline-none focus:border-[var(--teal-500)] transition-colors"
|
|
83
69
|
data-testid="search-input"
|
|
84
70
|
/>
|
|
85
71
|
</div>
|
|
@@ -88,26 +74,26 @@ export const Header: React.FC<HeaderProps> = ({ activeTab, agents, onSettingsCli
|
|
|
88
74
|
<div className="flex items-center gap-1">
|
|
89
75
|
<button
|
|
90
76
|
onClick={handleMinimize}
|
|
91
|
-
className="p-2 hover:bg-
|
|
77
|
+
className="p-2 hover:bg-[var(--bg-hover)] rounded-[var(--radius-sm)] text-[var(--text-muted)] hover:text-[var(--text-secondary)] transition-colors"
|
|
92
78
|
data-testid="window-minimize"
|
|
93
79
|
>
|
|
94
|
-
<Minus className="w-
|
|
80
|
+
<Minus className="w-3.5 h-3.5" />
|
|
95
81
|
</button>
|
|
96
82
|
<button
|
|
97
83
|
onClick={handleMaximize}
|
|
98
|
-
className="p-2 hover:bg-
|
|
84
|
+
className="p-2 hover:bg-[var(--bg-hover)] rounded-[var(--radius-sm)] text-[var(--text-muted)] hover:text-[var(--text-secondary)] transition-colors"
|
|
99
85
|
data-testid="window-maximize"
|
|
100
86
|
>
|
|
101
|
-
<Square className="w-
|
|
87
|
+
<Square className="w-3.5 h-3.5" />
|
|
102
88
|
</button>
|
|
103
89
|
<button
|
|
104
90
|
onClick={handleClose}
|
|
105
|
-
className="p-2 hover:bg-
|
|
91
|
+
className="p-2 hover:bg-[rgba(232,90,106,0.1)] hover:text-[var(--error)] rounded-[var(--radius-sm)] text-[var(--text-muted)] transition-colors"
|
|
106
92
|
data-testid="window-close"
|
|
107
93
|
>
|
|
108
|
-
<X className="w-
|
|
94
|
+
<X className="w-3.5 h-3.5" />
|
|
109
95
|
</button>
|
|
110
96
|
</div>
|
|
111
97
|
</header>
|
|
112
98
|
);
|
|
113
|
-
};
|
|
99
|
+
};
|