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.
Files changed (86) hide show
  1. package/locales/de.json +374 -12
  2. package/locales/en.json +374 -12
  3. package/locales/es.json +398 -11
  4. package/locales/fr.json +398 -11
  5. package/locales/ja.json +398 -11
  6. package/locales/ko.json +398 -11
  7. package/locales/vi.json +374 -12
  8. package/locales/zh.json +398 -11
  9. package/package.json +1 -1
  10. package/server.ts +283 -6
  11. package/src/app/[locale]/not-found.tsx +6 -3
  12. package/src/app/[locale]/page.tsx +14 -4
  13. package/src/app/api/attempts/[id]/workflow/route.ts +76 -0
  14. package/src/app/api/questions/answer/route.ts +58 -0
  15. package/src/app/api/questions/route.ts +68 -0
  16. package/src/app/api/tasks/[id]/compact/route.ts +62 -0
  17. package/src/components/access-anywhere/api-access-key-setup-modal.tsx +2 -2
  18. package/src/components/access-anywhere/tunnel-settings-dialog.tsx +6 -6
  19. package/src/components/access-anywhere/wizard-step-ctunnel.tsx +8 -8
  20. package/src/components/agent-factory/dependency-tree.tsx +5 -3
  21. package/src/components/agent-factory/discovery-dialog.tsx +26 -22
  22. package/src/components/agent-factory/plugin-detail-dialog.tsx +41 -38
  23. package/src/components/agent-factory/plugin-form-dialog.tsx +23 -20
  24. package/src/components/agent-factory/plugin-list.tsx +20 -17
  25. package/src/components/agent-factory/upload-dialog.tsx +17 -14
  26. package/src/components/auth/agent-provider-dialog.tsx +67 -65
  27. package/src/components/auth/api-key-dialog.tsx +14 -11
  28. package/src/components/auth/auth-error-message.tsx +6 -3
  29. package/src/components/editor/code-editor-with-inline-edit.tsx +4 -2
  30. package/src/components/editor/file-diff-resolver-modal.tsx +31 -26
  31. package/src/components/editor/inline-edit-dialog.tsx +9 -6
  32. package/src/components/editor/selection-mention-popup.tsx +3 -1
  33. package/src/components/header/project-selector.tsx +7 -4
  34. package/src/components/header.tsx +70 -4
  35. package/src/components/kanban/column.tsx +11 -0
  36. package/src/components/kanban/task-card.tsx +70 -4
  37. package/src/components/project-settings/component-selector.tsx +3 -1
  38. package/src/components/project-settings/plugin-upload-dialog.tsx +7 -5
  39. package/src/components/project-settings/project-settings-dialog.tsx +5 -3
  40. package/src/components/questions/questions-panel.tsx +136 -0
  41. package/src/components/settings/folder-browser-dialog.tsx +29 -25
  42. package/src/components/settings/settings-page.tsx +64 -18
  43. package/src/components/settings/setup-dialog.tsx +26 -23
  44. package/src/components/setup/unified-setup-wizard.tsx +12 -9
  45. package/src/components/sidebar/file-browser/file-create-buttons.tsx +7 -3
  46. package/src/components/sidebar/file-browser/file-tab-content.tsx +19 -15
  47. package/src/components/sidebar/file-browser/file-tabs-panel.tsx +7 -4
  48. package/src/components/sidebar/file-browser/file-tree.tsx +3 -1
  49. package/src/components/sidebar/git-changes/branch-checkout-modal.tsx +6 -4
  50. package/src/components/sidebar/git-changes/commit-details-modal.tsx +5 -3
  51. package/src/components/sidebar/git-changes/diff-tabs-panel.tsx +3 -1
  52. package/src/components/sidebar/git-changes/git-file-item.tsx +8 -6
  53. package/src/components/sidebar/git-changes/git-graph.tsx +8 -5
  54. package/src/components/sidebar/git-changes/git-panel.tsx +28 -27
  55. package/src/components/sidebar/git-changes/git-section.tsx +5 -3
  56. package/src/components/sidebar/shells/shell-panel.tsx +3 -1
  57. package/src/components/task/attachment-bar.tsx +4 -1
  58. package/src/components/task/attempt-item.tsx +7 -5
  59. package/src/components/task/conversation-view.tsx +21 -13
  60. package/src/components/task/floating-chat-window.tsx +14 -5
  61. package/src/components/task/interactive-command/checkpoint-list.tsx +5 -3
  62. package/src/components/task/interactive-command/confirm-dialog.tsx +9 -4
  63. package/src/components/task/interactive-command/interactive-command-overlay.tsx +23 -9
  64. package/src/components/task/interactive-command/question-prompt.tsx +12 -8
  65. package/src/components/task/pending-question-indicator.tsx +5 -3
  66. package/src/components/task/prompt-input.tsx +1 -1
  67. package/src/components/task/shell-log-view.tsx +3 -1
  68. package/src/components/task/status-line.tsx +84 -23
  69. package/src/components/task/task-detail-panel.tsx +27 -27
  70. package/src/components/task/task-shell-indicator.tsx +10 -6
  71. package/src/components/terminal/terminal-context-menu.tsx +6 -4
  72. package/src/components/terminal/terminal-instance.tsx +11 -3
  73. package/src/components/terminal/terminal-panel.tsx +6 -3
  74. package/src/components/terminal/terminal-shortcut-bar.tsx +3 -1
  75. package/src/components/terminal/terminal-tab-bar.tsx +5 -3
  76. package/src/components/workflow/workflow-panel.tsx +181 -0
  77. package/src/hooks/use-attempt-stream.ts +96 -3
  78. package/src/lib/agent-manager.ts +89 -3
  79. package/src/lib/db/index.ts +18 -0
  80. package/src/lib/db/schema.ts +29 -0
  81. package/src/lib/process-manager.ts +28 -7
  82. package/src/lib/session-manager.ts +60 -0
  83. package/src/lib/usage-tracker.ts +19 -19
  84. package/src/lib/workflow-tracker.ts +118 -20
  85. package/src/stores/questions-store.ts +76 -0
  86. package/src/stores/workflow-store.ts +71 -0
@@ -1,14 +1,45 @@
1
1
  'use client';
2
2
 
3
+ import { useState, useEffect } from 'react';
3
4
  import { useSortable } from '@dnd-kit/sortable';
4
5
  import { CSS } from '@dnd-kit/utilities';
5
6
  import { Task } from '@/types';
6
7
  import { cn, getProjectColor } from '@/lib/utils';
7
- import { GripVertical, MessageSquare, Trash2, Search } from 'lucide-react';
8
+ import { GripVertical, MessageSquare, Trash2, Search, Network } from 'lucide-react';
9
+ import { useTranslations } from 'next-intl';
8
10
  import { useTaskStore } from '@/stores/task-store';
9
11
  import { useProjectStore } from '@/stores/project-store';
12
+ import { useQuestionsStore } from '@/stores/questions-store';
13
+ import { useWorkflowStore } from '@/stores/workflow-store';
10
14
  import type { ChatHistoryMatch } from '@/hooks/use-chat-history-search';
11
15
 
16
+ function formatRelativeTime(timestamp: number): string {
17
+ const now = Date.now();
18
+ const diffMs = now - timestamp;
19
+ const diffMin = Math.floor(diffMs / 60000);
20
+ const diffHr = Math.floor(diffMs / 3600000);
21
+ const diffDay = Math.floor(diffMs / 86400000);
22
+
23
+ if (diffMin < 1) return 'just now';
24
+ if (diffMin < 60) return `${diffMin}m ago`;
25
+ if (diffHr < 24) return `${diffHr}h ago`;
26
+ if (diffDay < 30) return `${diffDay}d ago`;
27
+ return new Date(timestamp).toLocaleDateString();
28
+ }
29
+
30
+ function formatAbsoluteTime(timestamp: number): string {
31
+ return new Date(timestamp).toLocaleString();
32
+ }
33
+
34
+ function RelativeTime({ timestamp }: { timestamp: number }) {
35
+ const [, setTick] = useState(0);
36
+ useEffect(() => {
37
+ const interval = setInterval(() => setTick((t) => t + 1), 60000);
38
+ return () => clearInterval(interval);
39
+ }, []);
40
+ return <>{formatRelativeTime(timestamp)}</>;
41
+ }
42
+
12
43
  interface TaskCardProps {
13
44
  task: Task;
14
45
  attemptCount?: number;
@@ -20,7 +51,14 @@ interface TaskCardProps {
20
51
  export function TaskCard({ task, attemptCount = 0, searchQuery = '', isMobile = false, chatHistoryMatch }: TaskCardProps) {
21
52
  const { selectedTaskId, selectTask, deleteTask } = useTaskStore();
22
53
  const { projects, selectedProjectIds, isAllProjectsMode } = useProjectStore();
54
+ const { getByTaskId } = useQuestionsStore();
55
+ const { getByTaskId: getWorkflowByTaskId } = useWorkflowStore();
56
+ const tTask = useTranslations('task');
57
+ const tKanban = useTranslations('kanban');
23
58
  const isSelected = selectedTaskId === task.id;
59
+ const hasPendingQuestion = !!getByTaskId(task.id);
60
+ const workflowEntry = getWorkflowByTaskId(task.id);
61
+ const hasActiveWorkflow = workflowEntry && workflowEntry.summary.activeCount > 0;
24
62
 
25
63
  // Helper function to highlight matched text
26
64
  const highlightText = (text: string) => {
@@ -47,7 +85,7 @@ export function TaskCard({ task, attemptCount = 0, searchQuery = '', isMobile =
47
85
 
48
86
  const handleDelete = async (e: React.MouseEvent) => {
49
87
  e.stopPropagation();
50
- if (!confirm(`Delete task "${task.title}"?`)) return;
88
+ if (!confirm(tTask('deleteTaskConfirm', { title: task.title }))) return;
51
89
  try {
52
90
  await fetch(`/api/tasks/${task.id}`, { method: 'DELETE' });
53
91
  deleteTask(task.id);
@@ -115,11 +153,31 @@ export function TaskCard({ task, attemptCount = 0, searchQuery = '', isMobile =
115
153
  : '-left-1 -translate-x-full opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none',
116
154
  'hover:bg-muted'
117
155
  )}
118
- aria-label="Drag to reorder"
156
+ aria-label={tKanban('dragToReorder')}
119
157
  >
120
158
  <GripVertical className="size-4" />
121
159
  </button>
122
160
 
161
+ {/* Pending question indicator dot */}
162
+ {hasPendingQuestion && (
163
+ <span
164
+ className="absolute top-1.5 right-1.5 size-2 rounded-full bg-amber-500 z-10"
165
+ title="Pending question"
166
+ />
167
+ )}
168
+
169
+ {/* Active workflow indicator */}
170
+ {hasActiveWorkflow && (
171
+ <span
172
+ className="absolute top-1.5 flex items-center gap-0.5 text-[9px] font-medium text-blue-500 z-10"
173
+ style={{ right: hasPendingQuestion ? '1rem' : '0.375rem' }}
174
+ title={`${workflowEntry.summary.activeCount} agent${workflowEntry.summary.activeCount !== 1 ? 's' : ''} running`}
175
+ >
176
+ <Network className="size-2.5" />
177
+ <span>{workflowEntry.summary.activeCount}</span>
178
+ </span>
179
+ )}
180
+
123
181
  {/* Delete button - always visible for Done/Cancelled tasks */}
124
182
  {showDeleteButton && (
125
183
  <button
@@ -129,7 +187,7 @@ export function TaskCard({ task, attemptCount = 0, searchQuery = '', isMobile =
129
187
  'text-muted-foreground hover:text-destructive',
130
188
  'hover:bg-muted pointer-events-auto z-10'
131
189
  )}
132
- aria-label="Delete task"
190
+ aria-label={tKanban('deleteTask')}
133
191
  >
134
192
  <Trash2 className="size-3" />
135
193
  </button>
@@ -186,6 +244,14 @@ export function TaskCard({ task, attemptCount = 0, searchQuery = '', isMobile =
186
244
  </div>
187
245
  </div>
188
246
  )}
247
+
248
+ {/* Timestamp - shows relative time, switches to exact time on card hover */}
249
+ {task.updatedAt && (
250
+ <div className="mt-1.5 text-[10px] text-muted-foreground/70">
251
+ <span className="group-hover:hidden"><RelativeTime timestamp={task.updatedAt} /></span>
252
+ <span className="hidden group-hover:inline">{formatAbsoluteTime(task.updatedAt)}</span>
253
+ </div>
254
+ )}
189
255
  </div>
190
256
  </div>
191
257
  </div>
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { useState, useEffect } from 'react';
4
+ import { useTranslations } from 'next-intl';
4
5
  import { Search, Check, Loader2, Trash2, Upload } from 'lucide-react';
5
6
  import { Input } from '@/components/ui/input';
6
7
  import { Checkbox } from '@/components/ui/checkbox';
@@ -24,6 +25,7 @@ interface InstalledStatus {
24
25
  }
25
26
 
26
27
  export function ComponentSelector({ type, selectedIds, onChange, projectId, installedIds = [], onRefresh, onCloseDialog }: ComponentSelectorProps) {
28
+ const t = useTranslations('agentFactory');
27
29
  const [components, setComponents] = useState<AgentFactoryPlugin[]>([]);
28
30
  const [loading, setLoading] = useState(true);
29
31
  const [searchQuery, setSearchQuery] = useState('');
@@ -113,7 +115,7 @@ export function ComponentSelector({ type, selectedIds, onChange, projectId, inst
113
115
  onRefresh?.();
114
116
  } catch (error) {
115
117
  console.error('Error uninstalling component:', error);
116
- alert('Failed to uninstall component');
118
+ alert(t('failedToUninstallComponent'));
117
119
  } finally {
118
120
  setUninstalling(null);
119
121
  }
@@ -13,6 +13,7 @@ import { Badge } from '@/components/ui/badge';
13
13
  import { Checkbox } from '@/components/ui/checkbox';
14
14
  import { Label } from '@/components/ui/label';
15
15
  import { Upload, FileArchive, Loader2, AlertCircle, Package, FileText, Folder, Check, X } from 'lucide-react';
16
+ import { useTranslations } from 'next-intl';
16
17
 
17
18
  interface PreviewItem {
18
19
  type: 'skill' | 'command' | 'agent' | 'agent_set' | 'unknown';
@@ -35,6 +36,7 @@ interface PluginUploadDialogProps {
35
36
  * - Import to agent factory as well
36
37
  */
37
38
  export function PluginUploadDialog({ open, onOpenChange, projectId, onUploadSuccess }: PluginUploadDialogProps) {
39
+ const t = useTranslations('agentFactory');
38
40
  const [step, setStep] = useState<'upload' | 'preview' | 'importing'>('upload');
39
41
  const [uploading, setUploading] = useState(false);
40
42
  const [error, setError] = useState<string | null>(null);
@@ -370,15 +372,15 @@ export function PluginUploadDialog({ open, onOpenChange, projectId, onUploadSucc
370
372
  {uploading ? (
371
373
  <div className="flex flex-col items-center gap-3">
372
374
  <Loader2 className="w-12 h-12 animate-spin text-muted-foreground" />
373
- <p className="text-muted-foreground">Analyzing archive...</p>
375
+ <p className="text-muted-foreground">{t('analyzingArchive')}</p>
374
376
  </div>
375
377
  ) : (
376
378
  <div className="flex flex-col items-center gap-3">
377
379
  <Upload className="w-12 h-12 text-muted-foreground" />
378
380
  <div>
379
- <p className="font-medium">Click to upload or drag and drop</p>
381
+ <p className="font-medium">{t('clickToUploadOrDrag')}</p>
380
382
  <p className="text-sm text-muted-foreground mt-1">
381
- .zip, .tar, .gz, .gzip, or .tgz files
383
+ {t('supportedFormats')}
382
384
  </p>
383
385
  </div>
384
386
  </div>
@@ -395,7 +397,7 @@ export function PluginUploadDialog({ open, onOpenChange, projectId, onUploadSucc
395
397
 
396
398
  {/* Info */}
397
399
  <div className="text-sm text-muted-foreground bg-muted/50 p-3 rounded-lg">
398
- <p className="font-medium mb-2">Automatic Organization:</p>
400
+ <p className="font-medium mb-2">{t('automaticOrganization')}</p>
399
401
  <p className="text-xs mb-2">
400
402
  Files will be automatically organized into the correct folders:
401
403
  </p>
@@ -495,7 +497,7 @@ export function PluginUploadDialog({ open, onOpenChange, projectId, onUploadSucc
495
497
  {step === 'importing' && (
496
498
  <div className="flex flex-col items-center gap-4 py-8">
497
499
  <Loader2 className="w-12 h-12 animate-spin text-muted-foreground" />
498
- <p className="text-muted-foreground">Installing plugins...</p>
500
+ <p className="text-muted-foreground">{t('importingPlugins')}</p>
499
501
  </div>
500
502
  )}
501
503
  </div>
@@ -19,6 +19,7 @@ import { ComponentSelector } from './component-selector';
19
19
  import { useProjectSettingsStore } from '@/stores/project-settings-store';
20
20
  import { useAgentFactoryUIStore } from '@/stores/agent-factory-ui-store';
21
21
  import { useToast } from '@/hooks/use-toast';
22
+ import { useTranslations } from 'next-intl';
22
23
 
23
24
  interface InstallResult {
24
25
  installed: string[];
@@ -33,6 +34,7 @@ interface ProjectSettingsDialogProps {
33
34
  }
34
35
 
35
36
  export function ProjectSettingsDialog({ open, onOpenChange, projectId }: ProjectSettingsDialogProps) {
37
+ const t = useTranslations('settings');
36
38
  const { projects } = useProjectStore();
37
39
  const { setOpen: setAgentFactoryOpen } = useAgentFactoryUIStore();
38
40
  const {
@@ -155,8 +157,8 @@ export function ProjectSettingsDialog({ open, onOpenChange, projectId }: Project
155
157
  <Dialog open={open} onOpenChange={onOpenChange}>
156
158
  <DialogContent>
157
159
  <DialogHeader>
158
- <DialogTitle>Project Settings</DialogTitle>
159
- <DialogDescription>No projects available</DialogDescription>
160
+ <DialogTitle>{t('projectSettings')}</DialogTitle>
161
+ <DialogDescription>{t('noProjectsAvailable')}</DialogDescription>
160
162
  </DialogHeader>
161
163
  </DialogContent>
162
164
  </Dialog>
@@ -169,7 +171,7 @@ export function ProjectSettingsDialog({ open, onOpenChange, projectId }: Project
169
171
  <DialogHeader>
170
172
  <DialogTitle className="flex items-center gap-2">
171
173
  <Settings className="h-5 w-5" />
172
- {selectedProject?.name || 'Project Settings'}
174
+ {selectedProject?.name || t('projectSettings')}
173
175
  </DialogTitle>
174
176
  <DialogDescription>
175
177
  Configure plugins and agent sets for this project
@@ -0,0 +1,136 @@
1
+ 'use client';
2
+
3
+ import { MessageCircleQuestion, X } from 'lucide-react';
4
+ import { Badge } from '@/components/ui/badge';
5
+ import { Button } from '@/components/ui/button';
6
+ import { useQuestionsStore, type PendingQuestionEntry } from '@/stores/questions-store';
7
+ import { useTaskStore } from '@/stores/task-store';
8
+ import { cn } from '@/lib/utils';
9
+
10
+ interface Question {
11
+ question: string;
12
+ header: string;
13
+ options: Array<{ label: string; description: string }>;
14
+ multiSelect: boolean;
15
+ }
16
+
17
+ function QuestionEntryItem({ entry }: { entry: PendingQuestionEntry }) {
18
+ const { selectTask } = useTaskStore();
19
+ const { closePanel } = useQuestionsStore();
20
+
21
+ const questions = entry.questions as Question[];
22
+ const firstQuestion = questions[0];
23
+
24
+ const handleGoToTask = () => {
25
+ selectTask(entry.taskId);
26
+ closePanel();
27
+ };
28
+
29
+ const timeAgo = formatTimeAgo(entry.timestamp);
30
+
31
+ return (
32
+ <div className="border-b border-border last:border-b-0">
33
+ {/* Clickable summary row - opens task in chat */}
34
+ <button
35
+ onClick={handleGoToTask}
36
+ className="w-full text-left px-4 py-3 hover:bg-accent/50 transition-colors flex items-start gap-2"
37
+ >
38
+ <div className="flex-1 min-w-0">
39
+ <div className="flex items-center gap-2 mb-0.5">
40
+ <span className="text-sm font-medium truncate">{entry.taskTitle}</span>
41
+ <span className="text-[10px] text-muted-foreground shrink-0">{timeAgo}</span>
42
+ </div>
43
+ <div className="flex items-center gap-1.5">
44
+ <Badge variant="outline" className="text-[10px] px-1 py-0">
45
+ {firstQuestion?.header}
46
+ </Badge>
47
+ <span className="text-xs text-muted-foreground truncate">
48
+ {firstQuestion?.question}
49
+ </span>
50
+ </div>
51
+ </div>
52
+ </button>
53
+ </div>
54
+ );
55
+ }
56
+
57
+ function formatTimeAgo(timestamp: number): string {
58
+ const diffMs = Date.now() - timestamp;
59
+ const diffMin = Math.floor(diffMs / 60000);
60
+ if (diffMin < 1) return 'just now';
61
+ if (diffMin < 60) return `${diffMin}m ago`;
62
+ const diffHr = Math.floor(diffMs / 3600000);
63
+ if (diffHr < 24) return `${diffHr}h ago`;
64
+ return `${Math.floor(diffMs / 86400000)}d ago`;
65
+ }
66
+
67
+ interface QuestionsPanelProps {
68
+ className?: string;
69
+ }
70
+
71
+ export function QuestionsPanel({ className }: QuestionsPanelProps) {
72
+ const { isOpen, closePanel, pendingQuestions } = useQuestionsStore();
73
+ const entries = Array.from(pendingQuestions.values()).sort((a, b) => b.timestamp - a.timestamp);
74
+
75
+ if (!isOpen) return null;
76
+
77
+ return (
78
+ <>
79
+ {/* Overlay for mobile */}
80
+ <div
81
+ className="fixed inset-0 bg-black/50 z-40 sm:hidden"
82
+ onClick={closePanel}
83
+ />
84
+
85
+ {/* Sidebar */}
86
+ <div
87
+ className={cn(
88
+ 'fixed right-0 top-0 h-full w-96 bg-background border-l shadow-lg z-50',
89
+ 'flex flex-col',
90
+ className
91
+ )}
92
+ >
93
+ {/* Header */}
94
+ <div className="flex items-center justify-between px-4 py-3 border-b">
95
+ <div className="flex items-center gap-2">
96
+ <MessageCircleQuestion className="size-4 text-muted-foreground" />
97
+ <h2 className="font-semibold text-sm">Pending Questions</h2>
98
+ {entries.length > 0 && (
99
+ <Badge variant="secondary" className="text-[10px] px-1.5 py-0">
100
+ {entries.length}
101
+ </Badge>
102
+ )}
103
+ </div>
104
+ <Button
105
+ variant="ghost"
106
+ size="icon"
107
+ onClick={closePanel}
108
+ className="h-8 w-8"
109
+ >
110
+ <X className="h-4 w-4" />
111
+ </Button>
112
+ </div>
113
+
114
+ {/* Content */}
115
+ <div className="flex-1 overflow-y-auto">
116
+ {entries.length === 0 ? (
117
+ <div className="px-4 py-12 text-center">
118
+ <MessageCircleQuestion className="size-10 text-muted-foreground/30 mx-auto mb-3" />
119
+ <p className="text-sm text-muted-foreground">No pending questions</p>
120
+ <p className="text-xs text-muted-foreground/70 mt-1">
121
+ Questions from running tasks will appear here
122
+ </p>
123
+ </div>
124
+ ) : (
125
+ entries.map((entry) => (
126
+ <QuestionEntryItem
127
+ key={entry.attemptId}
128
+ entry={entry}
129
+ />
130
+ ))
131
+ )}
132
+ </div>
133
+ </div>
134
+ </>
135
+ );
136
+ }
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { useState, useEffect, useRef } from 'react';
4
+ import { useTranslations } from 'next-intl';
4
5
  import {
5
6
  Dialog,
6
7
  DialogContent,
@@ -44,6 +45,9 @@ export function FolderBrowserDialog({
44
45
  onSelect,
45
46
  initialPath,
46
47
  }: FolderBrowserDialogProps) {
48
+ const t = useTranslations('settings');
49
+ const tCommon = useTranslations('common');
50
+ const tSidebar = useTranslations('sidebar');
47
51
  const [currentPath, setCurrentPath] = useState(initialPath || '');
48
52
  const [directories, setDirectories] = useState<DirectoryEntry[]>([]);
49
53
  const [parentPath, setParentPath] = useState<string | null>(null);
@@ -152,7 +156,7 @@ export function FolderBrowserDialog({
152
156
  const handleCreate = async () => {
153
157
  const trimmedName = createName.trim();
154
158
  if (!trimmedName) {
155
- toast.error('Name cannot be empty');
159
+ toast.error(tSidebar('nameCannotBeEmpty'));
156
160
  return;
157
161
  }
158
162
 
@@ -174,12 +178,12 @@ export function FolderBrowserDialog({
174
178
  throw new Error(data.error || 'Create failed');
175
179
  }
176
180
 
177
- toast.success('Folder created');
181
+ toast.success(t('folderCreated'));
178
182
  setCreateDialogOpen(false);
179
183
  // Refresh directory listing
180
184
  fetchDirectory(currentPath);
181
185
  } catch (err) {
182
- toast.error(err instanceof Error ? err.message : 'Create failed');
186
+ toast.error(err instanceof Error ? err.message : t('createFailed'));
183
187
  } finally {
184
188
  setIsCreating(false);
185
189
  }
@@ -212,7 +216,7 @@ export function FolderBrowserDialog({
212
216
 
213
217
  const trimmedName = renameName.trim();
214
218
  if (!trimmedName) {
215
- toast.error('Name cannot be empty');
219
+ toast.error(tSidebar('nameCannotBeEmpty'));
216
220
  return;
217
221
  }
218
222
 
@@ -238,12 +242,12 @@ export function FolderBrowserDialog({
238
242
  throw new Error(data.error || 'Rename failed');
239
243
  }
240
244
 
241
- toast.success('Folder renamed');
245
+ toast.success(t('folderRenamed'));
242
246
  setRenameDialogOpen(false);
243
247
  // Refresh directory listing
244
248
  fetchDirectory(currentPath);
245
249
  } catch (err) {
246
- toast.error(err instanceof Error ? err.message : 'Rename failed');
250
+ toast.error(err instanceof Error ? err.message : t('renameFailed'));
247
251
  } finally {
248
252
  setIsRenaming(false);
249
253
  }
@@ -263,9 +267,9 @@ export function FolderBrowserDialog({
263
267
  <Dialog open={open} onOpenChange={onOpenChange}>
264
268
  <DialogContent className="sm:max-w-[600px] h-[600px] flex flex-col overflow-hidden">
265
269
  <DialogHeader>
266
- <DialogTitle>Select Folder</DialogTitle>
270
+ <DialogTitle>{t('selectFolder')}</DialogTitle>
267
271
  <DialogDescription>
268
- Navigate to and select your project folder
272
+ {t('navigateAndSelect')}
269
273
  </DialogDescription>
270
274
  </DialogHeader>
271
275
 
@@ -291,7 +295,7 @@ export function FolderBrowserDialog({
291
295
  disabled={!parentPath || loading}
292
296
  >
293
297
  <ChevronUp className="h-4 w-4 mr-1" />
294
- Up
298
+ {t('up')}
295
299
  </Button>
296
300
  <Button
297
301
  variant="outline"
@@ -300,7 +304,7 @@ export function FolderBrowserDialog({
300
304
  disabled={loading}
301
305
  >
302
306
  <Home className="h-4 w-4 mr-1" />
303
- Home
307
+ {t('home')}
304
308
  </Button>
305
309
  <div className="flex-1" />
306
310
  <Button
@@ -310,7 +314,7 @@ export function FolderBrowserDialog({
310
314
  disabled={loading || !currentPath}
311
315
  >
312
316
  <FolderPlus className="h-4 w-4 mr-1" />
313
- New Folder
317
+ {t('createNewFolder')}
314
318
  </Button>
315
319
  </div>
316
320
 
@@ -330,7 +334,7 @@ export function FolderBrowserDialog({
330
334
  </div>
331
335
  ) : directories.length === 0 ? (
332
336
  <div className="flex items-center justify-center h-[200px] text-muted-foreground">
333
- No subdirectories
337
+ {t('noSubdirectories')}
334
338
  </div>
335
339
  ) : (
336
340
  <div className="p-2 space-y-1">
@@ -354,7 +358,7 @@ export function FolderBrowserDialog({
354
358
  e.stopPropagation();
355
359
  openRenameDialog(dir);
356
360
  }}
357
- title="Rename folder"
361
+ title={t('renameFolderTitle')}
358
362
  >
359
363
  <Pencil className="h-3.5 w-3.5" />
360
364
  </Button>
@@ -368,10 +372,10 @@ export function FolderBrowserDialog({
368
372
  {/* Actions */}
369
373
  <div className="flex justify-end gap-2 pt-2">
370
374
  <Button variant="outline" onClick={() => onOpenChange(false)}>
371
- Cancel
375
+ {tCommon('cancel')}
372
376
  </Button>
373
377
  <Button onClick={handleSelect} disabled={!currentPath}>
374
- Select This Folder
378
+ {t('selectThisFolder')}
375
379
  </Button>
376
380
  </div>
377
381
  </DialogContent>
@@ -380,14 +384,14 @@ export function FolderBrowserDialog({
380
384
  <Dialog open={createDialogOpen} onOpenChange={setCreateDialogOpen}>
381
385
  <DialogContent>
382
386
  <DialogHeader>
383
- <DialogTitle>Create New Folder</DialogTitle>
387
+ <DialogTitle>{t('createNewFolder')}</DialogTitle>
384
388
  <DialogDescription>
385
- Enter a name for the new folder in <strong>{currentPath.split('/').pop() || currentPath}</strong>
389
+ {t('enterFolderNameIn')} <strong>{currentPath.split('/').pop() || currentPath}</strong>
386
390
  </DialogDescription>
387
391
  </DialogHeader>
388
392
  <div className="space-y-4 py-4">
389
393
  <div className="space-y-2">
390
- <Label htmlFor="create-folder-name">Folder Name</Label>
394
+ <Label htmlFor="create-folder-name">{t('folderName')}</Label>
391
395
  <Input
392
396
  id="create-folder-name"
393
397
  ref={createInputRef}
@@ -405,10 +409,10 @@ export function FolderBrowserDialog({
405
409
  onClick={() => setCreateDialogOpen(false)}
406
410
  disabled={isCreating}
407
411
  >
408
- Cancel
412
+ {tCommon('cancel')}
409
413
  </Button>
410
414
  <Button onClick={handleCreate} disabled={isCreating}>
411
- {isCreating ? 'Creating...' : 'Create'}
415
+ {isCreating ? tCommon('creating') : tCommon('create')}
412
416
  </Button>
413
417
  </DialogFooter>
414
418
  </DialogContent>
@@ -418,14 +422,14 @@ export function FolderBrowserDialog({
418
422
  <Dialog open={renameDialogOpen} onOpenChange={setRenameDialogOpen}>
419
423
  <DialogContent>
420
424
  <DialogHeader>
421
- <DialogTitle>Rename Folder</DialogTitle>
425
+ <DialogTitle>{t('renameFolder')}</DialogTitle>
422
426
  <DialogDescription>
423
- Enter a new name for <strong>{renameTarget?.name}</strong>
427
+ {t('enterNewNameFor')} <strong>{renameTarget?.name}</strong>
424
428
  </DialogDescription>
425
429
  </DialogHeader>
426
430
  <div className="space-y-4 py-4">
427
431
  <div className="space-y-2">
428
- <Label htmlFor="rename-folder-name">New Name</Label>
432
+ <Label htmlFor="rename-folder-name">{t('newName')}</Label>
429
433
  <Input
430
434
  id="rename-folder-name"
431
435
  ref={renameInputRef}
@@ -443,10 +447,10 @@ export function FolderBrowserDialog({
443
447
  onClick={() => setRenameDialogOpen(false)}
444
448
  disabled={isRenaming}
445
449
  >
446
- Cancel
450
+ {tCommon('cancel')}
447
451
  </Button>
448
452
  <Button onClick={handleRename} disabled={isRenaming}>
449
- {isRenaming ? 'Renaming...' : 'Rename'}
453
+ {isRenaming ? tCommon('renaming') : tCommon('rename')}
450
454
  </Button>
451
455
  </DialogFooter>
452
456
  </DialogContent>