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
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { db, schema } from '@/lib/db';
|
|
3
|
+
import { eq } from 'drizzle-orm';
|
|
4
|
+
import { nanoid } from 'nanoid';
|
|
5
|
+
import { agentManager } from '@/lib/agent-manager';
|
|
6
|
+
import { sessionManager } from '@/lib/session-manager';
|
|
7
|
+
import { createLogger } from '@/lib/logger';
|
|
8
|
+
|
|
9
|
+
const log = createLogger('CompactTask');
|
|
10
|
+
|
|
11
|
+
// POST /api/tasks/[id]/compact - Trigger conversation compaction
|
|
12
|
+
export async function POST(
|
|
13
|
+
request: NextRequest,
|
|
14
|
+
{ params }: { params: Promise<{ id: string }> }
|
|
15
|
+
) {
|
|
16
|
+
try {
|
|
17
|
+
const { id: taskId } = await params;
|
|
18
|
+
|
|
19
|
+
const task = await db.query.tasks.findFirst({
|
|
20
|
+
where: eq(schema.tasks.id, taskId),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
if (!task) {
|
|
24
|
+
return NextResponse.json({ error: 'Task not found' }, { status: 404 });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const project = await db.query.projects.findFirst({
|
|
28
|
+
where: eq(schema.projects.id, task.projectId),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
if (!project) {
|
|
32
|
+
return NextResponse.json({ error: 'Project not found' }, { status: 404 });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const conversationSummary = await sessionManager.getConversationSummary(taskId);
|
|
36
|
+
|
|
37
|
+
const attemptId = nanoid();
|
|
38
|
+
await db.insert(schema.attempts).values({
|
|
39
|
+
id: attemptId,
|
|
40
|
+
taskId,
|
|
41
|
+
prompt: 'Compact: summarize conversation context',
|
|
42
|
+
displayPrompt: 'Compacting conversation...',
|
|
43
|
+
status: 'running',
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
log.info({ attemptId, taskId }, 'Starting compact');
|
|
47
|
+
|
|
48
|
+
agentManager.compact({
|
|
49
|
+
attemptId,
|
|
50
|
+
projectPath: project.path,
|
|
51
|
+
conversationSummary,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
return NextResponse.json({ success: true, attemptId });
|
|
55
|
+
} catch (error) {
|
|
56
|
+
log.error({ error }, 'Failed to compact');
|
|
57
|
+
return NextResponse.json(
|
|
58
|
+
{ error: 'Failed to compact conversation' },
|
|
59
|
+
{ status: 500 }
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -73,14 +73,14 @@ export function ApiAccessKeySetupForm({ onSuccess }: ApiAccessKeySetupFormProps)
|
|
|
73
73
|
const data = await res.json();
|
|
74
74
|
|
|
75
75
|
if (!res.ok) {
|
|
76
|
-
throw new Error(data.error || '
|
|
76
|
+
throw new Error(data.error || t('failedToSaveApiKey'));
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
if (onSuccess) {
|
|
80
80
|
onSuccess();
|
|
81
81
|
}
|
|
82
82
|
} catch (err) {
|
|
83
|
-
setError(err instanceof Error ? err.message : '
|
|
83
|
+
setError(err instanceof Error ? err.message : t('failedToSaveApiKey'));
|
|
84
84
|
} finally {
|
|
85
85
|
setSaving(false);
|
|
86
86
|
}
|
|
@@ -81,7 +81,7 @@ export function TunnelSettingsDialog() {
|
|
|
81
81
|
};
|
|
82
82
|
|
|
83
83
|
const handleReset = async () => {
|
|
84
|
-
if (confirm('
|
|
84
|
+
if (confirm(t('resetConfirmation'))) {
|
|
85
85
|
setResetting(true);
|
|
86
86
|
try {
|
|
87
87
|
await useTunnelStore.getState().resetOnboarding();
|
|
@@ -120,7 +120,7 @@ export function TunnelSettingsDialog() {
|
|
|
120
120
|
<VisuallyHidden>
|
|
121
121
|
<DialogTitle>Access Anywhere</DialogTitle>
|
|
122
122
|
</VisuallyHidden>
|
|
123
|
-
|
|
123
|
+
{t('noTunnelConfig')}
|
|
124
124
|
</div>
|
|
125
125
|
) : (
|
|
126
126
|
<>
|
|
@@ -141,7 +141,7 @@ export function TunnelSettingsDialog() {
|
|
|
141
141
|
variant={status === 'connected' ? 'default' : 'secondary'}
|
|
142
142
|
className={status === 'connected' ? 'bg-green-500 hover:bg-green-600 text-white' : ''}
|
|
143
143
|
>
|
|
144
|
-
{status === 'connected' ?
|
|
144
|
+
{status === 'connected' ? `● ${t('connected')}` : `○ ${t('disconnected')}`}
|
|
145
145
|
</Badge>
|
|
146
146
|
{subdomain && (
|
|
147
147
|
<>
|
|
@@ -159,7 +159,7 @@ export function TunnelSettingsDialog() {
|
|
|
159
159
|
variant="outline"
|
|
160
160
|
size="sm"
|
|
161
161
|
onClick={handleCopyUrl}
|
|
162
|
-
title={copiedUrl ? '
|
|
162
|
+
title={copiedUrl ? t('copiedToClipboard') : t('copyUrl')}
|
|
163
163
|
>
|
|
164
164
|
{copiedUrl ? <Check className="h-3 w-3 text-green-500" /> : <Copy className="h-3 w-3" />}
|
|
165
165
|
</Button>
|
|
@@ -195,13 +195,13 @@ export function TunnelSettingsDialog() {
|
|
|
195
195
|
variant="outline"
|
|
196
196
|
size="icon"
|
|
197
197
|
onClick={handleCopyApiKey}
|
|
198
|
-
title={copied ? '
|
|
198
|
+
title={copied ? t('copiedToClipboard') : t('clickToCopyApiKey')}
|
|
199
199
|
>
|
|
200
200
|
{copied ? <RefreshCw className="h-4 w-4 text-green-500" /> : <Copy className="h-4 w-4" />}
|
|
201
201
|
</Button>
|
|
202
202
|
</div>
|
|
203
203
|
<p className="text-xs text-muted-foreground">
|
|
204
|
-
{copied ? '
|
|
204
|
+
{copied ? t('copiedToClipboard') : t('clickToCopyApiKey')}
|
|
205
205
|
</p>
|
|
206
206
|
</div>
|
|
207
207
|
)}
|
|
@@ -73,7 +73,7 @@ export function WizardStepCtunnel() {
|
|
|
73
73
|
|
|
74
74
|
const checkAvailability = async () => {
|
|
75
75
|
if (!subdomain || !email) {
|
|
76
|
-
setErrorMessage('
|
|
76
|
+
setErrorMessage(t('subdomainRequired'));
|
|
77
77
|
return;
|
|
78
78
|
}
|
|
79
79
|
|
|
@@ -84,7 +84,7 @@ export function WizardStepCtunnel() {
|
|
|
84
84
|
// Subdomain already registered to this email, proceed to confirmation or direct connect
|
|
85
85
|
await registerSubdomain();
|
|
86
86
|
} else {
|
|
87
|
-
setErrorMessage('
|
|
87
|
+
setErrorMessage(t('subdomainNotAvailable'));
|
|
88
88
|
}
|
|
89
89
|
return;
|
|
90
90
|
}
|
|
@@ -107,10 +107,10 @@ export function WizardStepCtunnel() {
|
|
|
107
107
|
if (data.success) {
|
|
108
108
|
setStep('confirm');
|
|
109
109
|
} else {
|
|
110
|
-
setErrorMessage(data.message || '
|
|
110
|
+
setErrorMessage(data.message || t('failedToRegisterSubdomain'));
|
|
111
111
|
}
|
|
112
112
|
} catch (error) {
|
|
113
|
-
setErrorMessage(error instanceof Error ? error.message : '
|
|
113
|
+
setErrorMessage(error instanceof Error ? error.message : t('failedToRegisterSubdomain'));
|
|
114
114
|
} finally {
|
|
115
115
|
setRegistering(false);
|
|
116
116
|
}
|
|
@@ -118,7 +118,7 @@ export function WizardStepCtunnel() {
|
|
|
118
118
|
|
|
119
119
|
const confirmSubdomain = async () => {
|
|
120
120
|
if (!confirmationCode) {
|
|
121
|
-
setErrorMessage('
|
|
121
|
+
setErrorMessage(t('confirmationCodeIsRequired'));
|
|
122
122
|
return;
|
|
123
123
|
}
|
|
124
124
|
|
|
@@ -137,10 +137,10 @@ export function WizardStepCtunnel() {
|
|
|
137
137
|
setStep('connecting');
|
|
138
138
|
await startTunnel(subdomain);
|
|
139
139
|
} else {
|
|
140
|
-
setErrorMessage(data.message || '
|
|
140
|
+
setErrorMessage(data.message || t('failedToConfirmSubdomain'));
|
|
141
141
|
}
|
|
142
142
|
} catch (error) {
|
|
143
|
-
setErrorMessage(error instanceof Error ? error.message : '
|
|
143
|
+
setErrorMessage(error instanceof Error ? error.message : t('failedToConfirmSubdomain'));
|
|
144
144
|
} finally {
|
|
145
145
|
setVerifying(false);
|
|
146
146
|
}
|
|
@@ -151,7 +151,7 @@ export function WizardStepCtunnel() {
|
|
|
151
151
|
setStep('connected');
|
|
152
152
|
} else if (status === 'error' && step === 'connecting') {
|
|
153
153
|
setStep('error');
|
|
154
|
-
setErrorMessage(error || '
|
|
154
|
+
setErrorMessage(error || t('failedToStartTunnel'));
|
|
155
155
|
}
|
|
156
156
|
}, [status, error]);
|
|
157
157
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { Badge } from '@/components/ui/badge';
|
|
4
4
|
import { ChevronRight, ChevronDown, AlertTriangle, AlertCircle } from 'lucide-react';
|
|
5
5
|
import { useState } from 'react';
|
|
6
|
+
import { useTranslations } from 'next-intl';
|
|
6
7
|
|
|
7
8
|
export interface DependencyTreeNode {
|
|
8
9
|
type: 'skill' | 'command' | 'agent';
|
|
@@ -29,6 +30,7 @@ export function DependencyTree({ nodes }: DependencyTreeProps) {
|
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
function TreeNode({ node }: { node: DependencyTreeNode }) {
|
|
33
|
+
const t = useTranslations('agentFactory');
|
|
32
34
|
const [expanded, setExpanded] = useState(true);
|
|
33
35
|
const hasChildren = node.children && node.children.length > 0;
|
|
34
36
|
|
|
@@ -68,20 +70,20 @@ function TreeNode({ node }: { node: DependencyTreeNode }) {
|
|
|
68
70
|
{node.cycle && (
|
|
69
71
|
<Badge variant="outline" className="text-orange-500 border-orange-500 text-xs">
|
|
70
72
|
<AlertTriangle className="w-3 h-3 mr-1" />
|
|
71
|
-
|
|
73
|
+
{t('cycle')}
|
|
72
74
|
</Badge>
|
|
73
75
|
)}
|
|
74
76
|
|
|
75
77
|
{node.missing && (
|
|
76
78
|
<Badge variant="outline" className="text-red-500 border-red-500 text-xs">
|
|
77
79
|
<AlertCircle className="w-3 h-3 mr-1" />
|
|
78
|
-
|
|
80
|
+
{t('missing')}
|
|
79
81
|
</Badge>
|
|
80
82
|
)}
|
|
81
83
|
|
|
82
84
|
{node.truncated && (
|
|
83
85
|
<Badge variant="outline" className="text-gray-500 text-xs">
|
|
84
|
-
|
|
86
|
+
{t('maxDepth')}
|
|
85
87
|
</Badge>
|
|
86
88
|
)}
|
|
87
89
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect, useMemo, useCallback, memo } from 'react';
|
|
4
|
+
import { useTranslations } from 'next-intl';
|
|
4
5
|
import {
|
|
5
6
|
Dialog,
|
|
6
7
|
DialogContent,
|
|
@@ -102,6 +103,7 @@ const TreeNode = memo(function TreeNode({
|
|
|
102
103
|
onImport,
|
|
103
104
|
onClick
|
|
104
105
|
}: TreeNodeProps) {
|
|
106
|
+
const t = useTranslations('agentFactory');
|
|
105
107
|
const key = getNodeKey(node, index);
|
|
106
108
|
const isExpanded = expandedFolders.has(key);
|
|
107
109
|
|
|
@@ -121,11 +123,11 @@ const TreeNode = memo(function TreeNode({
|
|
|
121
123
|
const getStatusBadge = (status: string) => {
|
|
122
124
|
switch (status) {
|
|
123
125
|
case 'new':
|
|
124
|
-
return <span className="text-xs px-2 py-0.5 rounded-full bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200">
|
|
126
|
+
return <span className="text-xs px-2 py-0.5 rounded-full bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200">{t('newStatus')}</span>;
|
|
125
127
|
case 'update':
|
|
126
|
-
return <span className="text-xs px-2 py-0.5 rounded-full bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200">
|
|
128
|
+
return <span className="text-xs px-2 py-0.5 rounded-full bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200">{t('update')}</span>;
|
|
127
129
|
case 'current':
|
|
128
|
-
return <span className="text-xs px-2 py-0.5 rounded-full bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-200">
|
|
130
|
+
return <span className="text-xs px-2 py-0.5 rounded-full bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-200">{t('current')}</span>;
|
|
129
131
|
default:
|
|
130
132
|
return null;
|
|
131
133
|
}
|
|
@@ -253,10 +255,10 @@ const TreeNode = memo(function TreeNode({
|
|
|
253
255
|
) : statusMap.get(`${node.type}-${node.name}`)?.status === 'update' ? (
|
|
254
256
|
<>
|
|
255
257
|
<RotateCcw className="w-3 h-3 mr-1" />
|
|
256
|
-
|
|
258
|
+
{t('update')}
|
|
257
259
|
</>
|
|
258
260
|
) : (
|
|
259
|
-
'
|
|
261
|
+
t('import')
|
|
260
262
|
)}
|
|
261
263
|
</Button>
|
|
262
264
|
</>
|
|
@@ -287,6 +289,8 @@ const TreeNode = memo(function TreeNode({
|
|
|
287
289
|
});
|
|
288
290
|
|
|
289
291
|
export function DiscoveryDialog({ open, onOpenChange }: DiscoveryDialogProps) {
|
|
292
|
+
const t = useTranslations('agentFactory');
|
|
293
|
+
const tCommon = useTranslations('common');
|
|
290
294
|
const { plugins, discovering, discoverPlugins, importPlugin, fetchPlugins } = useAgentFactoryStore();
|
|
291
295
|
const [discovered, setDiscovered] = useState<DiscoveredNode[]>([]);
|
|
292
296
|
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
|
|
@@ -550,10 +554,10 @@ export function DiscoveryDialog({ open, onOpenChange }: DiscoveryDialogProps) {
|
|
|
550
554
|
<DialogHeader>
|
|
551
555
|
<DialogTitle className="flex items-center gap-3">
|
|
552
556
|
<Package className="w-6 h-6" />
|
|
553
|
-
|
|
557
|
+
{t('discoverPlugins')}
|
|
554
558
|
</DialogTitle>
|
|
555
559
|
<DialogDescription>
|
|
556
|
-
|
|
560
|
+
{t('scanDescription')}
|
|
557
561
|
</DialogDescription>
|
|
558
562
|
</DialogHeader>
|
|
559
563
|
|
|
@@ -561,17 +565,17 @@ export function DiscoveryDialog({ open, onOpenChange }: DiscoveryDialogProps) {
|
|
|
561
565
|
{!scanned ? (
|
|
562
566
|
<div className="text-center py-12 text-muted-foreground">
|
|
563
567
|
<Package className="w-12 h-12 mx-auto mb-4 text-muted-foreground/50" />
|
|
564
|
-
<p className="mb-4">
|
|
568
|
+
<p className="mb-4">{t('clickScanToSearch')}</p>
|
|
565
569
|
<Button onClick={handleScan} disabled={scanning}>
|
|
566
570
|
{scanning ? (
|
|
567
571
|
<>
|
|
568
572
|
<RefreshCw className="w-4 h-4 mr-2 animate-spin" />
|
|
569
|
-
|
|
573
|
+
{tCommon('scanning')}
|
|
570
574
|
</>
|
|
571
575
|
) : (
|
|
572
576
|
<>
|
|
573
577
|
<Search className="w-4 h-4 mr-2" />
|
|
574
|
-
|
|
578
|
+
{t('scan')}
|
|
575
579
|
</>
|
|
576
580
|
)}
|
|
577
581
|
</Button>
|
|
@@ -579,11 +583,11 @@ export function DiscoveryDialog({ open, onOpenChange }: DiscoveryDialogProps) {
|
|
|
579
583
|
) : scanning ? (
|
|
580
584
|
<div className="text-center py-8 text-muted-foreground">
|
|
581
585
|
<div className="animate-spin w-8 h-8 border-2 border-primary border-t-transparent rounded-full mx-auto mb-4" />
|
|
582
|
-
|
|
586
|
+
{t('scanningForPlugins')}
|
|
583
587
|
</div>
|
|
584
588
|
) : discovered.length === 0 ? (
|
|
585
589
|
<div className="text-center py-8 text-muted-foreground">
|
|
586
|
-
<p className="mb-4">
|
|
590
|
+
<p className="mb-4">{t('noPluginsFoundScan')}</p>
|
|
587
591
|
<Button variant="outline" onClick={handleScan} disabled={scanning}>
|
|
588
592
|
<RefreshCw className="w-4 h-4 mr-2" />
|
|
589
593
|
Rescan
|
|
@@ -592,19 +596,19 @@ export function DiscoveryDialog({ open, onOpenChange }: DiscoveryDialogProps) {
|
|
|
592
596
|
) : (
|
|
593
597
|
<div className="space-y-2">
|
|
594
598
|
<div className="flex items-center justify-between px-2 py-1 text-sm text-muted-foreground sticky top-0 bg-background">
|
|
595
|
-
<span>{statusMap.size}
|
|
599
|
+
<span>{statusMap.size} {t('pluginsFound')}</span>
|
|
596
600
|
<div className="flex gap-2">
|
|
597
601
|
<span className="flex items-center gap-1">
|
|
598
602
|
<span className="w-2 h-2 rounded-full bg-blue-500"></span>
|
|
599
|
-
{newCount}
|
|
603
|
+
{newCount} {t('newStatus')}
|
|
600
604
|
</span>
|
|
601
605
|
<span className="flex items-center gap-1">
|
|
602
606
|
<span className="w-2 h-2 rounded-full bg-orange-500"></span>
|
|
603
|
-
{updateCount} updates
|
|
607
|
+
{updateCount} {t('updates')}
|
|
604
608
|
</span>
|
|
605
609
|
<span className="flex items-center gap-1">
|
|
606
610
|
<span className="w-2 h-2 rounded-full bg-gray-400"></span>
|
|
607
|
-
{currentCount} current
|
|
611
|
+
{currentCount} {t('current')}
|
|
608
612
|
</span>
|
|
609
613
|
</div>
|
|
610
614
|
</div>
|
|
@@ -634,7 +638,7 @@ export function DiscoveryDialog({ open, onOpenChange }: DiscoveryDialogProps) {
|
|
|
634
638
|
</span>
|
|
635
639
|
<div className="flex gap-2">
|
|
636
640
|
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
|
637
|
-
|
|
641
|
+
{tCommon('close')}
|
|
638
642
|
</Button>
|
|
639
643
|
{scanned && discovered.length > 0 && (
|
|
640
644
|
<>
|
|
@@ -644,7 +648,7 @@ export function DiscoveryDialog({ open, onOpenChange }: DiscoveryDialogProps) {
|
|
|
644
648
|
disabled={scanning}
|
|
645
649
|
>
|
|
646
650
|
<RefreshCw className="w-4 h-4 mr-2" />
|
|
647
|
-
|
|
651
|
+
{t('rescan')}
|
|
648
652
|
</Button>
|
|
649
653
|
{needsAction > 0 && (
|
|
650
654
|
<Button
|
|
@@ -654,11 +658,11 @@ export function DiscoveryDialog({ open, onOpenChange }: DiscoveryDialogProps) {
|
|
|
654
658
|
{importing ? (
|
|
655
659
|
<>
|
|
656
660
|
<RefreshCw className="w-4 h-4 mr-2 animate-spin" />
|
|
657
|
-
|
|
661
|
+
{tCommon('importing')}
|
|
658
662
|
</>
|
|
659
663
|
) : (
|
|
660
664
|
<>
|
|
661
|
-
|
|
665
|
+
{t('importAll')} ({needsAction})
|
|
662
666
|
</>
|
|
663
667
|
)}
|
|
664
668
|
</Button>
|
|
@@ -671,10 +675,10 @@ export function DiscoveryDialog({ open, onOpenChange }: DiscoveryDialogProps) {
|
|
|
671
675
|
{importing ? (
|
|
672
676
|
<>
|
|
673
677
|
<RefreshCw className="w-4 h-4 mr-2 animate-spin" />
|
|
674
|
-
|
|
678
|
+
{tCommon('importing')}
|
|
675
679
|
</>
|
|
676
680
|
) : (
|
|
677
|
-
|
|
681
|
+
t('importSelected', { count: selectedIds.size })
|
|
678
682
|
)}
|
|
679
683
|
</Button>
|
|
680
684
|
)}
|