claude-ws 0.3.97 → 0.3.99
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/locales/de.json +374 -12
- package/locales/en.json +374 -12
- package/locales/es.json +398 -11
- package/locales/fr.json +398 -11
- package/locales/ja.json +398 -11
- package/locales/ko.json +398 -11
- package/locales/vi.json +374 -12
- package/locales/zh.json +398 -11
- package/package.json +1 -1
- package/server.ts +283 -6
- package/src/app/[locale]/not-found.tsx +6 -3
- package/src/app/[locale]/page.tsx +14 -4
- package/src/app/api/attempts/[id]/workflow/route.ts +76 -0
- package/src/app/api/questions/answer/route.ts +58 -0
- package/src/app/api/questions/route.ts +68 -0
- package/src/app/api/tasks/[id]/compact/route.ts +62 -0
- package/src/components/access-anywhere/api-access-key-setup-modal.tsx +2 -2
- package/src/components/access-anywhere/tunnel-settings-dialog.tsx +6 -6
- package/src/components/access-anywhere/wizard-step-ctunnel.tsx +8 -8
- package/src/components/agent-factory/dependency-tree.tsx +5 -3
- package/src/components/agent-factory/discovery-dialog.tsx +26 -22
- package/src/components/agent-factory/plugin-detail-dialog.tsx +41 -38
- package/src/components/agent-factory/plugin-form-dialog.tsx +23 -20
- package/src/components/agent-factory/plugin-list.tsx +20 -17
- package/src/components/agent-factory/upload-dialog.tsx +17 -14
- package/src/components/auth/agent-provider-dialog.tsx +67 -65
- package/src/components/auth/api-key-dialog.tsx +14 -11
- package/src/components/auth/auth-error-message.tsx +6 -3
- package/src/components/editor/code-editor-with-inline-edit.tsx +4 -2
- package/src/components/editor/file-diff-resolver-modal.tsx +31 -26
- package/src/components/editor/inline-edit-dialog.tsx +9 -6
- package/src/components/editor/selection-mention-popup.tsx +3 -1
- package/src/components/header/project-selector.tsx +7 -4
- package/src/components/header.tsx +70 -4
- package/src/components/kanban/column.tsx +11 -0
- package/src/components/kanban/task-card.tsx +70 -4
- package/src/components/project-settings/component-selector.tsx +3 -1
- package/src/components/project-settings/plugin-upload-dialog.tsx +7 -5
- package/src/components/project-settings/project-settings-dialog.tsx +5 -3
- package/src/components/questions/questions-panel.tsx +136 -0
- package/src/components/settings/folder-browser-dialog.tsx +29 -25
- package/src/components/settings/settings-page.tsx +64 -18
- package/src/components/settings/setup-dialog.tsx +26 -23
- package/src/components/setup/unified-setup-wizard.tsx +12 -9
- package/src/components/sidebar/file-browser/file-create-buttons.tsx +7 -3
- package/src/components/sidebar/file-browser/file-tab-content.tsx +19 -15
- package/src/components/sidebar/file-browser/file-tabs-panel.tsx +7 -4
- package/src/components/sidebar/file-browser/file-tree.tsx +3 -1
- package/src/components/sidebar/git-changes/branch-checkout-modal.tsx +6 -4
- package/src/components/sidebar/git-changes/commit-details-modal.tsx +5 -3
- package/src/components/sidebar/git-changes/diff-tabs-panel.tsx +3 -1
- package/src/components/sidebar/git-changes/git-file-item.tsx +8 -6
- package/src/components/sidebar/git-changes/git-graph.tsx +8 -5
- package/src/components/sidebar/git-changes/git-panel.tsx +28 -27
- package/src/components/sidebar/git-changes/git-section.tsx +5 -3
- package/src/components/sidebar/shells/shell-panel.tsx +3 -1
- package/src/components/task/attachment-bar.tsx +4 -1
- package/src/components/task/attempt-item.tsx +7 -5
- package/src/components/task/conversation-view.tsx +21 -13
- package/src/components/task/floating-chat-window.tsx +14 -5
- package/src/components/task/interactive-command/checkpoint-list.tsx +5 -3
- package/src/components/task/interactive-command/confirm-dialog.tsx +9 -4
- package/src/components/task/interactive-command/interactive-command-overlay.tsx +23 -9
- package/src/components/task/interactive-command/question-prompt.tsx +12 -8
- package/src/components/task/pending-question-indicator.tsx +5 -3
- package/src/components/task/prompt-input.tsx +1 -1
- package/src/components/task/shell-log-view.tsx +3 -1
- package/src/components/task/status-line.tsx +84 -23
- package/src/components/task/task-detail-panel.tsx +27 -27
- package/src/components/task/task-shell-indicator.tsx +10 -6
- package/src/components/terminal/terminal-context-menu.tsx +6 -4
- package/src/components/terminal/terminal-instance.tsx +11 -3
- package/src/components/terminal/terminal-panel.tsx +6 -3
- package/src/components/terminal/terminal-shortcut-bar.tsx +3 -1
- package/src/components/terminal/terminal-tab-bar.tsx +5 -3
- package/src/components/workflow/workflow-panel.tsx +181 -0
- package/src/hooks/use-attempt-stream.ts +96 -3
- package/src/lib/agent-manager.ts +89 -3
- package/src/lib/db/index.ts +18 -0
- package/src/lib/db/schema.ts +29 -0
- package/src/lib/process-manager.ts +28 -7
- package/src/lib/session-manager.ts +60 -0
- package/src/lib/usage-tracker.ts +19 -19
- package/src/lib/workflow-tracker.ts +118 -20
- package/src/stores/questions-store.ts +76 -0
- package/src/stores/workflow-store.ts +71 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useState } from 'react';
|
|
4
|
+
import { useTranslations } from 'next-intl';
|
|
4
5
|
import { AlertCircle, Settings2 } from 'lucide-react';
|
|
5
6
|
import { Button } from '@/components/ui/button';
|
|
6
7
|
import { AgentProviderDialog, isProviderAuthError } from './agent-provider-dialog';
|
|
@@ -15,6 +16,7 @@ interface AuthErrorMessageProps {
|
|
|
15
16
|
* Shows when authentication/provider errors are detected
|
|
16
17
|
*/
|
|
17
18
|
export function AuthErrorMessage({ message, className }: AuthErrorMessageProps) {
|
|
19
|
+
const t = useTranslations('auth');
|
|
18
20
|
const [dialogOpen, setDialogOpen] = useState(false);
|
|
19
21
|
|
|
20
22
|
// Only show if it's an auth-related error
|
|
@@ -29,7 +31,7 @@ export function AuthErrorMessage({ message, className }: AuthErrorMessageProps)
|
|
|
29
31
|
<AlertCircle className="h-5 w-5 text-destructive shrink-0 mt-0.5" />
|
|
30
32
|
<div className="flex-1 space-y-3">
|
|
31
33
|
<div className="text-sm text-destructive font-medium">
|
|
32
|
-
|
|
34
|
+
{t('authenticationError')}
|
|
33
35
|
</div>
|
|
34
36
|
<div className="text-sm text-muted-foreground">
|
|
35
37
|
{message}
|
|
@@ -41,7 +43,7 @@ export function AuthErrorMessage({ message, className }: AuthErrorMessageProps)
|
|
|
41
43
|
className="gap-2"
|
|
42
44
|
>
|
|
43
45
|
<Settings2 className="h-4 w-4" />
|
|
44
|
-
|
|
46
|
+
{t('configAgentProvider')}
|
|
45
47
|
</Button>
|
|
46
48
|
</div>
|
|
47
49
|
</div>
|
|
@@ -59,6 +61,7 @@ export function AuthErrorMessage({ message, className }: AuthErrorMessageProps)
|
|
|
59
61
|
* Inline button for showing in tool results or smaller contexts
|
|
60
62
|
*/
|
|
61
63
|
export function ConfigProviderButton({ className }: { className?: string }) {
|
|
64
|
+
const t = useTranslations('auth');
|
|
62
65
|
const [dialogOpen, setDialogOpen] = useState(false);
|
|
63
66
|
|
|
64
67
|
return (
|
|
@@ -70,7 +73,7 @@ export function ConfigProviderButton({ className }: { className?: string }) {
|
|
|
70
73
|
className={className}
|
|
71
74
|
>
|
|
72
75
|
<Settings2 className="h-4 w-4 mr-2" />
|
|
73
|
-
|
|
76
|
+
{t('configAgentProvider')}
|
|
74
77
|
</Button>
|
|
75
78
|
|
|
76
79
|
<AgentProviderDialog
|
|
@@ -33,6 +33,7 @@ import { useContextMentionStore } from '@/stores/context-mention-store';
|
|
|
33
33
|
import { useTaskStore } from '@/stores/task-store';
|
|
34
34
|
import { useState } from 'react';
|
|
35
35
|
import { toast } from 'sonner';
|
|
36
|
+
import { useTranslations } from 'next-intl';
|
|
36
37
|
|
|
37
38
|
interface EditorPosition {
|
|
38
39
|
lineNumber?: number;
|
|
@@ -75,6 +76,7 @@ export function CodeEditorWithInlineEdit({
|
|
|
75
76
|
onSelectionChange,
|
|
76
77
|
}: CodeEditorWithInlineEditProps) {
|
|
77
78
|
const { theme } = useTheme();
|
|
79
|
+
const t = useTranslations('editor');
|
|
78
80
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
79
81
|
const editorViewRef = useRef<EditorView | null>(null);
|
|
80
82
|
const [height, setHeight] = useState<number>(400);
|
|
@@ -340,7 +342,7 @@ export function CodeEditorWithInlineEdit({
|
|
|
340
342
|
const handleAddToContext = useCallback(
|
|
341
343
|
(selection: ContextSelection) => {
|
|
342
344
|
if (!selectedTaskId) {
|
|
343
|
-
toast.error('
|
|
345
|
+
toast.error(t('selectTaskFirst'));
|
|
344
346
|
return;
|
|
345
347
|
}
|
|
346
348
|
|
|
@@ -365,7 +367,7 @@ export function CodeEditorWithInlineEdit({
|
|
|
365
367
|
(startLine: number, endLine: number) => {
|
|
366
368
|
if (!filePath) return;
|
|
367
369
|
if (!selectedTaskId) {
|
|
368
|
-
toast.error('
|
|
370
|
+
toast.error(t('selectTaskFirst'));
|
|
369
371
|
return;
|
|
370
372
|
}
|
|
371
373
|
|
|
@@ -15,6 +15,7 @@ import { Button } from '@/components/ui/button';
|
|
|
15
15
|
import { AlertTriangle, Copy, ArrowLeft, Check, Plus } from 'lucide-react';
|
|
16
16
|
import { cn } from '@/lib/utils';
|
|
17
17
|
import { toast } from 'sonner';
|
|
18
|
+
import { useTranslations } from 'next-intl';
|
|
18
19
|
|
|
19
20
|
interface FileDiffResolverModalProps {
|
|
20
21
|
/** Whether the modal is open */
|
|
@@ -53,6 +54,8 @@ export function FileDiffResolverModal({
|
|
|
53
54
|
onKeepLocal,
|
|
54
55
|
onMerge,
|
|
55
56
|
}: FileDiffResolverModalProps) {
|
|
57
|
+
const t = useTranslations('editor');
|
|
58
|
+
|
|
56
59
|
// Working copy of local content that user can modify via inserts
|
|
57
60
|
const [workingContent, setWorkingContent] = useState(localContent);
|
|
58
61
|
const localScrollRef = useRef<HTMLDivElement>(null);
|
|
@@ -81,7 +84,7 @@ export function FileDiffResolverModal({
|
|
|
81
84
|
// Insert the remote line with >>>>> marker before it
|
|
82
85
|
lines.splice(insertAfterLocalLine, 0, '>>>>> REMOTE', remoteLine);
|
|
83
86
|
setWorkingContent(lines.join('\n'));
|
|
84
|
-
toast.success('
|
|
87
|
+
toast.success(t('lineInserted'));
|
|
85
88
|
}, [workingContent]);
|
|
86
89
|
|
|
87
90
|
// Insert all lines from a remote block
|
|
@@ -90,7 +93,7 @@ export function FileDiffResolverModal({
|
|
|
90
93
|
// Insert marker before the block and all remote lines
|
|
91
94
|
lines.splice(insertAfterLocalLine, 0, '>>>>> REMOTE', ...remoteLines, '<<<<< END REMOTE');
|
|
92
95
|
setWorkingContent(lines.join('\n'));
|
|
93
|
-
toast.success(
|
|
96
|
+
toast.success(t('linesInserted', { count: remoteLines.length }));
|
|
94
97
|
}, [workingContent]);
|
|
95
98
|
|
|
96
99
|
// Insert deletion marker for lines that exist in local but not remote
|
|
@@ -106,34 +109,34 @@ export function FileDiffResolverModal({
|
|
|
106
109
|
lines.splice(startPosition, 0, '>>>>> REMOTE DELETED');
|
|
107
110
|
|
|
108
111
|
setWorkingContent(lines.join('\n'));
|
|
109
|
-
toast.success(lineCount > 1 ? '
|
|
112
|
+
toast.success(lineCount > 1 ? t('deletionMarkersInserted') : t('deletionMarkerInserted'));
|
|
110
113
|
}, [workingContent]);
|
|
111
114
|
|
|
112
115
|
// Handle keeping local (dismiss all remote changes)
|
|
113
116
|
const handleKeepLocal = useCallback(() => {
|
|
114
117
|
onKeepLocal();
|
|
115
118
|
onClose();
|
|
116
|
-
toast.success('
|
|
119
|
+
toast.success(t('keptLocalChanges'));
|
|
117
120
|
}, [onKeepLocal, onClose]);
|
|
118
121
|
|
|
119
122
|
// Handle accepting remote entirely
|
|
120
123
|
const handleAcceptRemote = useCallback(() => {
|
|
121
124
|
onAcceptRemote();
|
|
122
125
|
onClose();
|
|
123
|
-
toast.success('
|
|
126
|
+
toast.success(t('acceptedRemoteChanges'));
|
|
124
127
|
}, [onAcceptRemote, onClose]);
|
|
125
128
|
|
|
126
129
|
// Handle applying the working (merged) content
|
|
127
130
|
const handleApplyMerged = useCallback(() => {
|
|
128
131
|
onMerge(workingContent);
|
|
129
132
|
onClose();
|
|
130
|
-
toast.success('
|
|
133
|
+
toast.success(t('appliedMergedChanges'));
|
|
131
134
|
}, [workingContent, onMerge, onClose]);
|
|
132
135
|
|
|
133
136
|
// Copy content to clipboard
|
|
134
137
|
const handleCopy = useCallback(async (content: string, label: string) => {
|
|
135
138
|
await navigator.clipboard.writeText(content);
|
|
136
|
-
toast.success(
|
|
139
|
+
toast.success(t('copiedToClipboard', { label }));
|
|
137
140
|
}, []);
|
|
138
141
|
|
|
139
142
|
// Sync scroll between panels
|
|
@@ -159,32 +162,32 @@ export function FileDiffResolverModal({
|
|
|
159
162
|
<DialogHeader>
|
|
160
163
|
<DialogTitle className="flex items-center gap-2">
|
|
161
164
|
<AlertTriangle className="size-5 text-amber-500" />
|
|
162
|
-
|
|
165
|
+
{t('fileChangedExternally')}
|
|
163
166
|
</DialogTitle>
|
|
164
167
|
<DialogDescription>
|
|
165
168
|
<span className="font-mono text-xs bg-muted px-1.5 py-0.5 rounded">{fileName}</span>
|
|
166
|
-
{' '}
|
|
169
|
+
{' '}{t('hasBeenModified')} <Plus className="inline size-3" /> {t('toInsertRemoteLines')}
|
|
167
170
|
</DialogDescription>
|
|
168
171
|
</DialogHeader>
|
|
169
172
|
|
|
170
173
|
{/* Stats bar */}
|
|
171
174
|
<div className="flex items-center gap-4 text-sm">
|
|
172
|
-
<span className="text-muted-foreground">
|
|
175
|
+
<span className="text-muted-foreground">{t('differences')}</span>
|
|
173
176
|
{hasChanges ? (
|
|
174
177
|
<>
|
|
175
178
|
<span className="text-blue-600 dark:text-blue-400">
|
|
176
|
-
{diffBlocks.filter(b => b.type === 'added').reduce((sum, b) => sum + b.remoteLines.length, 0)}
|
|
179
|
+
{diffBlocks.filter(b => b.type === 'added').reduce((sum, b) => sum + b.remoteLines.length, 0)} {t('newInRemote')}
|
|
177
180
|
</span>
|
|
178
181
|
<span className="text-green-600 dark:text-green-400">
|
|
179
|
-
{diffBlocks.filter(b => b.type === 'removed').reduce((sum, b) => sum + b.localLines.length, 0)}
|
|
182
|
+
{diffBlocks.filter(b => b.type === 'removed').reduce((sum, b) => sum + b.localLines.length, 0)} {t('onlyInLocal')}
|
|
180
183
|
</span>
|
|
181
184
|
</>
|
|
182
185
|
) : (
|
|
183
|
-
<span className="text-green-600 dark:text-green-400">✓
|
|
186
|
+
<span className="text-green-600 dark:text-green-400">✓ {t('filesIdentical')}</span>
|
|
184
187
|
)}
|
|
185
188
|
{hasLocalModifications && (
|
|
186
189
|
<span className="text-amber-600 dark:text-amber-400 ml-auto text-xs">
|
|
187
|
-
•
|
|
190
|
+
• {t('modifiedFromOriginal')}
|
|
188
191
|
</span>
|
|
189
192
|
)}
|
|
190
193
|
</div>
|
|
@@ -195,13 +198,13 @@ export function FileDiffResolverModal({
|
|
|
195
198
|
<div className="flex flex-col border rounded-md overflow-hidden">
|
|
196
199
|
<div className="flex items-center justify-between px-3 py-1.5 bg-muted/50 border-b">
|
|
197
200
|
<span className="text-xs font-medium text-muted-foreground">
|
|
198
|
-
|
|
201
|
+
{t('local')} {hasLocalModifications && t('modified')}
|
|
199
202
|
</span>
|
|
200
203
|
<Button
|
|
201
204
|
variant="ghost"
|
|
202
205
|
size="icon-sm"
|
|
203
206
|
onClick={() => handleCopy(workingContent, 'local content')}
|
|
204
|
-
title=
|
|
207
|
+
title={t('copyLocalContent')}
|
|
205
208
|
>
|
|
206
209
|
<Copy className="size-3.5" />
|
|
207
210
|
</Button>
|
|
@@ -221,12 +224,12 @@ export function FileDiffResolverModal({
|
|
|
221
224
|
{/* Remote (right) */}
|
|
222
225
|
<div className="flex flex-col border rounded-md overflow-hidden">
|
|
223
226
|
<div className="flex items-center justify-between px-3 py-1.5 bg-muted/50 border-b">
|
|
224
|
-
<span className="text-xs font-medium text-muted-foreground">
|
|
227
|
+
<span className="text-xs font-medium text-muted-foreground">{t('remoteDisk')}</span>
|
|
225
228
|
<Button
|
|
226
229
|
variant="ghost"
|
|
227
230
|
size="icon-sm"
|
|
228
231
|
onClick={() => handleCopy(remoteContent, 'remote content')}
|
|
229
|
-
title=
|
|
232
|
+
title={t('copyRemoteContent')}
|
|
230
233
|
>
|
|
231
234
|
<Copy className="size-3.5" />
|
|
232
235
|
</Button>
|
|
@@ -250,15 +253,15 @@ export function FileDiffResolverModal({
|
|
|
250
253
|
<DialogFooter className="gap-2 sm:gap-2 flex-wrap">
|
|
251
254
|
<Button variant="outline" onClick={handleKeepLocal} className="gap-2">
|
|
252
255
|
<ArrowLeft className="size-4" />
|
|
253
|
-
|
|
256
|
+
{t('keepLocalOnly')}
|
|
254
257
|
</Button>
|
|
255
258
|
<Button variant="outline" onClick={handleAcceptRemote} className="gap-2">
|
|
256
|
-
|
|
259
|
+
{t('acceptRemoteOnly')}
|
|
257
260
|
</Button>
|
|
258
261
|
{hasLocalModifications && (
|
|
259
262
|
<Button variant="default" onClick={handleApplyMerged} className="gap-2">
|
|
260
263
|
<Check className="size-4" />
|
|
261
|
-
|
|
264
|
+
{t('applyMerged')}
|
|
262
265
|
</Button>
|
|
263
266
|
)}
|
|
264
267
|
</DialogFooter>
|
|
@@ -432,6 +435,7 @@ interface DiffPanelLocalProps {
|
|
|
432
435
|
}
|
|
433
436
|
|
|
434
437
|
function DiffPanelLocal({ diffBlocks, workingLines }: DiffPanelLocalProps) {
|
|
438
|
+
const t = useTranslations('editor');
|
|
435
439
|
let lineNumber = 0;
|
|
436
440
|
|
|
437
441
|
return (
|
|
@@ -490,7 +494,7 @@ function DiffPanelLocal({ diffBlocks, workingLines }: DiffPanelLocalProps) {
|
|
|
490
494
|
className="px-2 py-0.5 flex bg-blue-500/10 text-muted-foreground/50 italic"
|
|
491
495
|
>
|
|
492
496
|
<span className="w-8 text-right mr-2 shrink-0">+</span>
|
|
493
|
-
<span className="flex-1">{block.remoteLines.length}
|
|
497
|
+
<span className="flex-1">{t('linesInRemote', { count: block.remoteLines.length })}</span>
|
|
494
498
|
</div>
|
|
495
499
|
);
|
|
496
500
|
}
|
|
@@ -512,6 +516,7 @@ interface DiffPanelRemoteProps {
|
|
|
512
516
|
}
|
|
513
517
|
|
|
514
518
|
function DiffPanelRemote({ diffBlocks, remoteLines, onInsertLine, onInsertBlock, onInsertDeletedMarker }: DiffPanelRemoteProps) {
|
|
519
|
+
const t = useTranslations('editor');
|
|
515
520
|
let lineNumber = 0;
|
|
516
521
|
let currentLocalLine = 0;
|
|
517
522
|
|
|
@@ -564,7 +569,7 @@ function DiffPanelRemote({ diffBlocks, remoteLines, onInsertLine, onInsertBlock,
|
|
|
564
569
|
size="icon-sm"
|
|
565
570
|
className="opacity-0 group-hover:opacity-100 shrink-0 ml-1 h-5 w-5"
|
|
566
571
|
onClick={() => onInsertLine(line, insertPosition)}
|
|
567
|
-
title=
|
|
572
|
+
title={t('insertLineIntoLocal')}
|
|
568
573
|
>
|
|
569
574
|
<Plus className="size-3" />
|
|
570
575
|
</Button>
|
|
@@ -587,7 +592,7 @@ function DiffPanelRemote({ diffBlocks, remoteLines, onInsertLine, onInsertBlock,
|
|
|
587
592
|
onClick={() => onInsertBlock(block.remoteLines, insertPosition)}
|
|
588
593
|
>
|
|
589
594
|
<Plus className="size-3" />
|
|
590
|
-
|
|
595
|
+
{t('insertAllLines', { count: block.remoteLines.length })}
|
|
591
596
|
</Button>
|
|
592
597
|
</div>
|
|
593
598
|
);
|
|
@@ -603,7 +608,7 @@ function DiffPanelRemote({ diffBlocks, remoteLines, onInsertLine, onInsertBlock,
|
|
|
603
608
|
className="px-2 py-0.5 flex items-center justify-between bg-green-500/10 gap-2"
|
|
604
609
|
>
|
|
605
610
|
<span className="text-muted-foreground/70 text-xs italic">
|
|
606
|
-
− {
|
|
611
|
+
− {t('linesDeletedInRemote', { count: deletedLineCount })}
|
|
607
612
|
</span>
|
|
608
613
|
<Button
|
|
609
614
|
variant="outline"
|
|
@@ -612,7 +617,7 @@ function DiffPanelRemote({ diffBlocks, remoteLines, onInsertLine, onInsertBlock,
|
|
|
612
617
|
onClick={() => onInsertDeletedMarker(insertPosition, deletedLineCount)}
|
|
613
618
|
>
|
|
614
619
|
<Plus className="size-3" />
|
|
615
|
-
|
|
620
|
+
{t('markAsDeleted')}
|
|
616
621
|
</Button>
|
|
617
622
|
</div>
|
|
618
623
|
);
|
|
@@ -12,6 +12,7 @@ import { Button } from '@/components/ui/button';
|
|
|
12
12
|
import { Input } from '@/components/ui/input';
|
|
13
13
|
import { X, Loader2, Check, RotateCcw } from 'lucide-react';
|
|
14
14
|
import { useInlineEditStore } from '@/stores/inline-edit-store';
|
|
15
|
+
import { useTranslations } from 'next-intl';
|
|
15
16
|
|
|
16
17
|
interface InlineEditDialogProps {
|
|
17
18
|
filePath: string;
|
|
@@ -21,6 +22,8 @@ interface InlineEditDialogProps {
|
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
export function InlineEditDialog({ filePath, onSubmit, onAccept, onReject }: InlineEditDialogProps) {
|
|
25
|
+
const t = useTranslations('editor');
|
|
26
|
+
const tCommon = useTranslations('common');
|
|
24
27
|
const [instruction, setInstruction] = useState('');
|
|
25
28
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
26
29
|
const popupRef = useRef<HTMLDivElement>(null);
|
|
@@ -155,7 +158,7 @@ export function InlineEditDialog({ filePath, onSubmit, onAccept, onReject }: Inl
|
|
|
155
158
|
className="flex items-center gap-1.5 text-xs bg-emerald-600/20 text-emerald-400 hover:bg-emerald-600/30 hover:text-emerald-300 font-medium px-2.5 py-1 rounded-md transition-colors"
|
|
156
159
|
>
|
|
157
160
|
<Check className="size-3" />
|
|
158
|
-
|
|
161
|
+
{t('accept')}
|
|
159
162
|
<kbd className="text-[10px] text-emerald-600 bg-emerald-950/50 px-1 py-0.5 rounded">⌘↵</kbd>
|
|
160
163
|
</button>
|
|
161
164
|
|
|
@@ -165,7 +168,7 @@ export function InlineEditDialog({ filePath, onSubmit, onAccept, onReject }: Inl
|
|
|
165
168
|
className="flex items-center gap-1.5 text-xs bg-zinc-700/30 text-zinc-400 hover:bg-zinc-700/50 hover:text-zinc-300 font-medium px-2.5 py-1 rounded-md transition-colors"
|
|
166
169
|
>
|
|
167
170
|
<RotateCcw className="size-3" />
|
|
168
|
-
|
|
171
|
+
{t('reject')}
|
|
169
172
|
<kbd className="text-[10px] text-zinc-600 bg-zinc-800/50 px-1 py-0.5 rounded">Esc</kbd>
|
|
170
173
|
</button>
|
|
171
174
|
</div>
|
|
@@ -187,7 +190,7 @@ export function InlineEditDialog({ filePath, onSubmit, onAccept, onReject }: Inl
|
|
|
187
190
|
<div className="bg-[#1e1e1e] border border-[#3c3c3c] rounded-lg shadow-xl p-2 flex items-center gap-2">
|
|
188
191
|
<Input
|
|
189
192
|
ref={inputRef}
|
|
190
|
-
placeholder=
|
|
193
|
+
placeholder={t('describeChange')}
|
|
191
194
|
value={instruction}
|
|
192
195
|
onChange={(e) => setInstruction(e.target.value)}
|
|
193
196
|
onKeyDown={handleKeyDown}
|
|
@@ -197,14 +200,14 @@ export function InlineEditDialog({ filePath, onSubmit, onAccept, onReject }: Inl
|
|
|
197
200
|
|
|
198
201
|
{error && (
|
|
199
202
|
<span className="text-xs text-red-400 truncate max-w-[80px]" title={error}>
|
|
200
|
-
|
|
203
|
+
{t('error')}
|
|
201
204
|
</span>
|
|
202
205
|
)}
|
|
203
206
|
|
|
204
207
|
{isGenerating ? (
|
|
205
208
|
<div className="flex items-center gap-2 text-zinc-400 text-xs">
|
|
206
209
|
<Loader2 className="size-4 animate-spin" />
|
|
207
|
-
<span>
|
|
210
|
+
<span>{tCommon('generating')}</span>
|
|
208
211
|
</div>
|
|
209
212
|
) : (
|
|
210
213
|
<Button
|
|
@@ -213,7 +216,7 @@ export function InlineEditDialog({ filePath, onSubmit, onAccept, onReject }: Inl
|
|
|
213
216
|
disabled={!instruction.trim()}
|
|
214
217
|
className="h-7 px-3 bg-violet-600 hover:bg-violet-700 text-white text-xs font-medium"
|
|
215
218
|
>
|
|
216
|
-
|
|
219
|
+
{tCommon('submit')}
|
|
217
220
|
</Button>
|
|
218
221
|
)}
|
|
219
222
|
|
|
@@ -4,6 +4,7 @@ import { useState, useEffect, useCallback, useRef } from 'react';
|
|
|
4
4
|
import { AtSign } from 'lucide-react';
|
|
5
5
|
import { EditorView } from '@codemirror/view';
|
|
6
6
|
import { cn } from '@/lib/utils';
|
|
7
|
+
import { useTranslations } from 'next-intl';
|
|
7
8
|
|
|
8
9
|
interface SelectionMentionPopupProps {
|
|
9
10
|
containerRef: React.RefObject<HTMLDivElement | null>;
|
|
@@ -16,6 +17,7 @@ export function SelectionMentionPopup({
|
|
|
16
17
|
editorViewRef,
|
|
17
18
|
onAddToContext,
|
|
18
19
|
}: SelectionMentionPopupProps) {
|
|
20
|
+
const t = useTranslations('editor');
|
|
19
21
|
const [visible, setVisible] = useState(false);
|
|
20
22
|
const [position, setPosition] = useState<{ x: number; y: number } | null>(null);
|
|
21
23
|
const [selection, setSelection] = useState<{ startLine: number; endLine: number } | null>(null);
|
|
@@ -142,7 +144,7 @@ export function SelectionMentionPopup({
|
|
|
142
144
|
transform: 'translateX(-100%)', // Align to right
|
|
143
145
|
}}
|
|
144
146
|
onClick={handleClick}
|
|
145
|
-
title={
|
|
147
|
+
title={t('addLinesToChat', { startLine: selection.startLine, endLine: selection.endLine })}
|
|
146
148
|
>
|
|
147
149
|
<AtSign className="size-3" />
|
|
148
150
|
<span className="text-xs font-medium">{lineRange}</span>
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
import { useProjectStore } from '@/stores/project-store';
|
|
23
23
|
import { ProjectSettingsDialog } from '@/components/project-settings/project-settings-dialog';
|
|
24
24
|
import { getFolderName } from '@/lib/utils';
|
|
25
|
+
import { useTranslations } from 'next-intl';
|
|
25
26
|
|
|
26
27
|
interface ProjectSelectorProps {
|
|
27
28
|
onAddProject?: () => void;
|
|
@@ -29,6 +30,7 @@ interface ProjectSelectorProps {
|
|
|
29
30
|
|
|
30
31
|
// Content component for reuse in mobile dropdown
|
|
31
32
|
export function ProjectSelectorContent({ onAddProject }: ProjectSelectorProps) {
|
|
33
|
+
const tCommon = useTranslations('common');
|
|
32
34
|
const {
|
|
33
35
|
projects,
|
|
34
36
|
selectedProjectIds,
|
|
@@ -76,7 +78,7 @@ export function ProjectSelectorContent({ onAddProject }: ProjectSelectorProps) {
|
|
|
76
78
|
onCheckedChange={() => selectAllProjects()}
|
|
77
79
|
className="px-2 py-1.5"
|
|
78
80
|
>
|
|
79
|
-
|
|
81
|
+
{tCommon('allProjects')}
|
|
80
82
|
</DropdownMenuCheckboxItem>
|
|
81
83
|
<DropdownMenuSeparator />
|
|
82
84
|
|
|
@@ -84,7 +86,7 @@ export function ProjectSelectorContent({ onAddProject }: ProjectSelectorProps) {
|
|
|
84
86
|
<div className="max-h-[180px] overflow-y-auto">
|
|
85
87
|
{projects.length === 0 ? (
|
|
86
88
|
<div className="px-2 py-1.5 text-sm text-muted-foreground">
|
|
87
|
-
|
|
89
|
+
{tCommon('noProjectsYet')}
|
|
88
90
|
</div>
|
|
89
91
|
) : (
|
|
90
92
|
projects.map((project) => {
|
|
@@ -195,6 +197,7 @@ export function ProjectSelectorContent({ onAddProject }: ProjectSelectorProps) {
|
|
|
195
197
|
}
|
|
196
198
|
|
|
197
199
|
export function ProjectSelector({ onAddProject }: ProjectSelectorProps) {
|
|
200
|
+
const tCommon = useTranslations('common');
|
|
198
201
|
const {
|
|
199
202
|
projects,
|
|
200
203
|
selectedProjectIds,
|
|
@@ -203,11 +206,11 @@ export function ProjectSelector({ onAddProject }: ProjectSelectorProps) {
|
|
|
203
206
|
|
|
204
207
|
// Compute display text
|
|
205
208
|
const allMode = isAllProjectsMode();
|
|
206
|
-
let displayText = '
|
|
209
|
+
let displayText = tCommon('allProjects');
|
|
207
210
|
if (!allMode) {
|
|
208
211
|
if (selectedProjectIds.length === 1) {
|
|
209
212
|
const project = projects.find(p => p.id === selectedProjectIds[0]);
|
|
210
|
-
displayText = project ? getFolderName(project.name) : '
|
|
213
|
+
displayText = project ? getFolderName(project.name) : tCommon('selectProject');
|
|
211
214
|
} else if (selectedProjectIds.length > 1) {
|
|
212
215
|
displayText = `${selectedProjectIds.length} projects`;
|
|
213
216
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { useState } from 'react';
|
|
4
|
-
import { Settings, Plus, Search, PanelLeft, PanelRight, FolderTree, Terminal, X } from 'lucide-react';
|
|
3
|
+
import { useState, useEffect } from 'react';
|
|
4
|
+
import { Settings, Plus, Search, PanelLeft, PanelRight, FolderTree, MessageCircleQuestion, Network, Terminal, X } from 'lucide-react';
|
|
5
5
|
import Image from 'next/image';
|
|
6
6
|
import { Button } from '@/components/ui/button';
|
|
7
7
|
import { Input } from '@/components/ui/input';
|
|
@@ -25,6 +25,8 @@ import { useTerminalStore } from '@/stores/terminal-store';
|
|
|
25
25
|
import { useProjectStore } from '@/stores/project-store';
|
|
26
26
|
import { useSettingsUIStore } from '@/stores/settings-ui-store';
|
|
27
27
|
import { ProjectSelector, ProjectSelectorContent } from '@/components/header/project-selector';
|
|
28
|
+
import { useQuestionsStore } from '@/stores/questions-store';
|
|
29
|
+
import { useWorkflowStore } from '@/stores/workflow-store';
|
|
28
30
|
import { useTranslations } from 'next-intl';
|
|
29
31
|
|
|
30
32
|
interface HeaderProps {
|
|
@@ -43,7 +45,16 @@ export function Header({ onCreateTask, onAddProject, searchQuery: externalSearch
|
|
|
43
45
|
const { setOpen: setSettingsOpen } = useSettingsUIStore();
|
|
44
46
|
const { isOpen: terminalOpen, togglePanel: toggleTerminal } = useTerminalStore();
|
|
45
47
|
const { activeProjectId, selectedProjectIds } = useProjectStore();
|
|
48
|
+
const { pendingQuestions, fetchQuestions, isOpen: questionsPanelOpen, togglePanel: toggleQuestionsPanel } = useQuestionsStore();
|
|
49
|
+
const questionCount = pendingQuestions.size;
|
|
50
|
+
const { isOpen: workflowPanelOpen, togglePanel: toggleWorkflowPanel, getActiveAgentCount } = useWorkflowStore();
|
|
51
|
+
const activeAgentCount = getActiveAgentCount();
|
|
46
52
|
const [searchOpen, setSearchOpen] = useState(false);
|
|
53
|
+
|
|
54
|
+
// Fetch pending questions on mount
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
fetchQuestions(selectedProjectIds);
|
|
57
|
+
}, [selectedProjectIds.join(',')]);
|
|
47
58
|
const [internalSearchQuery, setInternalSearchQuery] = useState('');
|
|
48
59
|
const searchQuery = externalSearchQuery !== undefined ? externalSearchQuery : internalSearchQuery;
|
|
49
60
|
const setSearchQuery = onSearchChange || setInternalSearchQuery;
|
|
@@ -102,7 +113,7 @@ export function Header({ onCreateTask, onAddProject, searchQuery: externalSearch
|
|
|
102
113
|
<button
|
|
103
114
|
onClick={() => setSearchQuery('')}
|
|
104
115
|
className="absolute right-2 top-2 h-5 w-5 flex items-center justify-center rounded-sm hover:bg-muted-foreground/20 transition-colors"
|
|
105
|
-
aria-label=
|
|
116
|
+
aria-label={t('clearSearch')}
|
|
106
117
|
>
|
|
107
118
|
<X className="h-3.5 w-3.5 text-muted-foreground" />
|
|
108
119
|
</button>
|
|
@@ -161,6 +172,61 @@ export function Header({ onCreateTask, onAddProject, searchQuery: externalSearch
|
|
|
161
172
|
</div>
|
|
162
173
|
</div>
|
|
163
174
|
|
|
175
|
+
{/* Questions panel toggle */}
|
|
176
|
+
<TooltipProvider>
|
|
177
|
+
<Tooltip>
|
|
178
|
+
<TooltipTrigger asChild>
|
|
179
|
+
<Button
|
|
180
|
+
variant={questionsPanelOpen ? 'secondary' : 'ghost'}
|
|
181
|
+
size="icon"
|
|
182
|
+
onClick={() => {
|
|
183
|
+
fetchQuestions(selectedProjectIds);
|
|
184
|
+
toggleQuestionsPanel();
|
|
185
|
+
}}
|
|
186
|
+
className="shrink-0 relative"
|
|
187
|
+
>
|
|
188
|
+
<MessageCircleQuestion className="h-4 w-4" />
|
|
189
|
+
{questionCount > 0 && (
|
|
190
|
+
<span className="absolute -top-1 -right-1 h-4 min-w-4 px-1 flex items-center justify-center text-[10px] font-medium bg-amber-500 text-white rounded-full">
|
|
191
|
+
{questionCount}
|
|
192
|
+
</span>
|
|
193
|
+
)}
|
|
194
|
+
</Button>
|
|
195
|
+
</TooltipTrigger>
|
|
196
|
+
<TooltipContent>
|
|
197
|
+
<p>
|
|
198
|
+
Pending questions{questionCount > 0 ? ` (${questionCount})` : ''}
|
|
199
|
+
</p>
|
|
200
|
+
</TooltipContent>
|
|
201
|
+
</Tooltip>
|
|
202
|
+
</TooltipProvider>
|
|
203
|
+
|
|
204
|
+
{/* Workflow panel toggle */}
|
|
205
|
+
<TooltipProvider>
|
|
206
|
+
<Tooltip>
|
|
207
|
+
<TooltipTrigger asChild>
|
|
208
|
+
<Button
|
|
209
|
+
variant={workflowPanelOpen ? 'secondary' : 'ghost'}
|
|
210
|
+
size="icon"
|
|
211
|
+
onClick={toggleWorkflowPanel}
|
|
212
|
+
className="shrink-0 relative"
|
|
213
|
+
>
|
|
214
|
+
<Network className="h-4 w-4" />
|
|
215
|
+
{activeAgentCount > 0 && (
|
|
216
|
+
<span className="absolute -top-1 -right-1 h-4 min-w-4 px-1 flex items-center justify-center text-[10px] font-medium bg-blue-500 text-white rounded-full">
|
|
217
|
+
{activeAgentCount}
|
|
218
|
+
</span>
|
|
219
|
+
)}
|
|
220
|
+
</Button>
|
|
221
|
+
</TooltipTrigger>
|
|
222
|
+
<TooltipContent>
|
|
223
|
+
<p>
|
|
224
|
+
Agent workflow{activeAgentCount > 0 ? ` (${activeAgentCount})` : ''}
|
|
225
|
+
</p>
|
|
226
|
+
</TooltipContent>
|
|
227
|
+
</Tooltip>
|
|
228
|
+
</TooltipProvider>
|
|
229
|
+
|
|
164
230
|
{/* Terminal toggle */}
|
|
165
231
|
<TooltipProvider>
|
|
166
232
|
<Tooltip>
|
|
@@ -226,7 +292,7 @@ export function Header({ onCreateTask, onAddProject, searchQuery: externalSearch
|
|
|
226
292
|
<button
|
|
227
293
|
onClick={() => setSearchQuery('')}
|
|
228
294
|
className="absolute right-2 top-2 h-5 w-5 flex items-center justify-center rounded-sm hover:bg-muted-foreground/20 transition-colors"
|
|
229
|
-
aria-label=
|
|
295
|
+
aria-label={t('clearSearch')}
|
|
230
296
|
>
|
|
231
297
|
<X className="h-3.5 w-3.5 text-muted-foreground" />
|
|
232
298
|
</button>
|
|
@@ -62,6 +62,17 @@ export function Column({ status, title, tasks, attemptCounts = new Map(), onCrea
|
|
|
62
62
|
<span className="text-xs text-muted-foreground bg-muted px-2 py-1 rounded-full">
|
|
63
63
|
{tasks.length}
|
|
64
64
|
</span>
|
|
65
|
+
{isArchiveColumn && tasks.length > 0 && (
|
|
66
|
+
<Button
|
|
67
|
+
variant="ghost"
|
|
68
|
+
size="sm"
|
|
69
|
+
className="h-6 w-6 p-0 text-muted-foreground hover:text-destructive hover:bg-destructive/10"
|
|
70
|
+
onClick={handleEmptyColumn}
|
|
71
|
+
title={t('deleteAllTasks', { count: tasks.length, status: title })}
|
|
72
|
+
>
|
|
73
|
+
<Trash2 className="h-3 w-3" />
|
|
74
|
+
</Button>
|
|
75
|
+
)}
|
|
65
76
|
{isTodoColumn && onCreateTask && (
|
|
66
77
|
<Button
|
|
67
78
|
variant="default"
|