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
|
@@ -9,6 +9,7 @@ import { useResizable } from '@/hooks/use-resizable';
|
|
|
9
9
|
import { useSidebarStore } from '@/stores/sidebar-store';
|
|
10
10
|
import { usePanelLayoutStore, PANEL_CONFIGS } from '@/stores/panel-layout-store';
|
|
11
11
|
import { cn } from '@/lib/utils';
|
|
12
|
+
import { useTranslations } from 'next-intl';
|
|
12
13
|
|
|
13
14
|
const { minWidth: MIN_WIDTH, maxWidth: MAX_WIDTH } = PANEL_CONFIGS.filePreview;
|
|
14
15
|
|
|
@@ -35,6 +36,8 @@ function trimFileName(fileName: string, maxLength = 25): string {
|
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
export function FileTabsPanel() {
|
|
39
|
+
const tCommon = useTranslations('common');
|
|
40
|
+
const tEditor = useTranslations('editor');
|
|
38
41
|
const {
|
|
39
42
|
openTabs,
|
|
40
43
|
activeTabId,
|
|
@@ -59,7 +62,7 @@ export function FileTabsPanel() {
|
|
|
59
62
|
const tab = openTabs.find(t => t.id === tabId);
|
|
60
63
|
if (tab?.isDirty) {
|
|
61
64
|
const fileName = tab.filePath.split('/').pop() || tab.filePath;
|
|
62
|
-
if (!confirm(
|
|
65
|
+
if (!confirm(tCommon('unsavedChangesConfirm', { fileName }))) {
|
|
63
66
|
return;
|
|
64
67
|
}
|
|
65
68
|
}
|
|
@@ -71,7 +74,7 @@ export function FileTabsPanel() {
|
|
|
71
74
|
const dirtyTabs = openTabs.filter(t => t.isDirty);
|
|
72
75
|
if (dirtyTabs.length > 0) {
|
|
73
76
|
const fileNames = dirtyTabs.map(t => t.filePath.split('/').pop()).join(', ');
|
|
74
|
-
if (!confirm(
|
|
77
|
+
if (!confirm(tCommon('unsavedChangesConfirm', { fileName: fileNames }))) {
|
|
75
78
|
return;
|
|
76
79
|
}
|
|
77
80
|
}
|
|
@@ -159,9 +162,9 @@ export function FileTabsPanel() {
|
|
|
159
162
|
size="sm"
|
|
160
163
|
onClick={handleCloseAllTabs}
|
|
161
164
|
className="text-xs text-muted-foreground h-8 px-2 mr-1"
|
|
162
|
-
title=
|
|
165
|
+
title={tEditor('closeAllTabs')}
|
|
163
166
|
>
|
|
164
|
-
|
|
167
|
+
{tEditor('closeAllTabs')}
|
|
165
168
|
</Button>
|
|
166
169
|
)}
|
|
167
170
|
</div>
|
|
@@ -10,6 +10,7 @@ import { useSidebarStore } from '@/stores/sidebar-store';
|
|
|
10
10
|
import { useActiveProject } from '@/hooks/use-active-project';
|
|
11
11
|
import type { FileEntry } from '@/types';
|
|
12
12
|
import { FileCreateButtons } from './file-create-buttons';
|
|
13
|
+
import { useTranslations } from 'next-intl';
|
|
13
14
|
|
|
14
15
|
interface FileTreeProps {
|
|
15
16
|
onFileSelect?: (path: string, lineNumber?: number, column?: number, matchLength?: number) => void;
|
|
@@ -17,6 +18,7 @@ interface FileTreeProps {
|
|
|
17
18
|
|
|
18
19
|
export function FileTree({ onFileSelect }: FileTreeProps) {
|
|
19
20
|
const activeProject = useActiveProject();
|
|
21
|
+
const tSidebar = useTranslations('sidebar');
|
|
20
22
|
const { expandedFolders, toggleFolder, selectedFile, setSelectedFile, openTab, setEditorPosition } =
|
|
21
23
|
useSidebarStore();
|
|
22
24
|
|
|
@@ -183,7 +185,7 @@ export function FileTree({ onFileSelect }: FileTreeProps) {
|
|
|
183
185
|
if (!activeProject) {
|
|
184
186
|
return (
|
|
185
187
|
<div className="flex items-center justify-center h-full text-muted-foreground text-sm">
|
|
186
|
-
|
|
188
|
+
{tSidebar('selectProject')}
|
|
187
189
|
</div>
|
|
188
190
|
);
|
|
189
191
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect, useCallback } from 'react';
|
|
4
4
|
import { Loader2, GitBranch, Check } from 'lucide-react';
|
|
5
|
+
import { useTranslations } from 'next-intl';
|
|
5
6
|
import {
|
|
6
7
|
Dialog,
|
|
7
8
|
DialogContent,
|
|
@@ -41,6 +42,7 @@ export function BranchCheckoutModal({
|
|
|
41
42
|
currentBranch,
|
|
42
43
|
onCheckout,
|
|
43
44
|
}: BranchCheckoutModalProps) {
|
|
45
|
+
const t = useTranslations('git');
|
|
44
46
|
const [search, setSearch] = useState('');
|
|
45
47
|
const [branches, setBranches] = useState<{ local: Branch[]; remote: Branch[] }>({
|
|
46
48
|
local: [],
|
|
@@ -59,7 +61,7 @@ export function BranchCheckoutModal({
|
|
|
59
61
|
const res = await fetch(`/api/git/branches?path=${encodeURIComponent(projectPath)}`);
|
|
60
62
|
if (!res.ok) {
|
|
61
63
|
const data = await res.json();
|
|
62
|
-
throw new Error(data.error || '
|
|
64
|
+
throw new Error(data.error || t('failedToFetchBranches'));
|
|
63
65
|
}
|
|
64
66
|
const data = await res.json();
|
|
65
67
|
setBranches({
|
|
@@ -67,7 +69,7 @@ export function BranchCheckoutModal({
|
|
|
67
69
|
remote: data.remoteBranches || [],
|
|
68
70
|
});
|
|
69
71
|
} catch (err) {
|
|
70
|
-
setError(err instanceof Error ? err.message : '
|
|
72
|
+
setError(err instanceof Error ? err.message : t('failedToFetchBranches'));
|
|
71
73
|
} finally {
|
|
72
74
|
setLoading(false);
|
|
73
75
|
}
|
|
@@ -116,7 +118,7 @@ export function BranchCheckoutModal({
|
|
|
116
118
|
|
|
117
119
|
<Command className="rounded-t-none border-t">
|
|
118
120
|
<CommandInput
|
|
119
|
-
placeholder=
|
|
121
|
+
placeholder={t('searchBranches')}
|
|
120
122
|
value={search}
|
|
121
123
|
onValueChange={setSearch}
|
|
122
124
|
/>
|
|
@@ -138,7 +140,7 @@ export function BranchCheckoutModal({
|
|
|
138
140
|
) : (
|
|
139
141
|
<>
|
|
140
142
|
{filteredLocalBranches.length === 0 && filteredRemoteBranches.length === 0 ? (
|
|
141
|
-
<CommandEmpty>
|
|
143
|
+
<CommandEmpty>{t('noBranches')}</CommandEmpty>
|
|
142
144
|
) : (
|
|
143
145
|
<>
|
|
144
146
|
{filteredLocalBranches.length > 0 && (
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useEffect, useState, useCallback } from 'react';
|
|
4
4
|
import { Loader2, Copy, Check, FileIcon, FilePlus, FileMinus, ArrowLeft, GitBranch, GitCommit, AlertCircle } from 'lucide-react';
|
|
5
|
+
import { useTranslations } from 'next-intl';
|
|
5
6
|
import {
|
|
6
7
|
Dialog,
|
|
7
8
|
DialogContent,
|
|
@@ -34,6 +35,7 @@ export function CommitDetailsModal({
|
|
|
34
35
|
commitHash,
|
|
35
36
|
projectPath,
|
|
36
37
|
}: CommitDetailsModalProps) {
|
|
38
|
+
const t = useTranslations('git');
|
|
37
39
|
const [details, setDetails] = useState<CommitDetails | null>(null);
|
|
38
40
|
const [loading, setLoading] = useState(false);
|
|
39
41
|
const [error, setError] = useState<string | null>(null);
|
|
@@ -210,7 +212,7 @@ export function CommitDetailsModal({
|
|
|
210
212
|
<button
|
|
211
213
|
onClick={copyHash}
|
|
212
214
|
className="p-1 hover:bg-accent rounded transition-colors"
|
|
213
|
-
title=
|
|
215
|
+
title={t('copyFullHash')}
|
|
214
216
|
>
|
|
215
217
|
{copied ? (
|
|
216
218
|
<Check className="size-3.5 text-green-500" />
|
|
@@ -228,7 +230,7 @@ export function CommitDetailsModal({
|
|
|
228
230
|
className="h-7 text-xs"
|
|
229
231
|
onClick={handleCheckout}
|
|
230
232
|
disabled={checkoutLoading}
|
|
231
|
-
title=
|
|
233
|
+
title={t('checkoutCommit')}
|
|
232
234
|
>
|
|
233
235
|
{checkoutLoading ? (
|
|
234
236
|
<Loader2 className="size-3.5 animate-spin mr-1.5" />
|
|
@@ -242,7 +244,7 @@ export function CommitDetailsModal({
|
|
|
242
244
|
size="sm"
|
|
243
245
|
className="h-7 text-xs"
|
|
244
246
|
onClick={() => setShowBranchDialog(true)}
|
|
245
|
-
title=
|
|
247
|
+
title={t('createBranchFromCommit')}
|
|
246
248
|
>
|
|
247
249
|
<GitBranch className="size-3.5 mr-1.5" />
|
|
248
250
|
New Branch
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useRef } from 'react';
|
|
4
4
|
import { X } from 'lucide-react';
|
|
5
|
+
import { useTranslations } from 'next-intl';
|
|
5
6
|
import { Button } from '@/components/ui/button';
|
|
6
7
|
import { ResizeHandle } from '@/components/ui/resize-handle';
|
|
7
8
|
import { DiffViewer } from './diff-viewer';
|
|
@@ -13,6 +14,7 @@ import { cn } from '@/lib/utils';
|
|
|
13
14
|
const { minWidth: MIN_WIDTH, maxWidth: MAX_WIDTH } = PANEL_CONFIGS.diffPreview;
|
|
14
15
|
|
|
15
16
|
export function DiffTabsPanel() {
|
|
17
|
+
const t = useTranslations('git');
|
|
16
18
|
const {
|
|
17
19
|
diffTabs,
|
|
18
20
|
activeDiffTabId,
|
|
@@ -115,7 +117,7 @@ export function DiffTabsPanel() {
|
|
|
115
117
|
size="sm"
|
|
116
118
|
onClick={closeAllDiffTabs}
|
|
117
119
|
className="text-xs text-muted-foreground h-8 px-2 mr-1"
|
|
118
|
-
title=
|
|
120
|
+
title={t('closeAllTabs')}
|
|
119
121
|
>
|
|
120
122
|
Close all
|
|
121
123
|
</Button>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { Plus, Minus, Undo2, EyeOff } from 'lucide-react';
|
|
4
|
+
import { useTranslations } from 'next-intl';
|
|
4
5
|
import { cn } from '@/lib/utils';
|
|
5
6
|
import { FileIcon } from '@/components/sidebar/file-browser/file-icon';
|
|
6
7
|
import type { GitFileStatus } from '@/types';
|
|
@@ -50,6 +51,7 @@ export function GitFileItem({
|
|
|
50
51
|
onDiscard,
|
|
51
52
|
onAddToGitignore,
|
|
52
53
|
}: GitFileItemProps) {
|
|
54
|
+
const t = useTranslations('git');
|
|
53
55
|
// Get filename and parent directory
|
|
54
56
|
const parts = file.path.split('/');
|
|
55
57
|
const fileName = parts.pop() || file.path;
|
|
@@ -90,7 +92,7 @@ export function GitFileItem({
|
|
|
90
92
|
e.stopPropagation();
|
|
91
93
|
onAddToGitignore();
|
|
92
94
|
}}
|
|
93
|
-
title=
|
|
95
|
+
title={t('addToGitignore')}
|
|
94
96
|
>
|
|
95
97
|
<EyeOff className="size-3.5" />
|
|
96
98
|
</button>
|
|
@@ -102,7 +104,7 @@ export function GitFileItem({
|
|
|
102
104
|
e.stopPropagation();
|
|
103
105
|
onUnstage?.();
|
|
104
106
|
}}
|
|
105
|
-
title=
|
|
107
|
+
title={t('unstageChanges')}
|
|
106
108
|
>
|
|
107
109
|
<Minus className="size-3.5" />
|
|
108
110
|
</button>
|
|
@@ -116,7 +118,7 @@ export function GitFileItem({
|
|
|
116
118
|
e.stopPropagation();
|
|
117
119
|
onDiscard?.();
|
|
118
120
|
}}
|
|
119
|
-
title=
|
|
121
|
+
title={t('discardChanges')}
|
|
120
122
|
>
|
|
121
123
|
<Undo2 className="size-3.5" />
|
|
122
124
|
</button>
|
|
@@ -128,7 +130,7 @@ export function GitFileItem({
|
|
|
128
130
|
e.stopPropagation();
|
|
129
131
|
onAddToGitignore();
|
|
130
132
|
}}
|
|
131
|
-
title=
|
|
133
|
+
title={t('addToGitignore')}
|
|
132
134
|
>
|
|
133
135
|
<EyeOff className="size-3.5" />
|
|
134
136
|
</button>
|
|
@@ -140,7 +142,7 @@ export function GitFileItem({
|
|
|
140
142
|
e.stopPropagation();
|
|
141
143
|
onStage?.();
|
|
142
144
|
}}
|
|
143
|
-
title=
|
|
145
|
+
title={t('stageChanges')}
|
|
144
146
|
>
|
|
145
147
|
<Plus className="size-3.5" />
|
|
146
148
|
</button>
|
|
@@ -151,7 +153,7 @@ export function GitFileItem({
|
|
|
151
153
|
{/* Stats: +X -Y for modified, "New" for new files - absolute positioned */}
|
|
152
154
|
<span className="absolute right-2 top-1/2 -translate-y-1/2 text-[11px] shrink-0 font-medium group-hover:opacity-0 transition-opacity">
|
|
153
155
|
{isNew ? (
|
|
154
|
-
<span className="text-green-500">
|
|
156
|
+
<span className="text-green-500">{t('newFile')}</span>
|
|
155
157
|
) : hasStats ? (
|
|
156
158
|
<>
|
|
157
159
|
<span className="text-green-500">+{file.additions || 0}</span>
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect, useCallback, useMemo } from 'react';
|
|
4
4
|
import { ChevronRight, ChevronDown, RefreshCw, Loader2, ArrowUpFromLine, ArrowDownToLine, RotateCcw } from 'lucide-react';
|
|
5
|
+
import { useTranslations } from 'next-intl';
|
|
5
6
|
import { GitCommitItem } from './git-commit-item';
|
|
6
7
|
import { GraphRenderer } from './graph-renderer';
|
|
7
8
|
import { CommitDetailsModal } from './commit-details-modal';
|
|
@@ -23,6 +24,8 @@ interface GitCommit {
|
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
export function GitGraph() {
|
|
27
|
+
const t = useTranslations('git');
|
|
28
|
+
const tCommon = useTranslations('common');
|
|
26
29
|
const activeProject = useActiveProject();
|
|
27
30
|
const [commits, setCommits] = useState<GitCommit[]>([]);
|
|
28
31
|
const [head, setHead] = useState<string>('');
|
|
@@ -129,7 +132,7 @@ export function GitGraph() {
|
|
|
129
132
|
e.stopPropagation();
|
|
130
133
|
setFilter(filter === 'current' ? 'all' : 'current');
|
|
131
134
|
}}
|
|
132
|
-
title={filter === 'current' ? '
|
|
135
|
+
title={filter === 'current' ? t('showAllBranches') : t('showCurrentBranchOnly')}
|
|
133
136
|
>
|
|
134
137
|
<svg className="size-3.5" viewBox="0 0 16 16" fill="currentColor">
|
|
135
138
|
{filter === 'current' ? (
|
|
@@ -147,7 +150,7 @@ export function GitGraph() {
|
|
|
147
150
|
gitAction('fetch');
|
|
148
151
|
}}
|
|
149
152
|
disabled={actionLoading !== null}
|
|
150
|
-
title=
|
|
153
|
+
title={t('fetch')}
|
|
151
154
|
>
|
|
152
155
|
<ArrowDownToLine className={cn('size-3.5', actionLoading === 'fetch' && 'animate-pulse')} />
|
|
153
156
|
</button>
|
|
@@ -159,7 +162,7 @@ export function GitGraph() {
|
|
|
159
162
|
gitAction('pull');
|
|
160
163
|
}}
|
|
161
164
|
disabled={actionLoading !== null}
|
|
162
|
-
title=
|
|
165
|
+
title={t('pull')}
|
|
163
166
|
>
|
|
164
167
|
<RotateCcw className={cn('size-3.5', actionLoading === 'pull' && 'animate-spin')} />
|
|
165
168
|
</button>
|
|
@@ -171,7 +174,7 @@ export function GitGraph() {
|
|
|
171
174
|
gitAction('push');
|
|
172
175
|
}}
|
|
173
176
|
disabled={actionLoading !== null}
|
|
174
|
-
title=
|
|
177
|
+
title={t('push')}
|
|
175
178
|
>
|
|
176
179
|
<ArrowUpFromLine className={cn('size-3.5', actionLoading === 'push' && 'animate-pulse')} />
|
|
177
180
|
</button>
|
|
@@ -182,7 +185,7 @@ export function GitGraph() {
|
|
|
182
185
|
e.stopPropagation();
|
|
183
186
|
fetchLog();
|
|
184
187
|
}}
|
|
185
|
-
title=
|
|
188
|
+
title={tCommon('refresh')}
|
|
186
189
|
>
|
|
187
190
|
<RefreshCw className={cn('size-3.5', loading && 'animate-spin')} />
|
|
188
191
|
</button>
|
|
@@ -17,6 +17,7 @@ import type { GitStatus, GitFileStatus } from '@/types';
|
|
|
17
17
|
|
|
18
18
|
export function GitPanel() {
|
|
19
19
|
const t = useTranslations('git');
|
|
20
|
+
const tCommon = useTranslations('common');
|
|
20
21
|
const activeProject = useActiveProject();
|
|
21
22
|
const { openDiffTab } = useSidebarStore();
|
|
22
23
|
const [status, setStatus] = useState<GitStatus | null>(null);
|
|
@@ -133,7 +134,7 @@ export function GitPanel() {
|
|
|
133
134
|
|
|
134
135
|
const discardFile = useCallback(async (filePath: string) => {
|
|
135
136
|
if (!activeProject?.path) return;
|
|
136
|
-
if (!confirm(
|
|
137
|
+
if (!confirm(t('discardChangesConfirm', { filePath }))) return;
|
|
137
138
|
try {
|
|
138
139
|
await fetch('/api/git/discard', {
|
|
139
140
|
method: 'POST',
|
|
@@ -176,7 +177,7 @@ export function GitPanel() {
|
|
|
176
177
|
|
|
177
178
|
const discardAll = useCallback(async () => {
|
|
178
179
|
if (!activeProject?.path) return;
|
|
179
|
-
if (!confirm('
|
|
180
|
+
if (!confirm(t('discardAllConfirm'))) return;
|
|
180
181
|
try {
|
|
181
182
|
await fetch('/api/git/discard', {
|
|
182
183
|
method: 'POST',
|
|
@@ -199,12 +200,12 @@ export function GitPanel() {
|
|
|
199
200
|
});
|
|
200
201
|
if (!res.ok) {
|
|
201
202
|
const data = await res.json();
|
|
202
|
-
throw new Error(data.error || '
|
|
203
|
+
throw new Error(data.error || t('failedToAddGitignore'));
|
|
203
204
|
}
|
|
204
205
|
fetchStatus(true);
|
|
205
206
|
} catch (err) {
|
|
206
207
|
console.error('Failed to add to .gitignore:', err);
|
|
207
|
-
alert(err instanceof Error ? err.message : '
|
|
208
|
+
alert(err instanceof Error ? err.message : t('failedToAddGitignore'));
|
|
208
209
|
}
|
|
209
210
|
}, [activeProject?.path, fetchStatus]);
|
|
210
211
|
|
|
@@ -231,13 +232,13 @@ export function GitPanel() {
|
|
|
231
232
|
});
|
|
232
233
|
if (!res.ok) {
|
|
233
234
|
const data = await res.json();
|
|
234
|
-
throw new Error(data.error || '
|
|
235
|
+
throw new Error(data.error || t('failedToCommit'));
|
|
235
236
|
}
|
|
236
237
|
setCommitTitle('');
|
|
237
238
|
setCommitDescription('');
|
|
238
239
|
fetchStatus(true);
|
|
239
240
|
} catch (err) {
|
|
240
|
-
alert(err instanceof Error ? err.message : '
|
|
241
|
+
alert(err instanceof Error ? err.message : t('failedToCommit'));
|
|
241
242
|
} finally {
|
|
242
243
|
setCommitting(false);
|
|
243
244
|
}
|
|
@@ -256,7 +257,7 @@ export function GitPanel() {
|
|
|
256
257
|
|
|
257
258
|
if (!res.ok) {
|
|
258
259
|
const data = await res.json();
|
|
259
|
-
throw new Error(data.error || '
|
|
260
|
+
throw new Error(data.error || t('failedToGenerateCommit'));
|
|
260
261
|
}
|
|
261
262
|
|
|
262
263
|
const { title, description } = await res.json();
|
|
@@ -264,7 +265,7 @@ export function GitPanel() {
|
|
|
264
265
|
setCommitDescription(description || '');
|
|
265
266
|
} catch (err) {
|
|
266
267
|
console.error('AI generation error:', err);
|
|
267
|
-
alert(err instanceof Error ? err.message : '
|
|
268
|
+
alert(err instanceof Error ? err.message : t('failedToGenerateCommit'));
|
|
268
269
|
} finally {
|
|
269
270
|
setGeneratingMessage(false);
|
|
270
271
|
}
|
|
@@ -281,11 +282,11 @@ export function GitPanel() {
|
|
|
281
282
|
});
|
|
282
283
|
if (!res.ok) {
|
|
283
284
|
const data = await res.json();
|
|
284
|
-
throw new Error(data.error || '
|
|
285
|
+
throw new Error(data.error || t('failedToPush'));
|
|
285
286
|
}
|
|
286
287
|
fetchStatus(true);
|
|
287
288
|
} catch (err) {
|
|
288
|
-
alert(err instanceof Error ? err.message : '
|
|
289
|
+
alert(err instanceof Error ? err.message : t('failedToPush'));
|
|
289
290
|
} finally {
|
|
290
291
|
setSyncing(false);
|
|
291
292
|
}
|
|
@@ -334,7 +335,7 @@ export function GitPanel() {
|
|
|
334
335
|
<div className="flex flex-col items-center justify-center h-full gap-2 p-4">
|
|
335
336
|
<p className="text-sm text-destructive text-center">{error}</p>
|
|
336
337
|
<Button variant="outline" size="sm" onClick={() => fetchStatus(true)}>
|
|
337
|
-
|
|
338
|
+
{tCommon('retry')}
|
|
338
339
|
</Button>
|
|
339
340
|
</div>
|
|
340
341
|
);
|
|
@@ -343,7 +344,7 @@ export function GitPanel() {
|
|
|
343
344
|
if (!activeProject) {
|
|
344
345
|
return (
|
|
345
346
|
<div className="flex items-center justify-center h-full text-muted-foreground text-sm">
|
|
346
|
-
|
|
347
|
+
{tCommon('noProjectsConfigured')}
|
|
347
348
|
</div>
|
|
348
349
|
);
|
|
349
350
|
}
|
|
@@ -360,11 +361,11 @@ export function GitPanel() {
|
|
|
360
361
|
<button
|
|
361
362
|
className="flex items-center gap-1.5 min-w-0 hover:bg-accent/50 rounded-md px-1.5 py-0.5 transition-colors cursor-pointer"
|
|
362
363
|
onClick={() => setBranchModalOpen(true)}
|
|
363
|
-
title=
|
|
364
|
+
title={t('clickToSwitchBranches')}
|
|
364
365
|
>
|
|
365
366
|
<GitBranch className="size-4 shrink-0 text-muted-foreground" />
|
|
366
367
|
<span className="text-sm font-medium truncate">
|
|
367
|
-
{status?.branch || '
|
|
368
|
+
{status?.branch || t('noBranch')}
|
|
368
369
|
</span>
|
|
369
370
|
{status && (status.ahead > 0 || status.behind > 0) && (
|
|
370
371
|
<div className="flex items-center gap-1 text-xs text-muted-foreground">
|
|
@@ -388,7 +389,7 @@ export function GitPanel() {
|
|
|
388
389
|
size="icon-sm"
|
|
389
390
|
onClick={() => fetchStatus(true)}
|
|
390
391
|
disabled={loading}
|
|
391
|
-
title=
|
|
392
|
+
title={tCommon('refresh')}
|
|
392
393
|
>
|
|
393
394
|
<RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} />
|
|
394
395
|
</Button>
|
|
@@ -413,7 +414,7 @@ export function GitPanel() {
|
|
|
413
414
|
) : (
|
|
414
415
|
<ChevronRight className="size-4" />
|
|
415
416
|
)}
|
|
416
|
-
<span className="flex-1">
|
|
417
|
+
<span className="flex-1">{t('changes')}</span>
|
|
417
418
|
|
|
418
419
|
{/* Section action buttons */}
|
|
419
420
|
<div className="flex items-center gap-0.5">
|
|
@@ -423,7 +424,7 @@ export function GitPanel() {
|
|
|
423
424
|
e.stopPropagation();
|
|
424
425
|
discardAll();
|
|
425
426
|
}}
|
|
426
|
-
title=
|
|
427
|
+
title={t('discardAllChanges')}
|
|
427
428
|
>
|
|
428
429
|
<Undo2 className="size-3.5" />
|
|
429
430
|
</button>
|
|
@@ -433,7 +434,7 @@ export function GitPanel() {
|
|
|
433
434
|
e.stopPropagation();
|
|
434
435
|
stageAll();
|
|
435
436
|
}}
|
|
436
|
-
title=
|
|
437
|
+
title={t('stageAllChanges')}
|
|
437
438
|
>
|
|
438
439
|
<Plus className="size-3.5" />
|
|
439
440
|
</button>
|
|
@@ -454,7 +455,7 @@ export function GitPanel() {
|
|
|
454
455
|
<input
|
|
455
456
|
type="text"
|
|
456
457
|
className="w-full px-2 py-1.5 text-sm bg-muted/50 border rounded-md focus:outline-none focus:ring-1 focus:ring-ring"
|
|
457
|
-
placeholder=
|
|
458
|
+
placeholder={t('commitTitle')}
|
|
458
459
|
value={commitTitle}
|
|
459
460
|
onChange={(e) => setCommitTitle(e.target.value)}
|
|
460
461
|
onKeyDown={(e) => {
|
|
@@ -466,7 +467,7 @@ export function GitPanel() {
|
|
|
466
467
|
{/* Commit description textarea */}
|
|
467
468
|
<textarea
|
|
468
469
|
className="w-full min-h-[60px] px-2 py-1.5 text-sm bg-muted/50 border rounded-md focus:outline-none focus:ring-1 focus:ring-ring resize-y"
|
|
469
|
-
placeholder=
|
|
470
|
+
placeholder={t('descriptionOptional')}
|
|
470
471
|
value={commitDescription}
|
|
471
472
|
onChange={(e) => setCommitDescription(e.target.value)}
|
|
472
473
|
onKeyDown={(e) => {
|
|
@@ -488,12 +489,12 @@ export function GitPanel() {
|
|
|
488
489
|
) : hasUnpushedCommits ? (
|
|
489
490
|
<>
|
|
490
491
|
<ArrowUp className="size-4 mr-1" />
|
|
491
|
-
|
|
492
|
+
{t('syncChanges')}
|
|
492
493
|
</>
|
|
493
494
|
) : (
|
|
494
495
|
<>
|
|
495
496
|
<Check className="size-4 mr-1" />
|
|
496
|
-
|
|
497
|
+
{t('commitChanges')}
|
|
497
498
|
</>
|
|
498
499
|
)}
|
|
499
500
|
</Button>
|
|
@@ -505,10 +506,10 @@ export function GitPanel() {
|
|
|
505
506
|
className="px-2"
|
|
506
507
|
title={
|
|
507
508
|
totalChanges === 0
|
|
508
|
-
? '
|
|
509
|
+
? t('noChangesToGenerate')
|
|
509
510
|
: generatingMessage
|
|
510
|
-
? '
|
|
511
|
-
: '
|
|
511
|
+
? t('generatingCommit')
|
|
512
|
+
: t('generateCommitMessage')
|
|
512
513
|
}
|
|
513
514
|
onClick={handleGenerateMessage}
|
|
514
515
|
disabled={generatingMessage || totalChanges === 0}
|
|
@@ -539,7 +540,7 @@ export function GitPanel() {
|
|
|
539
540
|
{totalChanges === 0 ? (
|
|
540
541
|
<div className="flex flex-col items-center justify-center py-4 text-muted-foreground text-sm">
|
|
541
542
|
<p>{t('noChanges')}</p>
|
|
542
|
-
<p className="text-xs mt-1">
|
|
543
|
+
<p className="text-xs mt-1">{t('workingTreeClean')}</p>
|
|
543
544
|
</div>
|
|
544
545
|
) : (
|
|
545
546
|
<>
|
|
@@ -627,7 +628,7 @@ export function GitPanel() {
|
|
|
627
628
|
open={branchModalOpen}
|
|
628
629
|
onOpenChange={setBranchModalOpen}
|
|
629
630
|
projectPath={activeProject.path}
|
|
630
|
-
currentBranch={status.branch || '
|
|
631
|
+
currentBranch={status.branch || t('noBranch')}
|
|
631
632
|
onCheckout={handleBranchCheckout}
|
|
632
633
|
/>
|
|
633
634
|
)}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState } from 'react';
|
|
4
4
|
import { ChevronRight, ChevronDown, Plus, Minus, Undo2 } from 'lucide-react';
|
|
5
|
+
import { useTranslations } from 'next-intl';
|
|
5
6
|
import { GitFileItem } from './git-file-item';
|
|
6
7
|
import { cn } from '@/lib/utils';
|
|
7
8
|
import type { GitFileStatus } from '@/types';
|
|
@@ -35,6 +36,7 @@ export function GitSection({
|
|
|
35
36
|
onUnstageAll,
|
|
36
37
|
onDiscardAll,
|
|
37
38
|
}: GitSectionProps) {
|
|
39
|
+
const t = useTranslations('git');
|
|
38
40
|
const [isExpanded, setIsExpanded] = useState(defaultExpanded);
|
|
39
41
|
|
|
40
42
|
if (files.length === 0) return null;
|
|
@@ -66,7 +68,7 @@ export function GitSection({
|
|
|
66
68
|
e.stopPropagation();
|
|
67
69
|
onUnstageAll?.();
|
|
68
70
|
}}
|
|
69
|
-
title=
|
|
71
|
+
title={t('unstageAllChanges')}
|
|
70
72
|
>
|
|
71
73
|
<Minus className="size-3.5" />
|
|
72
74
|
</button>
|
|
@@ -79,7 +81,7 @@ export function GitSection({
|
|
|
79
81
|
e.stopPropagation();
|
|
80
82
|
onDiscardAll?.();
|
|
81
83
|
}}
|
|
82
|
-
title=
|
|
84
|
+
title={t('discardAllChanges')}
|
|
83
85
|
>
|
|
84
86
|
<Undo2 className="size-3.5" />
|
|
85
87
|
</button>
|
|
@@ -90,7 +92,7 @@ export function GitSection({
|
|
|
90
92
|
e.stopPropagation();
|
|
91
93
|
onStageAll?.();
|
|
92
94
|
}}
|
|
93
|
-
title=
|
|
95
|
+
title={t('stageAllChanges')}
|
|
94
96
|
>
|
|
95
97
|
<Plus className="size-3.5" />
|
|
96
98
|
</button>
|
|
@@ -5,6 +5,7 @@ import { Square, Terminal, Circle } from 'lucide-react';
|
|
|
5
5
|
import { Button } from '@/components/ui/button';
|
|
6
6
|
import { cn } from '@/lib/utils';
|
|
7
7
|
import { useShellStore, type ShellInfo } from '@/stores/shell-store';
|
|
8
|
+
import { useTranslations } from 'next-intl';
|
|
8
9
|
|
|
9
10
|
interface ShellPanelProps {
|
|
10
11
|
projectId: string;
|
|
@@ -12,6 +13,7 @@ interface ShellPanelProps {
|
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
function ShellItem({ shell, onStop }: { shell: ShellInfo; onStop: () => void }) {
|
|
16
|
+
const t = useTranslations('shells');
|
|
15
17
|
// Truncate command for display
|
|
16
18
|
const displayCommand = shell.command.length > 30
|
|
17
19
|
? shell.command.slice(0, 30) + '...'
|
|
@@ -48,7 +50,7 @@ function ShellItem({ shell, onStop }: { shell: ShellInfo; onStop: () => void })
|
|
|
48
50
|
size="icon"
|
|
49
51
|
className="h-6 w-6 flex-shrink-0"
|
|
50
52
|
onClick={onStop}
|
|
51
|
-
title=
|
|
53
|
+
title={t('stopShell')}
|
|
52
54
|
>
|
|
53
55
|
<Square className="h-3 w-3" />
|
|
54
56
|
</Button>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { Plus } from 'lucide-react';
|
|
4
|
+
import { useTranslations } from 'next-intl';
|
|
4
5
|
import { FilePreview } from './file-preview';
|
|
5
6
|
import { Button } from '@/components/ui/button';
|
|
6
7
|
import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area';
|
|
@@ -19,6 +20,8 @@ export function AttachmentBar({
|
|
|
19
20
|
onRetry,
|
|
20
21
|
onAddFiles,
|
|
21
22
|
}: AttachmentBarProps) {
|
|
23
|
+
const t = useTranslations('editor');
|
|
24
|
+
|
|
22
25
|
if (files.length === 0) return null;
|
|
23
26
|
|
|
24
27
|
return (
|
|
@@ -42,7 +45,7 @@ export function AttachmentBar({
|
|
|
42
45
|
variant="ghost"
|
|
43
46
|
size="icon"
|
|
44
47
|
onClick={onAddFiles}
|
|
45
|
-
title=
|
|
48
|
+
title={t('addMoreFiles')}
|
|
46
49
|
type="button"
|
|
47
50
|
className="shrink-0"
|
|
48
51
|
>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { formatDistanceToNow as formatDateDistance } from 'date-fns';
|
|
4
|
+
import { useTranslations } from 'next-intl';
|
|
4
5
|
import { Badge } from '@/components/ui/badge';
|
|
5
6
|
import { cn } from '@/lib/utils';
|
|
6
7
|
import type { Attempt } from '@/types';
|
|
@@ -13,13 +14,14 @@ interface AttemptItemProps {
|
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
const STATUS_VARIANTS = {
|
|
16
|
-
running: { variant: 'secondary' as const,
|
|
17
|
-
completed: { variant: 'secondary' as const,
|
|
18
|
-
failed: { variant: 'destructive' as const,
|
|
19
|
-
cancelled: { variant: 'outline' as const,
|
|
17
|
+
running: { variant: 'secondary' as const, labelKey: 'statusRunning' as const, color: 'text-yellow-600' },
|
|
18
|
+
completed: { variant: 'secondary' as const, labelKey: 'statusCompleted' as const, color: 'text-green-600' },
|
|
19
|
+
failed: { variant: 'destructive' as const, labelKey: 'statusFailed' as const, color: 'text-red-600' },
|
|
20
|
+
cancelled: { variant: 'outline' as const, labelKey: 'statusCancelled' as const, color: 'text-muted-foreground' },
|
|
20
21
|
};
|
|
21
22
|
|
|
22
23
|
export function AttemptItem({ attempt, onClick, isActive, className }: AttemptItemProps) {
|
|
24
|
+
const t = useTranslations('task');
|
|
23
25
|
const statusConfig = STATUS_VARIANTS[attempt.status];
|
|
24
26
|
const hasDiff = attempt.diffAdditions > 0 || attempt.diffDeletions > 0;
|
|
25
27
|
|
|
@@ -38,7 +40,7 @@ export function AttemptItem({ attempt, onClick, isActive, className }: AttemptIt
|
|
|
38
40
|
#{attempt.id.slice(0, 8)}
|
|
39
41
|
</span>
|
|
40
42
|
<Badge variant={statusConfig.variant} className={cn('text-xs', statusConfig.color)}>
|
|
41
|
-
{statusConfig.
|
|
43
|
+
{t(statusConfig.labelKey)}
|
|
42
44
|
</Badge>
|
|
43
45
|
</div>
|
|
44
46
|
<span className="text-xs text-muted-foreground whitespace-nowrap">
|