增加永久存储记录信息
完善 i18n
This commit is contained in:
29
src/App.tsx
29
src/App.tsx
@@ -1,6 +1,6 @@
|
|||||||
import { useState, useEffect, lazy, Suspense } from "react";
|
import { useState, useEffect, lazy, Suspense } from "react";
|
||||||
import { motion, AnimatePresence } from "framer-motion";
|
import { motion, AnimatePresence } from "framer-motion";
|
||||||
import { Plus, Loader2, ArrowLeft } from "lucide-react";
|
import { Loader2, ArrowLeft } from "lucide-react";
|
||||||
import { api, type Project, type Session, type ClaudeMdFile } from "@/lib/api";
|
import { api, type Project, type Session, type ClaudeMdFile } from "@/lib/api";
|
||||||
import { OutputCacheProvider } from "@/lib/outputCache";
|
import { OutputCacheProvider } from "@/lib/outputCache";
|
||||||
import { TabProvider } from "@/contexts/TabContext";
|
import { TabProvider } from "@/contexts/TabContext";
|
||||||
@@ -98,6 +98,13 @@ function AppContent() {
|
|||||||
initializeBackendLanguage();
|
initializeBackendLanguage();
|
||||||
}, []); // Run once on app startup
|
}, []); // Run once on app startup
|
||||||
|
|
||||||
|
// Update document title based on current language
|
||||||
|
useEffect(() => {
|
||||||
|
try {
|
||||||
|
document.title = `${t('app.name')} - ${t('app.tagline')}`;
|
||||||
|
} catch {}
|
||||||
|
}, [t]);
|
||||||
|
|
||||||
// Track when user reaches different journey stages
|
// Track when user reaches different journey stages
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (view === "projects" && projects.length > 0 && !hasTrackedFirstChat) {
|
if (view === "projects" && projects.length > 0 && !hasTrackedFirstChat) {
|
||||||
@@ -427,32 +434,16 @@ function AppContent() {
|
|||||||
exit={{ opacity: 0, x: 20 }}
|
exit={{ opacity: 0, x: 20 }}
|
||||||
transition={{ duration: 0.3 }}
|
transition={{ duration: 0.3 }}
|
||||||
>
|
>
|
||||||
{/* New session button at the top */}
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, y: 20 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
transition={{ duration: 0.5 }}
|
|
||||||
className="mb-4"
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
onClick={handleNewSession}
|
|
||||||
size="default"
|
|
||||||
className="w-full max-w-md"
|
|
||||||
>
|
|
||||||
<Plus className="mr-2 h-4 w-4" />
|
|
||||||
{t('newClaudeCodeSession')}
|
|
||||||
</Button>
|
|
||||||
</motion.div>
|
|
||||||
|
|
||||||
{/* Running Claude Sessions */}
|
{/* Running Claude Sessions */}
|
||||||
<RunningClaudeSessions />
|
<RunningClaudeSessions />
|
||||||
|
|
||||||
{/* Project list */}
|
{/* Project list with integrated new session button */}
|
||||||
{projects.length > 0 ? (
|
{projects.length > 0 ? (
|
||||||
<ProjectList
|
<ProjectList
|
||||||
projects={projects}
|
projects={projects}
|
||||||
onProjectClick={handleProjectClick}
|
onProjectClick={handleProjectClick}
|
||||||
onProjectSettings={handleProjectSettings}
|
onProjectSettings={handleProjectSettings}
|
||||||
|
onNewSession={handleNewSession}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
className="animate-fade-in"
|
className="animate-fade-in"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -274,7 +274,7 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
|
|||||||
const selected = await open({
|
const selected = await open({
|
||||||
directory: true,
|
directory: true,
|
||||||
multiple: false,
|
multiple: false,
|
||||||
title: "Select Project Directory"
|
title: t('webview.selectProjectDirectory')
|
||||||
});
|
});
|
||||||
|
|
||||||
if (selected) {
|
if (selected) {
|
||||||
@@ -576,7 +576,7 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
|
|||||||
<div>
|
<div>
|
||||||
<h1 className="text-xl font-bold">{t('agents.execute')}: {agent.name}</h1>
|
<h1 className="text-xl font-bold">{t('agents.execute')}: {agent.name}</h1>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
{model === 'opus' ? 'Claude 4.1 Opus' : 'Claude 4 Sonnet'}
|
{model === 'opus' ? t('agents.opusName') : t('agents.sonnetName')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -669,7 +669,7 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
|
|||||||
<div className="w-1.5 h-1.5 rounded-full bg-primary-foreground" />
|
<div className="w-1.5 h-1.5 rounded-full bg-primary-foreground" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<span>Claude 4 Sonnet</span>
|
<span>{t('agents.sonnetName')}</span>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -695,7 +695,7 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
|
|||||||
<div className="w-1.5 h-1.5 rounded-full bg-primary-foreground" />
|
<div className="w-1.5 h-1.5 rounded-full bg-primary-foreground" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<span>Claude 4.1 Opus</span>
|
<span>{t('agents.opusName')}</span>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -772,7 +772,7 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
|
|||||||
<div className="flex items-center justify-center h-full">
|
<div className="flex items-center justify-center h-full">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Loader2 className="h-6 w-6 animate-spin" />
|
<Loader2 className="h-6 w-6 animate-spin" />
|
||||||
<span className="text-sm text-muted-foreground">Initializing agent...</span>
|
<span className="text-sm text-muted-foreground">{t('agents.initializing') || 'Initializing agent...'}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -826,11 +826,11 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
|
|||||||
<div className="flex items-center justify-between p-4 border-b border-border">
|
<div className="flex items-center justify-between p-4 border-b border-border">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{renderIcon()}
|
{renderIcon()}
|
||||||
<h2 className="text-lg font-semibold">{agent.name} - Output</h2>
|
<h2 className="text-lg font-semibold">{agent.name} - {t('app.output')}</h2>
|
||||||
{isRunning && (
|
{isRunning && (
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
|
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
|
||||||
<span className="text-xs text-green-600 font-medium">Running</span>
|
<span className="text-xs text-green-600 font-medium">{t('agents.statusRunning')}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -878,7 +878,7 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
|
|||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2"
|
||||||
>
|
>
|
||||||
<X className="h-4 w-4" />
|
<X className="h-4 w-4" />
|
||||||
Close
|
{t('app.close')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -914,7 +914,7 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
|
|||||||
<div className="flex items-center justify-center h-full">
|
<div className="flex items-center justify-center h-full">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Loader2 className="h-6 w-6 animate-spin" />
|
<Loader2 className="h-6 w-6 animate-spin" />
|
||||||
<span className="text-sm text-muted-foreground">Initializing agent...</span>
|
<span className="text-sm text-muted-foreground">{t('agents.initializing') || 'Initializing agent...'}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -967,8 +967,8 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
|
|||||||
|
|
||||||
<Tabs value={activeHooksTab} onValueChange={setActiveHooksTab} className="flex-1 flex flex-col overflow-hidden">
|
<Tabs value={activeHooksTab} onValueChange={setActiveHooksTab} className="flex-1 flex flex-col overflow-hidden">
|
||||||
<TabsList className="grid w-full grid-cols-2">
|
<TabsList className="grid w-full grid-cols-2">
|
||||||
<TabsTrigger value="project">Project Settings</TabsTrigger>
|
<TabsTrigger value="project">{t('agents.projectSettings') || 'Project Settings'}</TabsTrigger>
|
||||||
<TabsTrigger value="local">Local Settings</TabsTrigger>
|
<TabsTrigger value="local">{t('agents.localSettings') || 'Local Settings'}</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
<TabsContent value="project" className="flex-1 overflow-auto">
|
<TabsContent value="project" className="flex-1 overflow-auto">
|
||||||
|
|||||||
@@ -237,7 +237,7 @@ export function AgentRunOutputViewer({
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load agent output:', error);
|
console.error('Failed to load agent output:', error);
|
||||||
setToast({ message: 'Failed to load agent output', type: 'error' });
|
setToast({ message: t('app.failedToLoadSessionOutput'), type: 'error' });
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -286,12 +286,12 @@ export function AgentRunOutputViewer({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const completeUnlisten = await listen<boolean>(`agent-complete:${run!.id}`, () => {
|
const completeUnlisten = await listen<boolean>(`agent-complete:${run!.id}`, () => {
|
||||||
setToast({ message: 'Agent execution completed', type: 'success' });
|
setToast({ message: t('app.agentExecutionCompleted'), type: 'success' });
|
||||||
// Don't set status here as the parent component should handle it
|
// Don't set status here as the parent component should handle it
|
||||||
});
|
});
|
||||||
|
|
||||||
const cancelUnlisten = await listen<boolean>(`agent-cancelled:${run!.id}`, () => {
|
const cancelUnlisten = await listen<boolean>(`agent-cancelled:${run!.id}`, () => {
|
||||||
setToast({ message: 'Agent execution was cancelled', type: 'error' });
|
setToast({ message: t('app.agentExecutionCancelled'), type: 'error' });
|
||||||
});
|
});
|
||||||
|
|
||||||
unlistenRefs.current = [outputUnlisten, errorUnlisten, completeUnlisten, cancelUnlisten];
|
unlistenRefs.current = [outputUnlisten, errorUnlisten, completeUnlisten, cancelUnlisten];
|
||||||
@@ -305,7 +305,7 @@ export function AgentRunOutputViewer({
|
|||||||
const jsonl = rawJsonlOutput.join('\n');
|
const jsonl = rawJsonlOutput.join('\n');
|
||||||
await navigator.clipboard.writeText(jsonl);
|
await navigator.clipboard.writeText(jsonl);
|
||||||
setCopyPopoverOpen(false);
|
setCopyPopoverOpen(false);
|
||||||
setToast({ message: 'Output copied as JSONL', type: 'success' });
|
setToast({ message: t('webview.sessionOutputCopiedJsonl'), type: 'success' });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCopyAsMarkdown = async () => {
|
const handleCopyAsMarkdown = async () => {
|
||||||
@@ -363,7 +363,7 @@ export function AgentRunOutputViewer({
|
|||||||
|
|
||||||
await navigator.clipboard.writeText(markdown);
|
await navigator.clipboard.writeText(markdown);
|
||||||
setCopyPopoverOpen(false);
|
setCopyPopoverOpen(false);
|
||||||
setToast({ message: 'Output copied as Markdown', type: 'success' });
|
setToast({ message: t('webview.sessionOutputCopiedMarkdown'), type: 'success' });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRefresh = async () => {
|
const handleRefresh = async () => {
|
||||||
@@ -383,7 +383,7 @@ export function AgentRunOutputViewer({
|
|||||||
const success = await api.killAgentSession(run.id);
|
const success = await api.killAgentSession(run.id);
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
setToast({ message: 'Agent execution stopped', type: 'success' });
|
setToast({ message: t('agentRun.executionStopped'), type: 'success' });
|
||||||
|
|
||||||
// Clean up listeners
|
// Clean up listeners
|
||||||
unlistenRefs.current.forEach(unlisten => unlisten());
|
unlistenRefs.current.forEach(unlisten => unlisten());
|
||||||
@@ -410,14 +410,11 @@ export function AgentRunOutputViewer({
|
|||||||
// Refresh the output to get updated status
|
// Refresh the output to get updated status
|
||||||
await loadOutput(true);
|
await loadOutput(true);
|
||||||
} else {
|
} else {
|
||||||
setToast({ message: 'Failed to stop agent - it may have already finished', type: 'error' });
|
setToast({ message: t('agentRun.stopFailed'), type: 'error' });
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[AgentRunOutputViewer] Failed to stop agent:', err);
|
console.error('[AgentRunOutputViewer] Failed to stop agent:', err);
|
||||||
setToast({
|
setToast({ message: t('agentRun.stopFailed'), type: 'error' });
|
||||||
message: `Failed to stop execution: ${err instanceof Error ? err.message : 'Unknown error'}`,
|
|
||||||
type: 'error'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -515,7 +512,7 @@ export function AgentRunOutputViewer({
|
|||||||
<div className="flex items-center justify-center h-full">
|
<div className="flex items-center justify-center h-full">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto mb-4"></div>
|
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto mb-4"></div>
|
||||||
<p className="text-muted-foreground">Loading agent run...</p>
|
<p className="text-muted-foreground">{t('app.loadingAgentRun')}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -537,7 +534,7 @@ export function AgentRunOutputViewer({
|
|||||||
{run.status === 'running' && (
|
{run.status === 'running' && (
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
|
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
|
||||||
<span className="text-xs text-green-600 font-medium">Running</span>
|
<span className="text-xs text-green-600 font-medium">{t('agents.statusRunning')}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
@@ -546,7 +543,7 @@ export function AgentRunOutputViewer({
|
|||||||
</p>
|
</p>
|
||||||
<div className="flex items-center gap-3 text-xs text-muted-foreground mt-2">
|
<div className="flex items-center gap-3 text-xs text-muted-foreground mt-2">
|
||||||
<Badge variant="outline" className="text-xs">
|
<Badge variant="outline" className="text-xs">
|
||||||
{run.model === 'opus' ? 'Claude 4.1 Opus' : 'Claude 4 Sonnet'}
|
{run.model === 'opus' ? t('agents.opusName') : t('agents.sonnetName')}
|
||||||
</Badge>
|
</Badge>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<Clock className="h-3 w-3" />
|
<Clock className="h-3 w-3" />
|
||||||
@@ -579,7 +576,7 @@ export function AgentRunOutputViewer({
|
|||||||
className="h-8 px-2"
|
className="h-8 px-2"
|
||||||
>
|
>
|
||||||
<Copy className="h-4 w-4 mr-1" />
|
<Copy className="h-4 w-4 mr-1" />
|
||||||
Copy
|
{t('app.copyOutput')}
|
||||||
<ChevronDown className="h-3 w-3 ml-1" />
|
<ChevronDown className="h-3 w-3 ml-1" />
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
@@ -611,7 +608,7 @@ export function AgentRunOutputViewer({
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => setIsFullscreen(!isFullscreen)}
|
onClick={() => setIsFullscreen(!isFullscreen)}
|
||||||
title={isFullscreen ? "Exit fullscreen" : "Enter fullscreen"}
|
title={isFullscreen ? t('webview.exitFullScreen') : t('webview.enterFullScreen')}
|
||||||
className="h-8 px-2"
|
className="h-8 px-2"
|
||||||
>
|
>
|
||||||
{isFullscreen ? (
|
{isFullscreen ? (
|
||||||
@@ -625,7 +622,7 @@ export function AgentRunOutputViewer({
|
|||||||
size="sm"
|
size="sm"
|
||||||
onClick={handleRefresh}
|
onClick={handleRefresh}
|
||||||
disabled={refreshing}
|
disabled={refreshing}
|
||||||
title="Refresh output"
|
title={t('app.refresh')}
|
||||||
className="h-8 px-2"
|
className="h-8 px-2"
|
||||||
>
|
>
|
||||||
<RotateCcw className={`h-4 w-4 ${refreshing ? 'animate-spin' : ''}`} />
|
<RotateCcw className={`h-4 w-4 ${refreshing ? 'animate-spin' : ''}`} />
|
||||||
@@ -636,7 +633,7 @@ export function AgentRunOutputViewer({
|
|||||||
size="sm"
|
size="sm"
|
||||||
onClick={handleStop}
|
onClick={handleStop}
|
||||||
disabled={refreshing}
|
disabled={refreshing}
|
||||||
title="Stop execution"
|
title={t('agents.stop')}
|
||||||
className="h-8 px-2 text-destructive hover:text-destructive"
|
className="h-8 px-2 text-destructive hover:text-destructive"
|
||||||
>
|
>
|
||||||
<StopCircle className="h-4 w-4" />
|
<StopCircle className="h-4 w-4" />
|
||||||
@@ -650,12 +647,12 @@ export function AgentRunOutputViewer({
|
|||||||
<div className="flex items-center justify-center h-full">
|
<div className="flex items-center justify-center h-full">
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<RefreshCw className="h-4 w-4 animate-spin" />
|
<RefreshCw className="h-4 w-4 animate-spin" />
|
||||||
<span>Loading output...</span>
|
<span>{t('app.loadingOutput')}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : messages.length === 0 ? (
|
) : messages.length === 0 ? (
|
||||||
<div className="flex items-center justify-center h-full text-muted-foreground">
|
<div className="flex items-center justify-center h-full text-muted-foreground">
|
||||||
<p>No output available yet</p>
|
<p>{t('app.noOutput')}</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
@@ -745,7 +742,7 @@ export function AgentRunOutputViewer({
|
|||||||
disabled={refreshing}
|
disabled={refreshing}
|
||||||
>
|
>
|
||||||
<StopCircle className="h-4 w-4 mr-2" />
|
<StopCircle className="h-4 w-4 mr-2" />
|
||||||
Stop
|
{t('agents.stop')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<Button
|
<Button
|
||||||
@@ -754,7 +751,7 @@ export function AgentRunOutputViewer({
|
|||||||
onClick={() => setIsFullscreen(false)}
|
onClick={() => setIsFullscreen(false)}
|
||||||
>
|
>
|
||||||
<Minimize2 className="h-4 w-4 mr-2" />
|
<Minimize2 className="h-4 w-4 mr-2" />
|
||||||
Exit Fullscreen
|
{t('webview.exitFullScreen')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -766,7 +763,7 @@ export function AgentRunOutputViewer({
|
|||||||
<div className="max-w-4xl mx-auto space-y-2">
|
<div className="max-w-4xl mx-auto space-y-2">
|
||||||
{messages.length === 0 ? (
|
{messages.length === 0 ? (
|
||||||
<div className="text-center text-muted-foreground py-8">
|
<div className="text-center text-muted-foreground py-8">
|
||||||
No output available yet
|
{t('app.noOutput')}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ export const AgentRunView: React.FC<AgentRunViewProps> = ({
|
|||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to load run:", err);
|
console.error("Failed to load run:", err);
|
||||||
setError("Failed to load execution details");
|
setError(t('agentRun.loadFailed'));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -198,7 +198,7 @@ export const AgentRunView: React.FC<AgentRunViewProps> = ({
|
|||||||
type: "result",
|
type: "result",
|
||||||
subtype: "error",
|
subtype: "error",
|
||||||
is_error: true,
|
is_error: true,
|
||||||
result: "Execution stopped by user",
|
result: t('agentRun.executionStopped'),
|
||||||
duration_ms: 0,
|
duration_ms: 0,
|
||||||
usage: {
|
usage: {
|
||||||
input_tokens: 0,
|
input_tokens: 0,
|
||||||
@@ -235,8 +235,8 @@ export const AgentRunView: React.FC<AgentRunViewProps> = ({
|
|||||||
if (error || !run) {
|
if (error || !run) {
|
||||||
return (
|
return (
|
||||||
<div className={cn("flex flex-col items-center justify-center h-full", className)}>
|
<div className={cn("flex flex-col items-center justify-center h-full", className)}>
|
||||||
<p className="text-destructive mb-4">{error || "Run not found"}</p>
|
<p className="text-destructive mb-4">{error || t('agentRun.runNotFound')}</p>
|
||||||
<Button onClick={onBack}>Go Back</Button>
|
<Button onClick={onBack}>{t('app.back')}</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -264,7 +264,7 @@ export const AgentRunView: React.FC<AgentRunViewProps> = ({
|
|||||||
{renderIcon(run.agent_icon)}
|
{renderIcon(run.agent_icon)}
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-lg font-semibold">{run.agent_name}</h2>
|
<h2 className="text-lg font-semibold">{run.agent_name}</h2>
|
||||||
<p className="text-xs text-muted-foreground">Execution History</p>
|
<p className="text-xs text-muted-foreground">{t('agents.executionHistory')}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -278,7 +278,7 @@ export const AgentRunView: React.FC<AgentRunViewProps> = ({
|
|||||||
className="text-destructive hover:text-destructive"
|
className="text-destructive hover:text-destructive"
|
||||||
>
|
>
|
||||||
<StopCircle className="h-4 w-4 mr-1" />
|
<StopCircle className="h-4 w-4 mr-1" />
|
||||||
Stop
|
{t('agents.stop')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -326,10 +326,10 @@ export const AgentRunView: React.FC<AgentRunViewProps> = ({
|
|||||||
<CardContent className="p-4">
|
<CardContent className="p-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<h3 className="text-sm font-medium">Task:</h3>
|
<h3 className="text-sm font-medium">{t('app.task')}:</h3>
|
||||||
<p className="text-sm text-muted-foreground flex-1">{run.task}</p>
|
<p className="text-sm text-muted-foreground flex-1">{run.task}</p>
|
||||||
<Badge variant="outline" className="text-xs">
|
<Badge variant="outline" className="text-xs">
|
||||||
{run.model === 'opus' ? 'Claude 4.1 Opus' : 'Claude 4 Sonnet'}
|
{run.model === 'opus' ? t('agents.opusName') : t('agents.sonnetName')}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -349,7 +349,7 @@ export const AgentRunView: React.FC<AgentRunViewProps> = ({
|
|||||||
{run.metrics?.total_tokens && (
|
{run.metrics?.total_tokens && (
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<Hash className="h-3 w-3" />
|
<Hash className="h-3 w-3" />
|
||||||
<span>{run.metrics.total_tokens} tokens</span>
|
<span>{run.metrics.total_tokens} {t('usage.tokens')}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -442,7 +442,7 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => handleExportAgent(agent)}
|
onClick={() => handleExportAgent(agent)}
|
||||||
className="flex items-center gap-1"
|
className="flex items-center gap-1"
|
||||||
title="Export agent to .claudia.json"
|
title={t('agents.exportToFile')}
|
||||||
>
|
>
|
||||||
<Upload className="h-3 w-3" />
|
<Upload className="h-3 w-3" />
|
||||||
{t('agents.export')}
|
{t('agents.export')}
|
||||||
|
|||||||
@@ -634,7 +634,7 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
|
|||||||
const selected = await open({
|
const selected = await open({
|
||||||
directory: true,
|
directory: true,
|
||||||
multiple: false,
|
multiple: false,
|
||||||
title: "Select Project Directory"
|
title: t('webview.selectProjectDirectory')
|
||||||
});
|
});
|
||||||
|
|
||||||
if (selected) {
|
if (selected) {
|
||||||
@@ -644,7 +644,7 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to select directory:", err);
|
console.error("Failed to select directory:", err);
|
||||||
const errorMessage = err instanceof Error ? err.message : String(err);
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
||||||
setError(`Failed to select directory: ${errorMessage}`);
|
setError(t('app.selectDirectoryFailed', { message: errorMessage }));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -652,7 +652,7 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
|
|||||||
console.log('[ClaudeCodeSession] handleSendPrompt called with:', { prompt, model, projectPath, claudeSessionId, effectiveSession });
|
console.log('[ClaudeCodeSession] handleSendPrompt called with:', { prompt, model, projectPath, claudeSessionId, effectiveSession });
|
||||||
|
|
||||||
if (!projectPath) {
|
if (!projectPath) {
|
||||||
setError("Please select a project directory first");
|
setError(t('app.selectProjectFirst'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React, { Component, ReactNode } from "react";
|
|||||||
import { AlertCircle } from "lucide-react";
|
import { AlertCircle } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardContent } from "@/components/ui/card";
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
|
import i18n from "@/lib/i18n";
|
||||||
|
|
||||||
interface ErrorBoundaryProps {
|
interface ErrorBoundaryProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
@@ -51,14 +52,14 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
|
|||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-4">
|
||||||
<AlertCircle className="h-8 w-8 text-destructive flex-shrink-0 mt-0.5" />
|
<AlertCircle className="h-8 w-8 text-destructive flex-shrink-0 mt-0.5" />
|
||||||
<div className="flex-1 space-y-2">
|
<div className="flex-1 space-y-2">
|
||||||
<h3 className="text-lg font-semibold">Something went wrong</h3>
|
<h3 className="text-lg font-semibold">{i18n.t('errorBoundary.somethingWentWrong')}</h3>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
An error occurred while rendering this component.
|
{i18n.t('errorBoundary.errorOccurred')}
|
||||||
</p>
|
</p>
|
||||||
{this.state.error.message && (
|
{this.state.error.message && (
|
||||||
<details className="mt-2">
|
<details className="mt-2">
|
||||||
<summary className="text-sm cursor-pointer text-muted-foreground hover:text-foreground">
|
<summary className="text-sm cursor-pointer text-muted-foreground hover:text-foreground">
|
||||||
Error details
|
{i18n.t('errorBoundary.errorDetails')}
|
||||||
</summary>
|
</summary>
|
||||||
<pre className="mt-2 text-xs bg-muted p-2 rounded overflow-auto">
|
<pre className="mt-2 text-xs bg-muted p-2 rounded overflow-auto">
|
||||||
{this.state.error.message}
|
{this.state.error.message}
|
||||||
@@ -70,7 +71,7 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
|
|||||||
size="sm"
|
size="sm"
|
||||||
className="mt-4"
|
className="mt-4"
|
||||||
>
|
>
|
||||||
Try again
|
{i18n.t('errorBoundary.tryAgain')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { Button } from "@/components/ui/button";
|
|||||||
import { openUrl } from "@tauri-apps/plugin-opener";
|
import { openUrl } from "@tauri-apps/plugin-opener";
|
||||||
import asteriskLogo from "@/assets/nfo/asterisk-logo.png";
|
import asteriskLogo from "@/assets/nfo/asterisk-logo.png";
|
||||||
import keygennMusic from "@/assets/nfo/claudia-nfo.ogg";
|
import keygennMusic from "@/assets/nfo/claudia-nfo.ogg";
|
||||||
|
import { useTranslation } from "@/hooks/useTranslation";
|
||||||
|
|
||||||
interface NFOCreditsProps {
|
interface NFOCreditsProps {
|
||||||
/**
|
/**
|
||||||
@@ -22,6 +23,7 @@ interface NFOCreditsProps {
|
|||||||
* <NFOCredits onClose={() => setShowNFO(false)} />
|
* <NFOCredits onClose={() => setShowNFO(false)} />
|
||||||
*/
|
*/
|
||||||
export const NFOCredits: React.FC<NFOCreditsProps> = ({ onClose }) => {
|
export const NFOCredits: React.FC<NFOCreditsProps> = ({ onClose }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const audioRef = useRef<HTMLAudioElement | null>(null);
|
const audioRef = useRef<HTMLAudioElement | null>(null);
|
||||||
const scrollRef = useRef<HTMLDivElement | null>(null);
|
const scrollRef = useRef<HTMLDivElement | null>(null);
|
||||||
const [isMuted, setIsMuted] = useState(false);
|
const [isMuted, setIsMuted] = useState(false);
|
||||||
@@ -159,10 +161,10 @@ export const NFOCredits: React.FC<NFOCreditsProps> = ({ onClose }) => {
|
|||||||
await openUrl("https://github.com/getAsterisk/claudia/issues/new");
|
await openUrl("https://github.com/getAsterisk/claudia/issues/new");
|
||||||
}}
|
}}
|
||||||
className="flex items-center gap-1 h-auto px-2 py-1"
|
className="flex items-center gap-1 h-auto px-2 py-1"
|
||||||
title="File a bug"
|
title={t('app.fileABug')}
|
||||||
>
|
>
|
||||||
<Github className="h-3 w-3" />
|
<Github className="h-3 w-3" />
|
||||||
<span className="text-xs">File a bug</span>
|
<span className="text-xs">{t('app.fileABug')}</span>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ import {
|
|||||||
Settings,
|
Settings,
|
||||||
MoreVertical,
|
MoreVertical,
|
||||||
Search,
|
Search,
|
||||||
X
|
X,
|
||||||
|
Plus
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card } from "@/components/ui/card";
|
import { Card } from "@/components/ui/card";
|
||||||
@@ -39,6 +40,10 @@ interface ProjectListProps {
|
|||||||
* Callback when hooks configuration is clicked
|
* Callback when hooks configuration is clicked
|
||||||
*/
|
*/
|
||||||
onProjectSettings?: (project: Project) => void;
|
onProjectSettings?: (project: Project) => void;
|
||||||
|
/**
|
||||||
|
* Callback when new session button is clicked
|
||||||
|
*/
|
||||||
|
onNewSession?: () => void;
|
||||||
/**
|
/**
|
||||||
* Whether the list is currently loading
|
* Whether the list is currently loading
|
||||||
*/
|
*/
|
||||||
@@ -72,6 +77,7 @@ export const ProjectList: React.FC<ProjectListProps> = ({
|
|||||||
projects,
|
projects,
|
||||||
onProjectClick,
|
onProjectClick,
|
||||||
onProjectSettings,
|
onProjectSettings,
|
||||||
|
onNewSession,
|
||||||
className,
|
className,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -107,16 +113,32 @@ export const ProjectList: React.FC<ProjectListProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn("space-y-4", className)}>
|
<div className={cn("space-y-4", className)}>
|
||||||
{/* Search bar and results info */}
|
{/* Action bar with new session button and search */}
|
||||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3">
|
<div className="flex flex-col lg:flex-row gap-3 items-stretch lg:items-center justify-between">
|
||||||
<div className="relative flex-1 max-w-md">
|
{/* New session button */}
|
||||||
|
{onNewSession && (
|
||||||
|
<Button
|
||||||
|
onClick={onNewSession}
|
||||||
|
size="default"
|
||||||
|
className="bg-primary hover:bg-primary/90 text-primary-foreground shadow-lg hover:shadow-xl transition-all duration-200 lg:w-auto w-full group relative overflow-hidden"
|
||||||
|
>
|
||||||
|
{/* Gradient overlay effect */}
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-r from-white/0 via-white/10 to-white/0 translate-x-[-100%] group-hover:translate-x-[100%] transition-transform duration-500" />
|
||||||
|
<Plus className="mr-2 h-4 w-4 relative z-10" />
|
||||||
|
<span className="relative z-10">{t('newClaudeCodeSession')}</span>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Search and results info */}
|
||||||
|
<div className="flex flex-col sm:flex-row sm:items-center gap-3 flex-1 lg:max-w-2xl">
|
||||||
|
<div className="relative flex-1">
|
||||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder={t('searchProjects')}
|
placeholder={t('searchProjects')}
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
className="pl-9 pr-9"
|
className="pl-9 pr-9 h-10"
|
||||||
/>
|
/>
|
||||||
{searchQuery && (
|
{searchQuery && (
|
||||||
<Button
|
<Button
|
||||||
@@ -130,8 +152,8 @@ export const ProjectList: React.FC<ProjectListProps> = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Results info */}
|
{/* Results info */}
|
||||||
<div className="text-sm text-muted-foreground">
|
<div className="text-sm text-muted-foreground whitespace-nowrap">
|
||||||
{searchQuery ? (
|
{searchQuery ? (
|
||||||
<span>
|
<span>
|
||||||
{t('showingResults')}: <span className="font-semibold text-foreground">{filteredAndSortedProjects.length}</span> / {projects.length}
|
{t('showingResults')}: <span className="font-semibold text-foreground">{filteredAndSortedProjects.length}</span> / {projects.length}
|
||||||
@@ -141,6 +163,7 @@ export const ProjectList: React.FC<ProjectListProps> = ({
|
|||||||
{t('totalProjects')}: <span className="font-semibold text-foreground">{projects.length}</span>
|
{t('totalProjects')}: <span className="font-semibold text-foreground">{projects.length}</span>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { HooksEditor } from '@/components/HooksEditor';
|
import { HooksEditor } from '@/components/HooksEditor';
|
||||||
import { SlashCommandsManager } from '@/components/SlashCommandsManager';
|
import { SlashCommandsManager } from '@/components/SlashCommandsManager';
|
||||||
import { api } from '@/lib/api';
|
import { api } from '@/lib/api';
|
||||||
@@ -33,6 +34,7 @@ export const ProjectSettings: React.FC<ProjectSettingsProps> = ({
|
|||||||
onBack,
|
onBack,
|
||||||
className
|
className
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [activeTab, setActiveTab] = useState('commands');
|
const [activeTab, setActiveTab] = useState('commands');
|
||||||
const [toast, setToast] = useState<{ message: string; type: 'success' | 'error' } | null>(null);
|
const [toast, setToast] = useState<{ message: string; type: 'success' | 'error' } | null>(null);
|
||||||
|
|
||||||
@@ -89,7 +91,7 @@ export const ProjectSettings: React.FC<ProjectSettingsProps> = ({
|
|||||||
</Button>
|
</Button>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Settings className="h-5 w-5 text-muted-foreground" />
|
<Settings className="h-5 w-5 text-muted-foreground" />
|
||||||
<h2 className="text-xl font-semibold">Project Settings</h2>
|
<h2 className="text-xl font-semibold">{t('agents.projectSettings')}</h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -109,15 +111,15 @@ export const ProjectSettings: React.FC<ProjectSettingsProps> = ({
|
|||||||
<TabsList className="mb-6">
|
<TabsList className="mb-6">
|
||||||
<TabsTrigger value="commands" className="gap-2">
|
<TabsTrigger value="commands" className="gap-2">
|
||||||
<Command className="h-4 w-4" />
|
<Command className="h-4 w-4" />
|
||||||
Slash Commands
|
{t('slashCommands.slashCommands')}
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger value="project" className="gap-2">
|
<TabsTrigger value="project" className="gap-2">
|
||||||
<GitBranch className="h-4 w-4" />
|
<GitBranch className="h-4 w-4" />
|
||||||
Project Hooks
|
{t('hooks.projectHooks', 'Project Hooks')}
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger value="local" className="gap-2">
|
<TabsTrigger value="local" className="gap-2">
|
||||||
<Shield className="h-4 w-4" />
|
<Shield className="h-4 w-4" />
|
||||||
Local Hooks
|
{t('hooks.localHooks', 'Local Hooks')}
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
@@ -125,7 +127,7 @@ export const ProjectSettings: React.FC<ProjectSettingsProps> = ({
|
|||||||
<Card className="p-6">
|
<Card className="p-6">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-semibold mb-2">Project Slash Commands</h3>
|
<h3 className="text-lg font-semibold mb-2">{t('slashCommands.projectSlashCommands')}</h3>
|
||||||
<p className="text-sm text-muted-foreground mb-4">
|
<p className="text-sm text-muted-foreground mb-4">
|
||||||
Custom commands that are specific to this project. These commands are stored in
|
Custom commands that are specific to this project. These commands are stored in
|
||||||
<code className="mx-1 px-2 py-1 bg-muted rounded text-xs">.claude/slash-commands/</code>
|
<code className="mx-1 px-2 py-1 bg-muted rounded text-xs">.claude/slash-commands/</code>
|
||||||
@@ -145,7 +147,7 @@ export const ProjectSettings: React.FC<ProjectSettingsProps> = ({
|
|||||||
<Card className="p-6">
|
<Card className="p-6">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-semibold mb-2">Project Hooks</h3>
|
<h3 className="text-lg font-semibold mb-2">{t('hooks.projectHooks', 'Project Hooks')}</h3>
|
||||||
<p className="text-sm text-muted-foreground mb-4">
|
<p className="text-sm text-muted-foreground mb-4">
|
||||||
These hooks apply to all users working on this project. They are stored in
|
These hooks apply to all users working on this project. They are stored in
|
||||||
<code className="mx-1 px-2 py-1 bg-muted rounded text-xs">.claude/settings.json</code>
|
<code className="mx-1 px-2 py-1 bg-muted rounded text-xs">.claude/settings.json</code>
|
||||||
@@ -165,7 +167,7 @@ export const ProjectSettings: React.FC<ProjectSettingsProps> = ({
|
|||||||
<Card className="p-6">
|
<Card className="p-6">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-semibold mb-2">Local Hooks</h3>
|
<h3 className="text-lg font-semibold mb-2">{t('hooks.localHooks', 'Local Hooks')}</h3>
|
||||||
<p className="text-sm text-muted-foreground mb-4">
|
<p className="text-sm text-muted-foreground mb-4">
|
||||||
These hooks only apply to your machine. They are stored in
|
These hooks only apply to your machine. They are stored in
|
||||||
<code className="mx-1 px-2 py-1 bg-muted rounded text-xs">.claude/settings.local.json</code>
|
<code className="mx-1 px-2 py-1 bg-muted rounded text-xs">.claude/settings.local.json</code>
|
||||||
@@ -177,7 +179,7 @@ export const ProjectSettings: React.FC<ProjectSettingsProps> = ({
|
|||||||
<AlertTriangle className="h-5 w-5 text-yellow-600" />
|
<AlertTriangle className="h-5 w-5 text-yellow-600" />
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<p className="text-sm text-yellow-600">
|
<p className="text-sm text-yellow-600">
|
||||||
Local settings file is not in .gitignore
|
{t('projectSettings.gitignoreLocalWarning')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
@@ -185,7 +187,7 @@ export const ProjectSettings: React.FC<ProjectSettingsProps> = ({
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={addToGitIgnore}
|
onClick={addToGitIgnore}
|
||||||
>
|
>
|
||||||
Add to .gitignore
|
{t('projectSettings.addToGitignore')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export const RunningClaudeSessions: React.FC<RunningClaudeSessionsProps> = ({
|
|||||||
setError(null);
|
setError(null);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to load running sessions:", err);
|
console.error("Failed to load running sessions:", err);
|
||||||
setError("Failed to load running sessions");
|
setError(t('runningSessions.loadFailed'));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -101,10 +101,10 @@ export const RunningClaudeSessions: React.FC<RunningClaudeSessionsProps> = ({
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse" />
|
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse" />
|
||||||
<h3 className="text-sm font-medium">Active Claude Sessions</h3>
|
<h3 className="text-sm font-medium">{t('runningSessions.title')}</h3>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-xs text-muted-foreground">
|
<span className="text-xs text-muted-foreground">
|
||||||
({runningSessions.length} running)
|
{t('runningSessions.countRunning', { count: runningSessions.length })}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -137,7 +137,7 @@ export const RunningClaudeSessions: React.FC<RunningClaudeSessionsProps> = ({
|
|||||||
{sessionId.substring(0, 20)}...
|
{sessionId.substring(0, 20)}...
|
||||||
</p>
|
</p>
|
||||||
<span className="text-xs text-green-600 font-medium">
|
<span className="text-xs text-green-600 font-medium">
|
||||||
Running
|
{t('runningSessions.running')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -163,7 +163,7 @@ export const RunningClaudeSessions: React.FC<RunningClaudeSessionsProps> = ({
|
|||||||
className="flex-shrink-0"
|
className="flex-shrink-0"
|
||||||
>
|
>
|
||||||
<Play className="h-3 w-3 mr-1" />
|
<Play className="h-3 w-3 mr-1" />
|
||||||
Resume
|
{t('runningSessions.resume')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { ClaudeMemoriesDropdown } from "@/components/ClaudeMemoriesDropdown";
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { formatUnixTimestamp, formatISOTimestamp, truncateText, getFirstLine } from "@/lib/date-utils";
|
import { formatUnixTimestamp, formatISOTimestamp, truncateText, getFirstLine } from "@/lib/date-utils";
|
||||||
import type { Session, ClaudeMdFile } from "@/lib/api";
|
import type { Session, ClaudeMdFile } from "@/lib/api";
|
||||||
|
import { useTranslation } from "@/hooks/useTranslation";
|
||||||
|
|
||||||
interface SessionListProps {
|
interface SessionListProps {
|
||||||
/**
|
/**
|
||||||
@@ -57,6 +58,7 @@ export const SessionList: React.FC<SessionListProps> = ({
|
|||||||
onEditClaudeFile,
|
onEditClaudeFile,
|
||||||
className,
|
className,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
|
||||||
// Calculate pagination
|
// Calculate pagination
|
||||||
@@ -89,7 +91,7 @@ export const SessionList: React.FC<SessionListProps> = ({
|
|||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<h2 className="text-base font-medium truncate">{projectPath}</h2>
|
<h2 className="text-base font-medium truncate">{projectPath}</h2>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
{sessions.length} session{sessions.length !== 1 ? 's' : ''}
|
{t('projects.sessionCount', { count: sessions.length })}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@@ -149,7 +151,7 @@ export const SessionList: React.FC<SessionListProps> = ({
|
|||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="flex items-center space-x-1 text-xs text-muted-foreground">
|
<div className="flex items-center space-x-1 text-xs text-muted-foreground">
|
||||||
<MessageSquare className="h-3 w-3" />
|
<MessageSquare className="h-3 w-3" />
|
||||||
<span>First message:</span>
|
<span>{t('sessions.firstMessage')}</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs line-clamp-2 text-foreground/80">
|
<p className="text-xs line-clamp-2 text-foreground/80">
|
||||||
{truncateText(getFirstLine(session.first_message), 100)}
|
{truncateText(getFirstLine(session.first_message), 100)}
|
||||||
@@ -173,7 +175,7 @@ export const SessionList: React.FC<SessionListProps> = ({
|
|||||||
{session.todo_data && (
|
{session.todo_data && (
|
||||||
<div className="flex items-center space-x-1">
|
<div className="flex items-center space-x-1">
|
||||||
<Calendar className="h-3 w-3" />
|
<Calendar className="h-3 w-3" />
|
||||||
<span>Has todo</span>
|
<span>{t('sessions.hasTodo')}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ export function SessionOutputViewer({ session, onClose, className }: SessionOutp
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load session output:', error);
|
console.error('Failed to load session output:', error);
|
||||||
setToast({ message: 'Failed to load session output', type: 'error' });
|
setToast({ message: t('app.failedToLoadSessionOutput'), type: 'error' });
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -223,12 +223,12 @@ export function SessionOutputViewer({ session, onClose, className }: SessionOutp
|
|||||||
});
|
});
|
||||||
|
|
||||||
const completeUnlisten = await listen<boolean>(`agent-complete:${session.id}`, () => {
|
const completeUnlisten = await listen<boolean>(`agent-complete:${session.id}`, () => {
|
||||||
setToast({ message: 'Agent execution completed', type: 'success' });
|
setToast({ message: t('app.agentExecutionCompleted'), type: 'success' });
|
||||||
// Don't set status here as the parent component should handle it
|
// Don't set status here as the parent component should handle it
|
||||||
});
|
});
|
||||||
|
|
||||||
const cancelUnlisten = await listen<boolean>(`agent-cancelled:${session.id}`, () => {
|
const cancelUnlisten = await listen<boolean>(`agent-cancelled:${session.id}`, () => {
|
||||||
setToast({ message: 'Agent execution was cancelled', type: 'error' });
|
setToast({ message: t('app.agentExecutionCancelled'), type: 'error' });
|
||||||
});
|
});
|
||||||
|
|
||||||
unlistenRefs.current = [outputUnlisten, errorUnlisten, completeUnlisten, cancelUnlisten];
|
unlistenRefs.current = [outputUnlisten, errorUnlisten, completeUnlisten, cancelUnlisten];
|
||||||
@@ -242,7 +242,7 @@ export function SessionOutputViewer({ session, onClose, className }: SessionOutp
|
|||||||
const jsonl = rawJsonlOutput.join('\n');
|
const jsonl = rawJsonlOutput.join('\n');
|
||||||
await navigator.clipboard.writeText(jsonl);
|
await navigator.clipboard.writeText(jsonl);
|
||||||
setCopyPopoverOpen(false);
|
setCopyPopoverOpen(false);
|
||||||
setToast({ message: 'Output copied as JSONL', type: 'success' });
|
setToast({ message: t('webview.sessionOutputCopiedJsonl'), type: 'success' });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCopyAsMarkdown = async () => {
|
const handleCopyAsMarkdown = async () => {
|
||||||
@@ -297,7 +297,7 @@ export function SessionOutputViewer({ session, onClose, className }: SessionOutp
|
|||||||
|
|
||||||
await navigator.clipboard.writeText(markdown);
|
await navigator.clipboard.writeText(markdown);
|
||||||
setCopyPopoverOpen(false);
|
setCopyPopoverOpen(false);
|
||||||
setToast({ message: 'Output copied as Markdown', type: 'success' });
|
setToast({ message: t('webview.sessionOutputCopiedMarkdown'), type: 'success' });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -305,10 +305,10 @@ export function SessionOutputViewer({ session, onClose, className }: SessionOutp
|
|||||||
setRefreshing(true);
|
setRefreshing(true);
|
||||||
try {
|
try {
|
||||||
await loadOutput(true); // Skip cache when manually refreshing
|
await loadOutput(true); // Skip cache when manually refreshing
|
||||||
setToast({ message: 'Output refreshed', type: 'success' });
|
setToast({ message: t('app.outputRefreshed'), type: 'success' });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to refresh output:', error);
|
console.error('Failed to refresh output:', error);
|
||||||
setToast({ message: 'Failed to refresh output', type: 'error' });
|
setToast({ message: t('app.failedToRefreshOutput'), type: 'error' });
|
||||||
} finally {
|
} finally {
|
||||||
setRefreshing(false);
|
setRefreshing(false);
|
||||||
}
|
}
|
||||||
@@ -388,7 +388,7 @@ export function SessionOutputViewer({ session, onClose, className }: SessionOutp
|
|||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<div className="text-2xl">{session.agent_icon}</div>
|
<div className="text-2xl">{session.agent_icon}</div>
|
||||||
<div>
|
<div>
|
||||||
<CardTitle className="text-base">{session.agent_name} - Output</CardTitle>
|
<CardTitle className="text-base">{session.agent_name} - {t('app.output')}</CardTitle>
|
||||||
<div className="flex items-center space-x-2 mt-1">
|
<div className="flex items-center space-x-2 mt-1">
|
||||||
<Badge variant={session.status === 'running' ? 'default' : 'secondary'}>
|
<Badge variant={session.status === 'running' ? 'default' : 'secondary'}>
|
||||||
{session.status}
|
{session.status}
|
||||||
@@ -396,7 +396,7 @@ export function SessionOutputViewer({ session, onClose, className }: SessionOutp
|
|||||||
{session.status === 'running' && (
|
{session.status === 'running' && (
|
||||||
<Badge variant="outline" className="text-xs bg-green-50 text-green-700 border-green-200">
|
<Badge variant="outline" className="text-xs bg-green-50 text-green-700 border-green-200">
|
||||||
<div className="w-1.5 h-1.5 bg-green-500 rounded-full animate-pulse mr-1"></div>
|
<div className="w-1.5 h-1.5 bg-green-500 rounded-full animate-pulse mr-1"></div>
|
||||||
Live
|
{t('agentRun.live')}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
<span className="text-xs text-muted-foreground">
|
<span className="text-xs text-muted-foreground">
|
||||||
@@ -459,7 +459,7 @@ export function SessionOutputViewer({ session, onClose, className }: SessionOutp
|
|||||||
size="sm"
|
size="sm"
|
||||||
onClick={refreshOutput}
|
onClick={refreshOutput}
|
||||||
disabled={refreshing}
|
disabled={refreshing}
|
||||||
title="Refresh output"
|
title={t('app.refresh')}
|
||||||
>
|
>
|
||||||
<RotateCcw className={`h-4 w-4 ${refreshing ? 'animate-spin' : ''}`} />
|
<RotateCcw className={`h-4 w-4 ${refreshing ? 'animate-spin' : ''}`} />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -474,7 +474,7 @@ export function SessionOutputViewer({ session, onClose, className }: SessionOutp
|
|||||||
<div className="flex items-center justify-center h-full">
|
<div className="flex items-center justify-center h-full">
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<RefreshCw className="h-4 w-4 animate-spin" />
|
<RefreshCw className="h-4 w-4 animate-spin" />
|
||||||
<span>Loading output...</span>
|
<span>{t('app.loadingOutput')}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -498,14 +498,14 @@ export function SessionOutputViewer({ session, onClose, className }: SessionOutp
|
|||||||
{session.status === 'running' ? (
|
{session.status === 'running' ? (
|
||||||
<>
|
<>
|
||||||
<RefreshCw className="h-8 w-8 animate-spin text-muted-foreground mb-2" />
|
<RefreshCw className="h-8 w-8 animate-spin text-muted-foreground mb-2" />
|
||||||
<p className="text-muted-foreground">Waiting for output...</p>
|
<p className="text-muted-foreground">{t('app.waitingForOutput')}</p>
|
||||||
<p className="text-xs text-muted-foreground mt-1">
|
<p className="text-xs text-muted-foreground mt-1">
|
||||||
Agent is running but no output received yet
|
{t('app.agentRunningNoOutput')}
|
||||||
</p>
|
</p>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<p className="text-muted-foreground">No output available</p>
|
<p className="text-muted-foreground">{t('app.noOutput')}</p>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -514,7 +514,7 @@ export function SessionOutputViewer({ session, onClose, className }: SessionOutp
|
|||||||
disabled={refreshing}
|
disabled={refreshing}
|
||||||
>
|
>
|
||||||
{refreshing ? <RefreshCw className="h-4 w-4 animate-spin mr-2" /> : <RotateCcw className="h-4 w-4 mr-2" />}
|
{refreshing ? <RefreshCw className="h-4 w-4 animate-spin mr-2" /> : <RotateCcw className="h-4 w-4 mr-2" />}
|
||||||
Refresh
|
{t('app.refresh')}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -551,11 +551,11 @@ export function SessionOutputViewer({ session, onClose, className }: SessionOutp
|
|||||||
<div className="flex items-center justify-between p-4 border-b border-border">
|
<div className="flex items-center justify-between p-4 border-b border-border">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="text-2xl">{session.agent_icon}</div>
|
<div className="text-2xl">{session.agent_icon}</div>
|
||||||
<h2 className="text-lg font-semibold">{session.agent_name} - Output</h2>
|
<h2 className="text-lg font-semibold">{session.agent_name} - {t('app.output')}</h2>
|
||||||
{session.status === 'running' && (
|
{session.status === 'running' && (
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
|
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
|
||||||
<span className="text-xs text-green-600 font-medium">Running</span>
|
<span className="text-xs text-green-600 font-medium">{t('agents.statusRunning')}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -605,7 +605,7 @@ export function SessionOutputViewer({ session, onClose, className }: SessionOutp
|
|||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2"
|
||||||
>
|
>
|
||||||
<X className="h-4 w-4" />
|
<X className="h-4 w-4" />
|
||||||
Close
|
{t('app.close')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -632,14 +632,14 @@ export function SessionOutputViewer({ session, onClose, className }: SessionOutp
|
|||||||
{session.status === 'running' ? (
|
{session.status === 'running' ? (
|
||||||
<>
|
<>
|
||||||
<RefreshCw className="h-8 w-8 animate-spin text-muted-foreground mb-2" />
|
<RefreshCw className="h-8 w-8 animate-spin text-muted-foreground mb-2" />
|
||||||
<p className="text-muted-foreground">Waiting for output...</p>
|
<p className="text-muted-foreground">{t('app.waitingForOutput')}</p>
|
||||||
<p className="text-xs text-muted-foreground mt-1">
|
<p className="text-xs text-muted-foreground mt-1">
|
||||||
Agent is running but no output received yet
|
{t('app.agentRunningNoOutput')}
|
||||||
</p>
|
</p>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<p className="text-muted-foreground">No output available</p>
|
<p className="text-muted-foreground">{t('app.noOutput')}</p>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -658,19 +658,37 @@ export const Settings: React.FC<SettingsProps> = ({
|
|||||||
{/* Cleanup Period */}
|
{/* Cleanup Period */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="cleanup">{t('settings.generalOptions.chatRetention')}</Label>
|
<Label htmlFor="cleanup">{t('settings.generalOptions.chatRetention')}</Label>
|
||||||
<Input
|
<div className="flex items-center gap-3">
|
||||||
id="cleanup"
|
<Input
|
||||||
type="number"
|
id="cleanup"
|
||||||
min="1"
|
type="number"
|
||||||
placeholder="30"
|
min="1"
|
||||||
value={settings?.cleanupPeriodDays || ""}
|
placeholder="30"
|
||||||
onChange={(e) => {
|
value={settings?.cleanupPeriodDays === -1 ? "" : (settings?.cleanupPeriodDays || "")}
|
||||||
const value = e.target.value ? parseInt(e.target.value) : undefined;
|
onChange={(e) => {
|
||||||
updateSetting("cleanupPeriodDays", value);
|
const value = e.target.value ? parseInt(e.target.value) : undefined;
|
||||||
}}
|
updateSetting("cleanupPeriodDays", value);
|
||||||
/>
|
}}
|
||||||
|
disabled={settings?.cleanupPeriodDays === -1}
|
||||||
|
className="flex-1"
|
||||||
|
/>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Switch
|
||||||
|
id="permanent-retention"
|
||||||
|
checked={settings?.cleanupPeriodDays === -1}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
updateSetting("cleanupPeriodDays", checked ? -1 : undefined);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Label htmlFor="permanent-retention" className="text-sm cursor-pointer">
|
||||||
|
{t('settings.generalOptions.permanentRetention')}
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
{t('settings.generalOptions.chatRetentionDesc')}
|
{settings?.cleanupPeriodDays === -1
|
||||||
|
? t('settings.generalOptions.chatRetentionPermanent')
|
||||||
|
: t('settings.generalOptions.chatRetentionDesc')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ export const StorageTab: React.FC = () => {
|
|||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to load tables:", err);
|
console.error("Failed to load tables:", err);
|
||||||
setError("Failed to load tables");
|
setError(t('storageTab.loadTablesFailed'));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -158,7 +158,7 @@ export const StorageTab: React.FC = () => {
|
|||||||
setCurrentPage(page);
|
setCurrentPage(page);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to load table data:", err);
|
console.error("Failed to load table data:", err);
|
||||||
setError("Failed to load table data");
|
setError(t('storageTab.loadTableDataFailed'));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -205,7 +205,7 @@ export const StorageTab: React.FC = () => {
|
|||||||
setEditingRow(null);
|
setEditingRow(null);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to update row:", err);
|
console.error("Failed to update row:", err);
|
||||||
setError("Failed to update row");
|
setError(t('storageTab.updateRowFailed'));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -225,7 +225,7 @@ export const StorageTab: React.FC = () => {
|
|||||||
setDeletingRow(null);
|
setDeletingRow(null);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to delete row:", err);
|
console.error("Failed to delete row:", err);
|
||||||
setError("Failed to delete row");
|
setError(t('storageTab.deleteRowFailed'));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -244,7 +244,7 @@ export const StorageTab: React.FC = () => {
|
|||||||
setNewRow(null);
|
setNewRow(null);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to insert row:", err);
|
console.error("Failed to insert row:", err);
|
||||||
setError("Failed to insert row");
|
setError(t('storageTab.insertRowFailed'));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -269,7 +269,7 @@ export const StorageTab: React.FC = () => {
|
|||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to execute SQL:", err);
|
console.error("Failed to execute SQL:", err);
|
||||||
setSqlError(err instanceof Error ? err.message : "Failed to execute SQL");
|
setSqlError(err instanceof Error ? err.message : t('storageTab.executeSqlFailed'));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -287,14 +287,14 @@ export const StorageTab: React.FC = () => {
|
|||||||
setTableData(null);
|
setTableData(null);
|
||||||
setShowResetConfirm(false);
|
setShowResetConfirm(false);
|
||||||
setToast({
|
setToast({
|
||||||
message: "Database Reset Complete: The database has been restored to its default state with empty tables (agents, agent_runs, app_settings).",
|
message: t('storageTab.resetSuccess'),
|
||||||
type: "success",
|
type: "success",
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to reset database:", err);
|
console.error("Failed to reset database:", err);
|
||||||
setError("Failed to reset database");
|
setError(t('storageTab.resetDatabaseFailed'));
|
||||||
setToast({
|
setToast({
|
||||||
message: "Reset Failed: Failed to reset the database. Please try again.",
|
message: t('storageTab.resetFailed'),
|
||||||
type: "error",
|
type: "error",
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
@@ -337,7 +337,7 @@ export const StorageTab: React.FC = () => {
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Database className="h-4 w-4 text-primary" />
|
<Database className="h-4 w-4 text-primary" />
|
||||||
<h3 className="text-sm font-semibold">Database Storage</h3>
|
<h3 className="text-sm font-semibold">{t('storageTab.title')}</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Button
|
<Button
|
||||||
@@ -347,7 +347,7 @@ export const StorageTab: React.FC = () => {
|
|||||||
className="gap-2 h-8 text-xs"
|
className="gap-2 h-8 text-xs"
|
||||||
>
|
>
|
||||||
<Terminal className="h-3 w-3" />
|
<Terminal className="h-3 w-3" />
|
||||||
SQL Query
|
{t('storageTab.sqlQuery')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
@@ -356,7 +356,7 @@ export const StorageTab: React.FC = () => {
|
|||||||
className="gap-2 h-8 text-xs"
|
className="gap-2 h-8 text-xs"
|
||||||
>
|
>
|
||||||
<RefreshCw className="h-3 w-3" />
|
<RefreshCw className="h-3 w-3" />
|
||||||
Reset DB
|
{t('storageTab.resetDbShort')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -365,7 +365,7 @@ export const StorageTab: React.FC = () => {
|
|||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Select value={selectedTable} onValueChange={setSelectedTable}>
|
<Select value={selectedTable} onValueChange={setSelectedTable}>
|
||||||
<SelectTrigger className="w-[200px] h-8 text-xs">
|
<SelectTrigger className="w-[200px] h-8 text-xs">
|
||||||
<SelectValue placeholder="Select a table">
|
<SelectValue placeholder={t('storageTab.selectTable')}>
|
||||||
{selectedTable && (
|
{selectedTable && (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Table className="h-3 w-3" />
|
<Table className="h-3 w-3" />
|
||||||
@@ -380,7 +380,7 @@ export const StorageTab: React.FC = () => {
|
|||||||
<div className="flex items-center justify-between w-full">
|
<div className="flex items-center justify-between w-full">
|
||||||
<span>{table.name}</span>
|
<span>{table.name}</span>
|
||||||
<span className="text-[10px] text-muted-foreground ml-2">
|
<span className="text-[10px] text-muted-foreground ml-2">
|
||||||
{table.row_count} rows
|
{table.row_count} {t('storageTab.rows')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
@@ -391,7 +391,7 @@ export const StorageTab: React.FC = () => {
|
|||||||
<div className="flex-1 relative">
|
<div className="flex-1 relative">
|
||||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-3 w-3 text-muted-foreground" />
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-3 w-3 text-muted-foreground" />
|
||||||
<Input
|
<Input
|
||||||
placeholder="Search in table..."
|
placeholder={t('storageTab.searchInTable')}
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={(e) => handleSearch(e.target.value)}
|
onChange={(e) => handleSearch(e.target.value)}
|
||||||
className="pl-8 h-8 text-xs"
|
className="pl-8 h-8 text-xs"
|
||||||
@@ -406,7 +406,7 @@ export const StorageTab: React.FC = () => {
|
|||||||
className="gap-2 h-8 text-xs"
|
className="gap-2 h-8 text-xs"
|
||||||
>
|
>
|
||||||
<Plus className="h-3 w-3" />
|
<Plus className="h-3 w-3" />
|
||||||
New Row
|
{t('storageTab.newRow')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -437,7 +437,7 @@ export const StorageTab: React.FC = () => {
|
|||||||
</th>
|
</th>
|
||||||
))}
|
))}
|
||||||
<th className="px-3 py-2 text-right text-xs font-medium text-muted-foreground">
|
<th className="px-3 py-2 text-right text-xs font-medium text-muted-foreground">
|
||||||
Actions
|
{t('storageTab.actions')}
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -520,9 +520,11 @@ export const StorageTab: React.FC = () => {
|
|||||||
{tableData.total_pages > 1 && (
|
{tableData.total_pages > 1 && (
|
||||||
<div className="flex items-center justify-between p-3 border-t">
|
<div className="flex items-center justify-between p-3 border-t">
|
||||||
<div className="text-xs text-muted-foreground">
|
<div className="text-xs text-muted-foreground">
|
||||||
Showing {(currentPage - 1) * pageSize + 1} to{" "}
|
{t('storageTab.pagination.showing', {
|
||||||
{Math.min(currentPage * pageSize, tableData.total_rows)} of{" "}
|
from: (currentPage - 1) * pageSize + 1,
|
||||||
{tableData.total_rows} rows
|
to: Math.min(currentPage * pageSize, tableData.total_rows),
|
||||||
|
total: tableData.total_rows
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Button
|
<Button
|
||||||
@@ -533,10 +535,10 @@ export const StorageTab: React.FC = () => {
|
|||||||
className="h-7 text-xs"
|
className="h-7 text-xs"
|
||||||
>
|
>
|
||||||
<ChevronLeft className="h-3 w-3" />
|
<ChevronLeft className="h-3 w-3" />
|
||||||
Previous
|
{t('app.previous')}
|
||||||
</Button>
|
</Button>
|
||||||
<div className="text-xs">
|
<div className="text-xs">
|
||||||
Page {currentPage} of {tableData.total_pages}
|
{t('storageTab.pagination.pageOf', { page: currentPage, total: tableData.total_pages })}
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@@ -545,7 +547,7 @@ export const StorageTab: React.FC = () => {
|
|||||||
disabled={currentPage === tableData.total_pages}
|
disabled={currentPage === tableData.total_pages}
|
||||||
className="h-7 text-xs"
|
className="h-7 text-xs"
|
||||||
>
|
>
|
||||||
Next
|
{t('app.next')}
|
||||||
<ChevronRight className="h-3 w-3" />
|
<ChevronRight className="h-3 w-3" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -575,9 +577,9 @@ export const StorageTab: React.FC = () => {
|
|||||||
<Dialog open={!!editingRow} onOpenChange={() => setEditingRow(null)}>
|
<Dialog open={!!editingRow} onOpenChange={() => setEditingRow(null)}>
|
||||||
<DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
|
<DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Edit Row</DialogTitle>
|
<DialogTitle>{t('storageTab.editRow')}</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
Update the values for this row in the {selectedTable} table.
|
{t('storageTab.editRowDesc', { table: selectedTable })}
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
{editingRow && tableData && (
|
{editingRow && tableData && (
|
||||||
@@ -588,7 +590,7 @@ export const StorageTab: React.FC = () => {
|
|||||||
{column.name}
|
{column.name}
|
||||||
{column.pk && (
|
{column.pk && (
|
||||||
<span className="text-xs text-muted-foreground ml-2">
|
<span className="text-xs text-muted-foreground ml-2">
|
||||||
(Primary Key)
|
({t('storageTab.primaryKey')})
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</Label>
|
</Label>
|
||||||
@@ -622,9 +624,9 @@ export const StorageTab: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
Type: {column.type_name}
|
{t('storageTab.type')}: {column.type_name}
|
||||||
{column.notnull && ", NOT NULL"}
|
{column.notnull && `, ${t('storageTab.notNull')}`}
|
||||||
{column.dflt_value && `, Default: ${column.dflt_value}`}
|
{column.dflt_value && `, ${t('storageTab.default')}: ${column.dflt_value}`}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -632,7 +634,7 @@ export const StorageTab: React.FC = () => {
|
|||||||
)}
|
)}
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button variant="outline" onClick={() => setEditingRow(null)}>
|
<Button variant="outline" onClick={() => setEditingRow(null)}>
|
||||||
Cancel
|
{t('app.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleUpdateRow(editingRow!)}
|
onClick={() => handleUpdateRow(editingRow!)}
|
||||||
@@ -641,7 +643,7 @@ export const StorageTab: React.FC = () => {
|
|||||||
{loading ? (
|
{loading ? (
|
||||||
<Loader2 className="h-4 w-4 animate-spin" />
|
<Loader2 className="h-4 w-4 animate-spin" />
|
||||||
) : (
|
) : (
|
||||||
"Update"
|
t('app.update')
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
@@ -652,9 +654,9 @@ export const StorageTab: React.FC = () => {
|
|||||||
<Dialog open={!!newRow} onOpenChange={() => setNewRow(null)}>
|
<Dialog open={!!newRow} onOpenChange={() => setNewRow(null)}>
|
||||||
<DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
|
<DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>New Row</DialogTitle>
|
<DialogTitle>{t('storageTab.newRow')}</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
Add a new row to the {selectedTable} table.
|
{t('storageTab.newRowDesc', { table: selectedTable })}
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
{newRow && tableData && (
|
{newRow && tableData && (
|
||||||
@@ -665,7 +667,7 @@ export const StorageTab: React.FC = () => {
|
|||||||
{column.name}
|
{column.name}
|
||||||
{column.notnull && (
|
{column.notnull && (
|
||||||
<span className="text-xs text-destructive ml-2">
|
<span className="text-xs text-destructive ml-2">
|
||||||
(Required)
|
({t('validation.required')})
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</Label>
|
</Label>
|
||||||
@@ -697,8 +699,8 @@ export const StorageTab: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
Type: {column.type_name}
|
{t('storageTab.type')}: {column.type_name}
|
||||||
{column.dflt_value && `, Default: ${column.dflt_value}`}
|
{column.dflt_value && `, ${t('storageTab.default')}: ${column.dflt_value}`}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -706,7 +708,7 @@ export const StorageTab: React.FC = () => {
|
|||||||
)}
|
)}
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button variant="outline" onClick={() => setNewRow(null)}>
|
<Button variant="outline" onClick={() => setNewRow(null)}>
|
||||||
Cancel
|
{t('app.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleInsertRow(newRow!)}
|
onClick={() => handleInsertRow(newRow!)}
|
||||||
@@ -715,7 +717,7 @@ export const StorageTab: React.FC = () => {
|
|||||||
{loading ? (
|
{loading ? (
|
||||||
<Loader2 className="h-4 w-4 animate-spin" />
|
<Loader2 className="h-4 w-4 animate-spin" />
|
||||||
) : (
|
) : (
|
||||||
"Insert"
|
t('storageTab.insert')
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
@@ -726,10 +728,9 @@ export const StorageTab: React.FC = () => {
|
|||||||
<Dialog open={!!deletingRow} onOpenChange={() => setDeletingRow(null)}>
|
<Dialog open={!!deletingRow} onOpenChange={() => setDeletingRow(null)}>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Delete Row</DialogTitle>
|
<DialogTitle>{t('storageTab.deleteRow')}</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
Are you sure you want to delete this row? This action cannot be
|
{t('storageTab.deleteRowConfirm')}
|
||||||
undone.
|
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
{deletingRow && (
|
{deletingRow && (
|
||||||
@@ -752,7 +753,7 @@ export const StorageTab: React.FC = () => {
|
|||||||
)}
|
)}
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button variant="outline" onClick={() => setDeletingRow(null)}>
|
<Button variant="outline" onClick={() => setDeletingRow(null)}>
|
||||||
Cancel
|
{t('app.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
@@ -762,7 +763,7 @@ export const StorageTab: React.FC = () => {
|
|||||||
{loading ? (
|
{loading ? (
|
||||||
<Loader2 className="h-4 w-4 animate-spin" />
|
<Loader2 className="h-4 w-4 animate-spin" />
|
||||||
) : (
|
) : (
|
||||||
"Delete"
|
t('app.delete')
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
@@ -773,18 +774,15 @@ export const StorageTab: React.FC = () => {
|
|||||||
<Dialog open={showResetConfirm} onOpenChange={setShowResetConfirm}>
|
<Dialog open={showResetConfirm} onOpenChange={setShowResetConfirm}>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Reset Database</DialogTitle>
|
<DialogTitle>{t('storageTab.resetDatabaseTitle')}</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
This will delete all data and recreate the database with its default structure
|
{t('storageTab.resetDatabaseDesc')}
|
||||||
(empty tables for agents, agent_runs, and app_settings). The database will be
|
|
||||||
restored to the same state as when you first installed the app. This action
|
|
||||||
cannot be undone.
|
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="flex items-center gap-3 p-4 rounded-md bg-destructive/10 text-destructive">
|
<div className="flex items-center gap-3 p-4 rounded-md bg-destructive/10 text-destructive">
|
||||||
<AlertTriangle className="h-5 w-5" />
|
<AlertTriangle className="h-5 w-5" />
|
||||||
<span className="text-sm font-medium">
|
<span className="text-sm font-medium">
|
||||||
All your agents, runs, and settings will be permanently deleted!
|
{t('storageTab.resetWarning')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
@@ -792,7 +790,7 @@ export const StorageTab: React.FC = () => {
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => setShowResetConfirm(false)}
|
onClick={() => setShowResetConfirm(false)}
|
||||||
>
|
>
|
||||||
Cancel
|
{t('app.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
@@ -802,7 +800,7 @@ export const StorageTab: React.FC = () => {
|
|||||||
{loading ? (
|
{loading ? (
|
||||||
<Loader2 className="h-4 w-4 animate-spin" />
|
<Loader2 className="h-4 w-4 animate-spin" />
|
||||||
) : (
|
) : (
|
||||||
"Reset Database"
|
t('storageTab.resetDatabaseTitle')
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
@@ -813,19 +811,19 @@ export const StorageTab: React.FC = () => {
|
|||||||
<Dialog open={showSqlEditor} onOpenChange={setShowSqlEditor}>
|
<Dialog open={showSqlEditor} onOpenChange={setShowSqlEditor}>
|
||||||
<DialogContent className="max-w-4xl max-h-[80vh]">
|
<DialogContent className="max-w-4xl max-h-[80vh]">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>SQL Query Editor</DialogTitle>
|
<DialogTitle>{t('storageTab.sqlEditorTitle')}</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
Execute raw SQL queries on the database. Use with caution.
|
{t('storageTab.sqlEditorDesc')}
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="sql-query">SQL Query</Label>
|
<Label htmlFor="sql-query">{t('storageTab.sqlQuery')}</Label>
|
||||||
<Textarea
|
<Textarea
|
||||||
id="sql-query"
|
id="sql-query"
|
||||||
value={sqlQuery}
|
value={sqlQuery}
|
||||||
onChange={(e) => setSqlQuery(e.target.value)}
|
onChange={(e) => setSqlQuery(e.target.value)}
|
||||||
placeholder="SELECT * FROM agents LIMIT 10;"
|
placeholder={t('storageTab.sqlQueryPlaceholder')}
|
||||||
className="font-mono text-sm h-32"
|
className="font-mono text-sm h-32"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -845,11 +843,10 @@ export const StorageTab: React.FC = () => {
|
|||||||
<div className="p-3 rounded-md bg-green-500/10 text-green-600 dark:text-green-400 text-sm">
|
<div className="p-3 rounded-md bg-green-500/10 text-green-600 dark:text-green-400 text-sm">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Check className="h-4 w-4" />
|
<Check className="h-4 w-4" />
|
||||||
Query executed successfully. {sqlResult.rows_affected} rows
|
{t('storageTab.queryExecuted')} {sqlResult.rows_affected} {t('storageTab.rowsAffected')}
|
||||||
affected.
|
|
||||||
{sqlResult.last_insert_rowid && (
|
{sqlResult.last_insert_rowid && (
|
||||||
<span>
|
<span>
|
||||||
Last insert ID: {sqlResult.last_insert_rowid}
|
{t('storageTab.lastInsertId')}: {sqlResult.last_insert_rowid}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -927,7 +924,7 @@ export const StorageTab: React.FC = () => {
|
|||||||
setSqlError(null);
|
setSqlError(null);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Close
|
{t('app.close')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleExecuteSql}
|
onClick={handleExecuteSql}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useTabState } from '@/hooks/useTabState';
|
|||||||
import { Tab, useTabContext } from '@/contexts/TabContext';
|
import { Tab, useTabContext } from '@/contexts/TabContext';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { useTrackEvent } from '@/hooks';
|
import { useTrackEvent } from '@/hooks';
|
||||||
|
import { useTranslation as useAppTranslation } from '@/hooks/useTranslation';
|
||||||
|
|
||||||
interface TabItemProps {
|
interface TabItemProps {
|
||||||
tab: Tab;
|
tab: Tab;
|
||||||
@@ -16,6 +17,7 @@ interface TabItemProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const TabItem: React.FC<TabItemProps> = ({ tab, isActive, onClose, onClick, isDragging = false, setDraggedTabId }) => {
|
const TabItem: React.FC<TabItemProps> = ({ tab, isActive, onClose, onClick, isDragging = false, setDraggedTabId }) => {
|
||||||
|
const { t } = useAppTranslation();
|
||||||
const [isHovered, setIsHovered] = useState(false);
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
|
|
||||||
const getIcon = () => {
|
const getIcon = () => {
|
||||||
@@ -103,7 +105,7 @@ const TabItem: React.FC<TabItemProps> = ({ tab, isActive, onClose, onClick, isDr
|
|||||||
{tab.hasUnsavedChanges && !statusIcon && (
|
{tab.hasUnsavedChanges && !statusIcon && (
|
||||||
<span
|
<span
|
||||||
className="w-1.5 h-1.5 bg-primary rounded-full"
|
className="w-1.5 h-1.5 bg-primary rounded-full"
|
||||||
title="Unsaved changes"
|
title={t('app.unsavedChanges')}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -120,7 +122,7 @@ const TabItem: React.FC<TabItemProps> = ({ tab, isActive, onClose, onClick, isDr
|
|||||||
"focus:outline-none focus:ring-1 focus:ring-destructive/50",
|
"focus:outline-none focus:ring-1 focus:ring-destructive/50",
|
||||||
(isHovered || isActive) ? "opacity-100" : "opacity-0"
|
(isHovered || isActive) ? "opacity-100" : "opacity-0"
|
||||||
)}
|
)}
|
||||||
title={`Close ${tab.title}`}
|
title={`${t('app.close')} ${tab.title}`}
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
>
|
>
|
||||||
<X className="w-3 h-3" />
|
<X className="w-3 h-3" />
|
||||||
@@ -135,6 +137,7 @@ interface TabManagerProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const TabManager: React.FC<TabManagerProps> = ({ className }) => {
|
export const TabManager: React.FC<TabManagerProps> = ({ className }) => {
|
||||||
|
const { t } = useAppTranslation();
|
||||||
const {
|
const {
|
||||||
tabs,
|
tabs,
|
||||||
activeTabId,
|
activeTabId,
|
||||||
@@ -342,7 +345,7 @@ export const TabManager: React.FC<TabManagerProps> = ({ className }) => {
|
|||||||
"transition-colors duration-200 flex items-center justify-center",
|
"transition-colors duration-200 flex items-center justify-center",
|
||||||
"bg-background/98 backdrop-blur-xl backdrop-saturate-[1.8] shadow-sm border border-border/60"
|
"bg-background/98 backdrop-blur-xl backdrop-saturate-[1.8] shadow-sm border border-border/60"
|
||||||
)}
|
)}
|
||||||
title="Scroll tabs left"
|
title={t('tabs.scrollLeft')}
|
||||||
>
|
>
|
||||||
<svg className="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
<svg className="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||||
<path d="M15 18l-6-6 6-6" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" />
|
<path d="M15 18l-6-6 6-6" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" />
|
||||||
@@ -396,7 +399,7 @@ export const TabManager: React.FC<TabManagerProps> = ({ className }) => {
|
|||||||
"transition-colors duration-200 flex items-center justify-center",
|
"transition-colors duration-200 flex items-center justify-center",
|
||||||
"bg-background/98 backdrop-blur-xl backdrop-saturate-[1.8] shadow-sm border border-border/60"
|
"bg-background/98 backdrop-blur-xl backdrop-saturate-[1.8] shadow-sm border border-border/60"
|
||||||
)}
|
)}
|
||||||
title="Scroll tabs right"
|
title={t('tabs.scrollRight')}
|
||||||
>
|
>
|
||||||
<svg className="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
<svg className="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||||
<path d="M9 18l6-6-6-6" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" />
|
<path d="M9 18l6-6-6-6" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" />
|
||||||
@@ -416,7 +419,7 @@ export const TabManager: React.FC<TabManagerProps> = ({ className }) => {
|
|||||||
? "hover:bg-muted/80 hover:border-border text-muted-foreground hover:text-foreground hover:shadow-sm"
|
? "hover:bg-muted/80 hover:border-border text-muted-foreground hover:text-foreground hover:shadow-sm"
|
||||||
: "opacity-50 cursor-not-allowed bg-muted/30"
|
: "opacity-50 cursor-not-allowed bg-muted/30"
|
||||||
)}
|
)}
|
||||||
title={canAddTab() ? "Browse projects (Ctrl+T)" : `Maximum tabs reached (${tabs.length}/20)`}
|
title={canAddTab() ? t('tabs.browseProjectsShortcut') : t('tabs.maximumTabsReached', { count: tabs.length })}
|
||||||
>
|
>
|
||||||
<Plus className="w-3.5 h-3.5" />
|
<Plus className="w-3.5 h-3.5" />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -294,7 +294,7 @@ export const TimelineNavigator: React.FC<TimelineNavigatorProps> = ({
|
|||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="flex items-center gap-2 mb-1">
|
<div className="flex items-center gap-2 mb-1">
|
||||||
{isCurrent && (
|
{isCurrent && (
|
||||||
<Badge variant="default" className="text-xs">Current</Badge>
|
<Badge variant="default" className="text-xs">{t('checkpoint.current') || 'Current'}</Badge>
|
||||||
)}
|
)}
|
||||||
<span className="text-xs font-mono text-muted-foreground">
|
<span className="text-xs font-mono text-muted-foreground">
|
||||||
{node.checkpoint.id.slice(0, 8)}
|
{node.checkpoint.id.slice(0, 8)}
|
||||||
@@ -309,7 +309,7 @@ export const TimelineNavigator: React.FC<TimelineNavigatorProps> = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<p className="text-xs text-muted-foreground line-clamp-2">
|
<p className="text-xs text-muted-foreground line-clamp-2">
|
||||||
{node.checkpoint.metadata.userPrompt || "No prompt"}
|
{node.checkpoint.metadata.userPrompt || t('checkpoint.noPrompt') || 'No prompt'}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex items-center gap-3 mt-2 text-xs text-muted-foreground">
|
<div className="flex items-center gap-3 mt-2 text-xs text-muted-foreground">
|
||||||
@@ -341,7 +341,7 @@ export const TimelineNavigator: React.FC<TimelineNavigatorProps> = ({
|
|||||||
<RotateCcw className="h-3 w-3" />
|
<RotateCcw className="h-3 w-3" />
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>Restore to this checkpoint</TooltipContent>
|
<TooltipContent>{t('checkpoint.restoreToThis') || 'Restore to this checkpoint'}</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
|
||||||
@@ -360,7 +360,7 @@ export const TimelineNavigator: React.FC<TimelineNavigatorProps> = ({
|
|||||||
<GitFork className="h-3 w-3" />
|
<GitFork className="h-3 w-3" />
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>Fork from this checkpoint</TooltipContent>
|
<TooltipContent>{t('checkpoint.forkFromThis') || 'Fork from this checkpoint'}</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
|
||||||
@@ -379,7 +379,7 @@ export const TimelineNavigator: React.FC<TimelineNavigatorProps> = ({
|
|||||||
<Diff className="h-3 w-3" />
|
<Diff className="h-3 w-3" />
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>Compare with another checkpoint</TooltipContent>
|
<TooltipContent>{t('checkpoint.compareWithAnother') || 'Compare with another checkpoint'}</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
</div>
|
</div>
|
||||||
@@ -442,7 +442,7 @@ export const TimelineNavigator: React.FC<TimelineNavigatorProps> = ({
|
|||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
>
|
>
|
||||||
<Save className="h-3 w-3 mr-1" />
|
<Save className="h-3 w-3 mr-1" />
|
||||||
Checkpoint
|
{t('checkpoint.createCheckpoint')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -469,18 +469,18 @@ export const TimelineNavigator: React.FC<TimelineNavigatorProps> = ({
|
|||||||
<Dialog open={showCreateDialog} onOpenChange={setShowCreateDialog}>
|
<Dialog open={showCreateDialog} onOpenChange={setShowCreateDialog}>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Create Checkpoint</DialogTitle>
|
<DialogTitle>{t('checkpoint.createCheckpoint')}</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
Save the current state of your session with an optional description.
|
{t('checkpoint.createCheckpointDesc') || 'Save the current state of your session with an optional description.'}
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<div className="space-y-4 py-4">
|
<div className="space-y-4 py-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="description">Description (optional)</Label>
|
<Label htmlFor="description">{t('checkpoint.descriptionOptional') || 'Description (optional)'}</Label>
|
||||||
<Input
|
<Input
|
||||||
id="description"
|
id="description"
|
||||||
placeholder="e.g., Before major refactoring"
|
placeholder={t('checkpoint.descriptionPlaceholder') || 'e.g., Before major refactoring'}
|
||||||
value={checkpointDescription}
|
value={checkpointDescription}
|
||||||
onChange={(e) => setCheckpointDescription(e.target.value)}
|
onChange={(e) => setCheckpointDescription(e.target.value)}
|
||||||
onKeyPress={(e) => {
|
onKeyPress={(e) => {
|
||||||
@@ -498,13 +498,13 @@ export const TimelineNavigator: React.FC<TimelineNavigatorProps> = ({
|
|||||||
onClick={() => setShowCreateDialog(false)}
|
onClick={() => setShowCreateDialog(false)}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
>
|
>
|
||||||
Cancel
|
{t('app.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleCreateCheckpoint}
|
onClick={handleCreateCheckpoint}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
>
|
>
|
||||||
Create Checkpoint
|
{t('checkpoint.createCheckpoint')}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
@@ -514,7 +514,7 @@ export const TimelineNavigator: React.FC<TimelineNavigatorProps> = ({
|
|||||||
<Dialog open={showDiffDialog} onOpenChange={setShowDiffDialog}>
|
<Dialog open={showDiffDialog} onOpenChange={setShowDiffDialog}>
|
||||||
<DialogContent className="max-w-3xl">
|
<DialogContent className="max-w-3xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Checkpoint Comparison</DialogTitle>
|
<DialogTitle>{t('checkpoint.checkpointComparison') || 'Checkpoint Comparison'}</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
Changes between "{selectedCheckpoint?.description || selectedCheckpoint?.id.slice(0, 8)}"
|
Changes between "{selectedCheckpoint?.description || selectedCheckpoint?.id.slice(0, 8)}"
|
||||||
and "{compareCheckpoint?.description || compareCheckpoint?.id.slice(0, 8)}"
|
and "{compareCheckpoint?.description || compareCheckpoint?.id.slice(0, 8)}"
|
||||||
@@ -527,19 +527,19 @@ export const TimelineNavigator: React.FC<TimelineNavigatorProps> = ({
|
|||||||
<div className="grid grid-cols-3 gap-4">
|
<div className="grid grid-cols-3 gap-4">
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent className="p-3">
|
<CardContent className="p-3">
|
||||||
<div className="text-xs text-muted-foreground">Modified Files</div>
|
<div className="text-xs text-muted-foreground">{t('checkpoint.modifiedFiles') || 'Modified Files'}</div>
|
||||||
<div className="text-2xl font-bold">{diff.modifiedFiles.length}</div>
|
<div className="text-2xl font-bold">{diff.modifiedFiles.length}</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent className="p-3">
|
<CardContent className="p-3">
|
||||||
<div className="text-xs text-muted-foreground">Added Files</div>
|
<div className="text-xs text-muted-foreground">{t('checkpoint.addedFiles') || 'Added Files'}</div>
|
||||||
<div className="text-2xl font-bold text-green-600">{diff.addedFiles.length}</div>
|
<div className="text-2xl font-bold text-green-600">{diff.addedFiles.length}</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent className="p-3">
|
<CardContent className="p-3">
|
||||||
<div className="text-xs text-muted-foreground">Deleted Files</div>
|
<div className="text-xs text-muted-foreground">{t('checkpoint.deletedFiles') || 'Deleted Files'}</div>
|
||||||
<div className="text-2xl font-bold text-red-600">{diff.deletedFiles.length}</div>
|
<div className="text-2xl font-bold text-red-600">{diff.deletedFiles.length}</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -548,14 +548,14 @@ export const TimelineNavigator: React.FC<TimelineNavigatorProps> = ({
|
|||||||
{/* Token delta */}
|
{/* Token delta */}
|
||||||
<div className="flex items-center justify-center">
|
<div className="flex items-center justify-center">
|
||||||
<Badge variant={diff.tokenDelta > 0 ? "default" : "secondary"}>
|
<Badge variant={diff.tokenDelta > 0 ? "default" : "secondary"}>
|
||||||
{diff.tokenDelta > 0 ? "+" : ""}{diff.tokenDelta.toLocaleString()} tokens
|
{diff.tokenDelta > 0 ? "+" : ""}{diff.tokenDelta.toLocaleString()} {t('usage.tokens')}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* File lists */}
|
{/* File lists */}
|
||||||
{diff.modifiedFiles.length > 0 && (
|
{diff.modifiedFiles.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<h4 className="text-sm font-medium mb-2">Modified Files</h4>
|
<h4 className="text-sm font-medium mb-2">{t('checkpoint.modifiedFiles') || 'Modified Files'}</h4>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
{diff.modifiedFiles.map((file) => (
|
{diff.modifiedFiles.map((file) => (
|
||||||
<div key={file.path} className="flex items-center justify-between text-xs">
|
<div key={file.path} className="flex items-center justify-between text-xs">
|
||||||
@@ -572,7 +572,7 @@ export const TimelineNavigator: React.FC<TimelineNavigatorProps> = ({
|
|||||||
|
|
||||||
{diff.addedFiles.length > 0 && (
|
{diff.addedFiles.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<h4 className="text-sm font-medium mb-2">Added Files</h4>
|
<h4 className="text-sm font-medium mb-2">{t('checkpoint.addedFiles') || 'Added Files'}</h4>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
{diff.addedFiles.map((file) => (
|
{diff.addedFiles.map((file) => (
|
||||||
<div key={file} className="text-xs font-mono text-green-600">
|
<div key={file} className="text-xs font-mono text-green-600">
|
||||||
@@ -585,7 +585,7 @@ export const TimelineNavigator: React.FC<TimelineNavigatorProps> = ({
|
|||||||
|
|
||||||
{diff.deletedFiles.length > 0 && (
|
{diff.deletedFiles.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<h4 className="text-sm font-medium mb-2">Deleted Files</h4>
|
<h4 className="text-sm font-medium mb-2">{t('checkpoint.deletedFiles') || 'Deleted Files'}</h4>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
{diff.deletedFiles.map((file) => (
|
{diff.deletedFiles.map((file) => (
|
||||||
<div key={file} className="text-xs font-mono text-red-600">
|
<div key={file} className="text-xs font-mono text-red-600">
|
||||||
@@ -607,7 +607,7 @@ export const TimelineNavigator: React.FC<TimelineNavigatorProps> = ({
|
|||||||
setCompareCheckpoint(null);
|
setCompareCheckpoint(null);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Close
|
{t('app.close')}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|||||||
@@ -635,6 +635,7 @@ export const BashWidget: React.FC<{
|
|||||||
description?: string;
|
description?: string;
|
||||||
result?: any;
|
result?: any;
|
||||||
}> = ({ command, description, result }) => {
|
}> = ({ command, description, result }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
// Extract result content if available
|
// Extract result content if available
|
||||||
let resultContent = '';
|
let resultContent = '';
|
||||||
let isError = false;
|
let isError = false;
|
||||||
@@ -660,7 +661,7 @@ export const BashWidget: React.FC<{
|
|||||||
<div className="rounded-lg border bg-zinc-950 overflow-hidden">
|
<div className="rounded-lg border bg-zinc-950 overflow-hidden">
|
||||||
<div className="px-4 py-2 bg-zinc-900/50 flex items-center gap-2 border-b">
|
<div className="px-4 py-2 bg-zinc-900/50 flex items-center gap-2 border-b">
|
||||||
<Terminal className="h-3.5 w-3.5 text-green-500" />
|
<Terminal className="h-3.5 w-3.5 text-green-500" />
|
||||||
<span className="text-xs font-mono text-muted-foreground">Terminal</span>
|
<span className="text-xs font-mono text-muted-foreground">{t('widgets.terminal.title')}</span>
|
||||||
{description && (
|
{description && (
|
||||||
<>
|
<>
|
||||||
<ChevronRight className="h-3 w-3 text-muted-foreground" />
|
<ChevronRight className="h-3 w-3 text-muted-foreground" />
|
||||||
@@ -671,7 +672,7 @@ export const BashWidget: React.FC<{
|
|||||||
{!result && (
|
{!result && (
|
||||||
<div className="ml-auto flex items-center gap-1 text-xs text-muted-foreground">
|
<div className="ml-auto flex items-center gap-1 text-xs text-muted-foreground">
|
||||||
<div className="h-2 w-2 bg-green-500 rounded-full animate-pulse" />
|
<div className="h-2 w-2 bg-green-500 rounded-full animate-pulse" />
|
||||||
<span>Running...</span>
|
<span>{t('widgets.common.running')}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -688,7 +689,7 @@ export const BashWidget: React.FC<{
|
|||||||
? "border-red-500/20 bg-red-500/5 text-red-400"
|
? "border-red-500/20 bg-red-500/5 text-red-400"
|
||||||
: "border-green-500/20 bg-green-500/5 text-green-300"
|
: "border-green-500/20 bg-green-500/5 text-green-300"
|
||||||
)}>
|
)}>
|
||||||
{resultContent || (isError ? "Command failed" : "Command completed")}
|
{resultContent || (isError ? t('widgets.bash.commandFailed') : t('widgets.bash.commandCompleted'))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ export const Topbar: React.FC<TopbarProps> = ({
|
|||||||
// Emit event to return to home
|
// Emit event to return to home
|
||||||
window.dispatchEvent(new CustomEvent('switch-to-welcome'));
|
window.dispatchEvent(new CustomEvent('switch-to-welcome'));
|
||||||
}}
|
}}
|
||||||
title="Return to Home"
|
title={t('app.returnHome')}
|
||||||
>
|
>
|
||||||
<div className="flex items-center space-x-2 text-xs">
|
<div className="flex items-center space-x-2 text-xs">
|
||||||
<Circle
|
<Circle
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ const WebviewPreviewComponent: React.FC<WebviewPreviewProps> = ({
|
|||||||
className={cn("flex flex-col h-full bg-background border-l", className)}
|
className={cn("flex flex-col h-full bg-background border-l", className)}
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
role="region"
|
role="region"
|
||||||
aria-label="Web preview"
|
aria-label={t('webview.preview')}
|
||||||
>
|
>
|
||||||
{/* Browser Top Bar */}
|
{/* Browser Top Bar */}
|
||||||
<div className="border-b bg-muted/30 flex-shrink-0">
|
<div className="border-b bg-muted/30 flex-shrink-0">
|
||||||
@@ -322,7 +322,7 @@ const WebviewPreviewComponent: React.FC<WebviewPreviewProps> = ({
|
|||||||
ref={iframeRef}
|
ref={iframeRef}
|
||||||
src={currentUrl}
|
src={currentUrl}
|
||||||
className="absolute inset-0 w-full h-full border-0"
|
className="absolute inset-0 w-full h-full border-0"
|
||||||
title="Preview"
|
title={t('webview.preview')}
|
||||||
sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox"
|
sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox"
|
||||||
onLoad={() => setIsLoading(false)}
|
onLoad={() => setIsLoading(false)}
|
||||||
onError={() => {
|
onError={() => {
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ export const SessionHeader: React.FC<SessionHeaderProps> = React.memo(({
|
|||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2"
|
||||||
>
|
>
|
||||||
<FolderOpen className="h-4 w-4" />
|
<FolderOpen className="h-4 w-4" />
|
||||||
Select Project
|
{t('webview.selectProjectDirectory')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -104,7 +104,7 @@ export const SessionHeader: React.FC<SessionHeaderProps> = React.memo(({
|
|||||||
</Badge>
|
</Badge>
|
||||||
{totalTokens > 0 && (
|
{totalTokens > 0 && (
|
||||||
<Badge variant="secondary" className="text-xs">
|
<Badge variant="secondary" className="text-xs">
|
||||||
{totalTokens.toLocaleString()} tokens
|
{totalTokens.toLocaleString()} {t('usage.tokens')}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -165,13 +165,13 @@ export const SessionHeader: React.FC<SessionHeaderProps> = React.memo(({
|
|||||||
{onProjectSettings && projectPath && (
|
{onProjectSettings && projectPath && (
|
||||||
<DropdownMenuItem onClick={onProjectSettings}>
|
<DropdownMenuItem onClick={onProjectSettings}>
|
||||||
<Settings className="h-4 w-4 mr-2" />
|
<Settings className="h-4 w-4 mr-2" />
|
||||||
Project Settings
|
{t('agents.projectSettings')}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
{onSlashCommandsSettings && projectPath && (
|
{onSlashCommandsSettings && projectPath && (
|
||||||
<DropdownMenuItem onClick={onSlashCommandsSettings}>
|
<DropdownMenuItem onClick={onSlashCommandsSettings}>
|
||||||
<Command className="h-4 w-4 mr-2" />
|
<Command className="h-4 w-4 mr-2" />
|
||||||
Slash Commands
|
{t('slashCommands.slashCommands')}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Terminal, ChevronRight } from "lucide-react";
|
import { Terminal, ChevronRight } from "lucide-react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
import { useTranslation } from "@/hooks/useTranslation";
|
||||||
|
|
||||||
interface BashWidgetProps {
|
interface BashWidgetProps {
|
||||||
command: string;
|
command: string;
|
||||||
@@ -9,6 +10,7 @@ interface BashWidgetProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const BashWidget: React.FC<BashWidgetProps> = ({ command, description, result }) => {
|
export const BashWidget: React.FC<BashWidgetProps> = ({ command, description, result }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
// Extract result content if available
|
// Extract result content if available
|
||||||
let resultContent = '';
|
let resultContent = '';
|
||||||
let isError = false;
|
let isError = false;
|
||||||
@@ -34,7 +36,7 @@ export const BashWidget: React.FC<BashWidgetProps> = ({ command, description, re
|
|||||||
<div className="rounded-lg border bg-zinc-950 overflow-hidden">
|
<div className="rounded-lg border bg-zinc-950 overflow-hidden">
|
||||||
<div className="px-4 py-2 bg-zinc-900/50 flex items-center gap-2 border-b">
|
<div className="px-4 py-2 bg-zinc-900/50 flex items-center gap-2 border-b">
|
||||||
<Terminal className="h-3.5 w-3.5 text-green-500" />
|
<Terminal className="h-3.5 w-3.5 text-green-500" />
|
||||||
<span className="text-xs font-mono text-muted-foreground">Terminal</span>
|
<span className="text-xs font-mono text-muted-foreground">{t('widgets.terminal.title')}</span>
|
||||||
{description && (
|
{description && (
|
||||||
<>
|
<>
|
||||||
<ChevronRight className="h-3 w-3 text-muted-foreground" />
|
<ChevronRight className="h-3 w-3 text-muted-foreground" />
|
||||||
@@ -45,7 +47,7 @@ export const BashWidget: React.FC<BashWidgetProps> = ({ command, description, re
|
|||||||
{!result && (
|
{!result && (
|
||||||
<div className="ml-auto flex items-center gap-1 text-xs text-muted-foreground">
|
<div className="ml-auto flex items-center gap-1 text-xs text-muted-foreground">
|
||||||
<div className="h-2 w-2 bg-green-500 rounded-full animate-pulse" />
|
<div className="h-2 w-2 bg-green-500 rounded-full animate-pulse" />
|
||||||
<span>Running...</span>
|
<span>{t('widgets.common.running')}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -62,7 +64,7 @@ export const BashWidget: React.FC<BashWidgetProps> = ({ command, description, re
|
|||||||
? "border-red-500/20 bg-red-500/5 text-red-400"
|
? "border-red-500/20 bg-red-500/5 text-red-400"
|
||||||
: "border-green-500/20 bg-green-500/5 text-green-300"
|
: "border-green-500/20 bg-green-500/5 text-green-300"
|
||||||
)}>
|
)}>
|
||||||
{resultContent || (isError ? "Command failed" : "Command completed")}
|
{resultContent || (isError ? t('widgets.bash.commandFailed') : t('widgets.bash.commandCompleted'))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
"name": "Claudia",
|
"name": "Claudia",
|
||||||
"welcome": "Welcome to Claudia",
|
"welcome": "Welcome to Claudia",
|
||||||
"tagline": "Powerful Claude Code session management tool",
|
"tagline": "Powerful Claude Code session management tool",
|
||||||
|
"returnHome": "Return to Home",
|
||||||
"loading": "Loading...",
|
"loading": "Loading...",
|
||||||
"error": "Error",
|
"error": "Error",
|
||||||
"success": "Success",
|
"success": "Success",
|
||||||
@@ -99,8 +100,22 @@
|
|||||||
"justNow": "just now",
|
"justNow": "just now",
|
||||||
"minutesAgo": "{{count}} minute{{plural}} ago",
|
"minutesAgo": "{{count}} minute{{plural}} ago",
|
||||||
"hoursAgo": "{{count}} hour{{plural}} ago",
|
"hoursAgo": "{{count}} hour{{plural}} ago",
|
||||||
"daysAgo": "{{count}} day{{plural}} ago"
|
"daysAgo": "{{count}} day{{plural}} ago",
|
||||||
|
"loadingOutput": "Loading output...",
|
||||||
|
"waitingForOutput": "Waiting for output...",
|
||||||
|
"noOutput": "No output available",
|
||||||
|
"agentRunningNoOutput": "Agent is running but no output received yet",
|
||||||
|
"failedToLoadSessionOutput": "Failed to load session output",
|
||||||
|
"outputRefreshed": "Output refreshed",
|
||||||
|
"fileABug": "File a bug",
|
||||||
|
"failedToRefreshOutput": "Failed to refresh output",
|
||||||
|
"loadingAgentRun": "Loading agent run...",
|
||||||
|
"agentExecutionCompleted": "Agent execution completed",
|
||||||
|
"agentExecutionCancelled": "Agent execution was cancelled",
|
||||||
|
"selectProjectFirst": "Please select a project directory first",
|
||||||
|
"selectDirectoryFailed": "Failed to select directory: {{message}}"
|
||||||
},
|
},
|
||||||
|
"claudeSession": {},
|
||||||
"navigation": {
|
"navigation": {
|
||||||
"projects": "CC Projects",
|
"projects": "CC Projects",
|
||||||
"agents": "Agent Management",
|
"agents": "Agent Management",
|
||||||
@@ -138,7 +153,27 @@
|
|||||||
"sessions": "Sessions",
|
"sessions": "Sessions",
|
||||||
"noSessions": "No sessions found",
|
"noSessions": "No sessions found",
|
||||||
"lastModified": "Last Modified",
|
"lastModified": "Last Modified",
|
||||||
"sessionHistory": "Session History"
|
"sessionHistory": "Session History",
|
||||||
|
"sessionCount": "{{count}} sessions"
|
||||||
|
},
|
||||||
|
"sessions": {
|
||||||
|
"firstMessage": "First message:",
|
||||||
|
"hasTodo": "Has todo"
|
||||||
|
},
|
||||||
|
"runningSessions": {
|
||||||
|
"title": "Active Claude Sessions",
|
||||||
|
"countRunning": "({{count}} running)",
|
||||||
|
"running": "Running",
|
||||||
|
"resume": "Resume",
|
||||||
|
"loadFailed": "Failed to load running sessions"
|
||||||
|
},
|
||||||
|
"widgets": {
|
||||||
|
"terminal": { "title": "Terminal" },
|
||||||
|
"common": { "running": "Running..." },
|
||||||
|
"bash": {
|
||||||
|
"commandFailed": "Command failed",
|
||||||
|
"commandCompleted": "Command completed"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"agents": {
|
"agents": {
|
||||||
"title": "Agent Management",
|
"title": "Agent Management",
|
||||||
@@ -153,6 +188,11 @@
|
|||||||
"enterTask": "Enter the task for the agent",
|
"enterTask": "Enter the task for the agent",
|
||||||
"hooks": "Hooks",
|
"hooks": "Hooks",
|
||||||
"configureHooks": "Configure Hooks",
|
"configureHooks": "Configure Hooks",
|
||||||
|
"configureHooksDesc": "Configure hooks that run before, during, and after tool executions. Changes are saved immediately.",
|
||||||
|
"projectSettings": "Project Settings",
|
||||||
|
"localSettings": "Local Settings",
|
||||||
|
"initializing": "Initializing agent...",
|
||||||
|
"exportToFile": "Export agent to .claudia.json",
|
||||||
"fullscreen": "Fullscreen",
|
"fullscreen": "Fullscreen",
|
||||||
"stop": "Stop",
|
"stop": "Stop",
|
||||||
"agentName": "Agent Name",
|
"agentName": "Agent Name",
|
||||||
@@ -317,7 +357,13 @@
|
|||||||
"condition": "Condition",
|
"condition": "Condition",
|
||||||
"matchesRegex": "Matches regex",
|
"matchesRegex": "Matches regex",
|
||||||
"message": "Message",
|
"message": "Message",
|
||||||
"enterShellCommand": "Enter shell command..."
|
"enterShellCommand": "Enter shell command...",
|
||||||
|
"projectHooks": "Project Hooks",
|
||||||
|
"localHooks": "Local Hooks"
|
||||||
|
},
|
||||||
|
"projectSettings": {
|
||||||
|
"gitignoreLocalWarning": "Local settings file is not in .gitignore",
|
||||||
|
"addToGitignore": "Add to .gitignore"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"title": "Settings",
|
"title": "Settings",
|
||||||
@@ -380,6 +426,8 @@
|
|||||||
"verboseOutputDesc": "Show full bash and command outputs",
|
"verboseOutputDesc": "Show full bash and command outputs",
|
||||||
"chatRetention": "Chat Transcript Retention (days)",
|
"chatRetention": "Chat Transcript Retention (days)",
|
||||||
"chatRetentionDesc": "How long to retain chat transcripts locally (default: 30 days)",
|
"chatRetentionDesc": "How long to retain chat transcripts locally (default: 30 days)",
|
||||||
|
"chatRetentionPermanent": "Permanent retention enabled, chat records will not be automatically deleted",
|
||||||
|
"permanentRetention": "Permanent Retention",
|
||||||
"claudeCodeInstallation": "Claude Code Installation",
|
"claudeCodeInstallation": "Claude Code Installation",
|
||||||
"choosePreferredInstallation": "Choose your preferred Claude Code installation.",
|
"choosePreferredInstallation": "Choose your preferred Claude Code installation.",
|
||||||
"loadingAvailableInstallations": "Loading available installations...",
|
"loadingAvailableInstallations": "Loading available installations...",
|
||||||
@@ -761,6 +809,18 @@
|
|||||||
"checkpointSettingsTitle": "Checkpoint Settings",
|
"checkpointSettingsTitle": "Checkpoint Settings",
|
||||||
"experimentalFeature": "Experimental Feature",
|
"experimentalFeature": "Experimental Feature",
|
||||||
"checkpointWarning": "Checkpointing may affect directory structure or cause data loss. Use with caution.",
|
"checkpointWarning": "Checkpointing may affect directory structure or cause data loss. Use with caution.",
|
||||||
|
"createCheckpointDesc": "Save the current state of your session with an optional description.",
|
||||||
|
"descriptionOptional": "Description (optional)",
|
||||||
|
"descriptionPlaceholder": "e.g., Before major refactoring",
|
||||||
|
"current": "Current",
|
||||||
|
"restoreToThis": "Restore to this checkpoint",
|
||||||
|
"forkFromThis": "Fork from this checkpoint",
|
||||||
|
"compareWithAnother": "Compare with another checkpoint",
|
||||||
|
"checkpointComparison": "Checkpoint Comparison",
|
||||||
|
"modifiedFiles": "Modified Files",
|
||||||
|
"addedFiles": "Added Files",
|
||||||
|
"deletedFiles": "Deleted Files",
|
||||||
|
"noPrompt": "No prompt",
|
||||||
"automaticCheckpoints": "Automatic Checkpoints",
|
"automaticCheckpoints": "Automatic Checkpoints",
|
||||||
"automaticCheckpointsDesc": "Automatically create checkpoints based on the selected strategy",
|
"automaticCheckpointsDesc": "Automatically create checkpoints based on the selected strategy",
|
||||||
"checkpointStrategy": "Checkpoint Strategy",
|
"checkpointStrategy": "Checkpoint Strategy",
|
||||||
@@ -967,4 +1027,60 @@
|
|||||||
"warning": {
|
"warning": {
|
||||||
"title": "Warning"
|
"title": "Warning"
|
||||||
}
|
}
|
||||||
|
,
|
||||||
|
"agentRun": {
|
||||||
|
"runNotFound": "Run not found",
|
||||||
|
"loadFailed": "Failed to load execution details",
|
||||||
|
"executionStopped": "Execution stopped by user",
|
||||||
|
"live": "Live",
|
||||||
|
"stopFailed": "Failed to stop agent - it may have already finished"
|
||||||
|
},
|
||||||
|
"tabs": {
|
||||||
|
"scrollLeft": "Scroll tabs left",
|
||||||
|
"scrollRight": "Scroll tabs right",
|
||||||
|
"browseProjectsShortcut": "Browse projects (Ctrl+T)",
|
||||||
|
"maximumTabsReached": "Maximum tabs reached ({{count}}/20)"
|
||||||
|
},
|
||||||
|
"storageTab": {
|
||||||
|
"title": "Database Storage",
|
||||||
|
"sqlQuery": "SQL Query",
|
||||||
|
"resetDbShort": "Reset DB",
|
||||||
|
"selectTable": "Select a table",
|
||||||
|
"rows": "rows",
|
||||||
|
"searchInTable": "Search in table...",
|
||||||
|
"newRow": "New Row",
|
||||||
|
"actions": "Actions",
|
||||||
|
"pagination": {
|
||||||
|
"showing": "Showing {{from}} to {{to}} of {{total}} rows",
|
||||||
|
"pageOf": "Page {{page}} of {{total}}"
|
||||||
|
},
|
||||||
|
"editRow": "Edit Row",
|
||||||
|
"editRowDesc": "Update the values for this row in the {{table}} table.",
|
||||||
|
"primaryKey": "Primary Key",
|
||||||
|
"type": "Type",
|
||||||
|
"notNull": "NOT NULL",
|
||||||
|
"default": "Default",
|
||||||
|
"insert": "Insert",
|
||||||
|
"newRowDesc": "Add a new row to the {{table}} table.",
|
||||||
|
"deleteRow": "Delete Row",
|
||||||
|
"deleteRowConfirm": "Are you sure you want to delete this row? This action cannot be undone.",
|
||||||
|
"resetDatabaseTitle": "Reset Database",
|
||||||
|
"resetDatabaseDesc": "This will delete all data and recreate the database with its default structure (empty tables for agents, agent_runs, and app_settings). The database will be restored to the same state as when you first installed the app. This action cannot be undone.",
|
||||||
|
"resetWarning": "All your agents, runs, and settings will be permanently deleted!",
|
||||||
|
"sqlEditorTitle": "SQL Query Editor",
|
||||||
|
"sqlEditorDesc": "Execute raw SQL queries on the database. Use with caution.",
|
||||||
|
"sqlQueryPlaceholder": "SELECT * FROM agents LIMIT 10;",
|
||||||
|
"queryExecuted": "Query executed successfully.",
|
||||||
|
"rowsAffected": "rows affected.",
|
||||||
|
"lastInsertId": "Last insert ID",
|
||||||
|
"loadTablesFailed": "Failed to load tables",
|
||||||
|
"loadTableDataFailed": "Failed to load table data",
|
||||||
|
"updateRowFailed": "Failed to update row",
|
||||||
|
"deleteRowFailed": "Failed to delete row",
|
||||||
|
"insertRowFailed": "Failed to insert row",
|
||||||
|
"executeSqlFailed": "Failed to execute SQL",
|
||||||
|
"resetDatabaseFailed": "Failed to reset database",
|
||||||
|
"resetFailed": "Reset failed: please try again.",
|
||||||
|
"resetSuccess": "Database has been reset to its default state with empty tables (agents, agent_runs, app_settings)."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
"name": "Claudia",
|
"name": "Claudia",
|
||||||
"welcome": "欢迎使用 Claudia",
|
"welcome": "欢迎使用 Claudia",
|
||||||
"tagline": "强大的 Claude Code 会话管理工具",
|
"tagline": "强大的 Claude Code 会话管理工具",
|
||||||
|
"returnHome": "返回首页",
|
||||||
"loading": "加载中...",
|
"loading": "加载中...",
|
||||||
"error": "错误",
|
"error": "错误",
|
||||||
"success": "成功",
|
"success": "成功",
|
||||||
@@ -96,7 +97,19 @@
|
|||||||
"justNow": "刚刚",
|
"justNow": "刚刚",
|
||||||
"minutesAgo": "{{count}} 分钟前",
|
"minutesAgo": "{{count}} 分钟前",
|
||||||
"hoursAgo": "{{count}} 小时前",
|
"hoursAgo": "{{count}} 小时前",
|
||||||
"daysAgo": "{{count}} 天前"
|
"daysAgo": "{{count}} 天前",
|
||||||
|
"loadingOutput": "加载输出中...",
|
||||||
|
"waitingForOutput": "等待输出...",
|
||||||
|
"noOutput": "暂无输出",
|
||||||
|
"agentRunningNoOutput": "智能体正在运行,但尚未收到输出",
|
||||||
|
"failedToLoadSessionOutput": "加载会话输出失败",
|
||||||
|
"outputRefreshed": "输出已刷新",
|
||||||
|
"failedToRefreshOutput": "刷新输出失败",
|
||||||
|
"agentExecutionCompleted": "智能体执行已完成",
|
||||||
|
"agentExecutionCancelled": "智能体执行已取消",
|
||||||
|
"loadingAgentRun": "正在加载运行详情...",
|
||||||
|
"selectProjectFirst": "请先选择项目目录",
|
||||||
|
"selectDirectoryFailed": "选择目录失败:{{message}}"
|
||||||
},
|
},
|
||||||
"navigation": {
|
"navigation": {
|
||||||
"projects": "Claude Code 项目",
|
"projects": "Claude Code 项目",
|
||||||
@@ -135,7 +148,27 @@
|
|||||||
"sessions": "会话",
|
"sessions": "会话",
|
||||||
"noSessions": "未找到会话",
|
"noSessions": "未找到会话",
|
||||||
"lastModified": "最近修改",
|
"lastModified": "最近修改",
|
||||||
"sessionHistory": "会话历史"
|
"sessionHistory": "会话历史",
|
||||||
|
"sessionCount": "共 {{count}} 个会话"
|
||||||
|
},
|
||||||
|
"sessions": {
|
||||||
|
"firstMessage": "首条消息:",
|
||||||
|
"hasTodo": "包含待办"
|
||||||
|
},
|
||||||
|
"runningSessions": {
|
||||||
|
"title": "运行中的 Claude 会话",
|
||||||
|
"countRunning": "({{count}} 个运行中)",
|
||||||
|
"running": "运行中",
|
||||||
|
"resume": "恢复",
|
||||||
|
"loadFailed": "加载运行中的会话失败"
|
||||||
|
},
|
||||||
|
"widgets": {
|
||||||
|
"terminal": { "title": "终端" },
|
||||||
|
"common": { "running": "运行中..." },
|
||||||
|
"bash": {
|
||||||
|
"commandFailed": "命令执行失败",
|
||||||
|
"commandCompleted": "命令执行完成"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"agents": {
|
"agents": {
|
||||||
"title": "Agent 管理",
|
"title": "Agent 管理",
|
||||||
@@ -202,6 +235,11 @@
|
|||||||
"enterTask": "输入智能体的任务",
|
"enterTask": "输入智能体的任务",
|
||||||
"hooks": "钩子",
|
"hooks": "钩子",
|
||||||
"configureHooks": "配置钩子",
|
"configureHooks": "配置钩子",
|
||||||
|
"configureHooksDesc": "配置在工具执行前、中、后运行的钩子。更改将立即保存。",
|
||||||
|
"projectSettings": "项目设置",
|
||||||
|
"localSettings": "本地设置",
|
||||||
|
"initializing": "正在初始化智能体...",
|
||||||
|
"exportToFile": "导出为 .claudia.json",
|
||||||
"fullscreen": "全屏",
|
"fullscreen": "全屏",
|
||||||
"stop": "停止",
|
"stop": "停止",
|
||||||
"selectProjectPathAndTask": "选择项目路径并输入任务以运行智能体",
|
"selectProjectPathAndTask": "选择项目路径并输入任务以运行智能体",
|
||||||
@@ -306,7 +344,13 @@
|
|||||||
"condition": "条件",
|
"condition": "条件",
|
||||||
"matchesRegex": "匹配正则表达式",
|
"matchesRegex": "匹配正则表达式",
|
||||||
"message": "消息",
|
"message": "消息",
|
||||||
"enterShellCommand": "输入 shell 命令..."
|
"enterShellCommand": "输入 shell 命令...",
|
||||||
|
"projectHooks": "项目钩子",
|
||||||
|
"localHooks": "本地钩子"
|
||||||
|
},
|
||||||
|
"projectSettings": {
|
||||||
|
"gitignoreLocalWarning": "本地设置文件未添加到 .gitignore",
|
||||||
|
"addToGitignore": "添加到 .gitignore"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"title": "设置",
|
"title": "设置",
|
||||||
@@ -369,6 +413,8 @@
|
|||||||
"verboseOutputDesc": "显示完整的 bash 和命令输出",
|
"verboseOutputDesc": "显示完整的 bash 和命令输出",
|
||||||
"chatRetention": "聊天记录保留期 (天)",
|
"chatRetention": "聊天记录保留期 (天)",
|
||||||
"chatRetentionDesc": "本地保留聊天记录的时长(默认:30 天)",
|
"chatRetentionDesc": "本地保留聊天记录的时长(默认:30 天)",
|
||||||
|
"chatRetentionPermanent": "已启用永久保存,聊天记录将不会被自动删除",
|
||||||
|
"permanentRetention": "永久保存",
|
||||||
"claudeCodeInstallation": "Claude Code 安装",
|
"claudeCodeInstallation": "Claude Code 安装",
|
||||||
"choosePreferredInstallation": "选择您偏好的 Claude Code 安装。",
|
"choosePreferredInstallation": "选择您偏好的 Claude Code 安装。",
|
||||||
"loadingAvailableInstallations": "正在加载可用安装...",
|
"loadingAvailableInstallations": "正在加载可用安装...",
|
||||||
@@ -678,6 +724,9 @@
|
|||||||
"checkpoint": {
|
"checkpoint": {
|
||||||
"title": "检查点",
|
"title": "检查点",
|
||||||
"createCheckpoint": "创建检查点",
|
"createCheckpoint": "创建检查点",
|
||||||
|
"createCheckpointDesc": "保存会话当前状态,并可填写可选说明。",
|
||||||
|
"descriptionOptional": "描述(可选)",
|
||||||
|
"descriptionPlaceholder": "例如:大型重构前",
|
||||||
"restoreCheckpoint": "恢复检查点",
|
"restoreCheckpoint": "恢复检查点",
|
||||||
"deleteCheckpoint": "删除检查点",
|
"deleteCheckpoint": "删除检查点",
|
||||||
"checkpointName": "检查点名称",
|
"checkpointName": "检查点名称",
|
||||||
@@ -688,6 +737,15 @@
|
|||||||
"checkpointSettingsTitle": "检查点设置",
|
"checkpointSettingsTitle": "检查点设置",
|
||||||
"experimentalFeature": "实验性功能",
|
"experimentalFeature": "实验性功能",
|
||||||
"checkpointWarning": "检查点可能会影响目录结构或导致数据丢失。请谨慎使用。",
|
"checkpointWarning": "检查点可能会影响目录结构或导致数据丢失。请谨慎使用。",
|
||||||
|
"current": "当前",
|
||||||
|
"restoreToThis": "恢复到此检查点",
|
||||||
|
"forkFromThis": "从此检查点分叉",
|
||||||
|
"compareWithAnother": "与另一个检查点比较",
|
||||||
|
"checkpointComparison": "检查点对比",
|
||||||
|
"modifiedFiles": "修改的文件",
|
||||||
|
"addedFiles": "新增的文件",
|
||||||
|
"deletedFiles": "删除的文件",
|
||||||
|
"noPrompt": "无提示",
|
||||||
"automaticCheckpoints": "自动检查点",
|
"automaticCheckpoints": "自动检查点",
|
||||||
"automaticCheckpointsDesc": "根据所选策略自动创建检查点",
|
"automaticCheckpointsDesc": "根据所选策略自动创建检查点",
|
||||||
"checkpointStrategy": "检查点策略",
|
"checkpointStrategy": "检查点策略",
|
||||||
@@ -894,4 +952,60 @@
|
|||||||
"warning": {
|
"warning": {
|
||||||
"title": "警告"
|
"title": "警告"
|
||||||
}
|
}
|
||||||
|
,
|
||||||
|
"agentRun": {
|
||||||
|
"runNotFound": "未找到运行记录",
|
||||||
|
"loadFailed": "加载执行详情失败",
|
||||||
|
"executionStopped": "已由用户停止执行",
|
||||||
|
"live": "实时",
|
||||||
|
"stopFailed": "停止失败,可能已结束"
|
||||||
|
},
|
||||||
|
"tabs": {
|
||||||
|
"scrollLeft": "向左滚动标签",
|
||||||
|
"scrollRight": "向右滚动标签",
|
||||||
|
"browseProjectsShortcut": "浏览项目(Ctrl+T)",
|
||||||
|
"maximumTabsReached": "已达到最大标签数({{count}}/20)"
|
||||||
|
},
|
||||||
|
"storageTab": {
|
||||||
|
"title": "数据库存储",
|
||||||
|
"sqlQuery": "SQL 查询",
|
||||||
|
"resetDbShort": "重置库",
|
||||||
|
"selectTable": "选择数据表",
|
||||||
|
"rows": "行",
|
||||||
|
"searchInTable": "在表中搜索...",
|
||||||
|
"newRow": "新增行",
|
||||||
|
"actions": "操作",
|
||||||
|
"pagination": {
|
||||||
|
"showing": "显示第 {{from}}-{{to}} 条,共 {{total}} 条",
|
||||||
|
"pageOf": "第 {{page}} / {{total}} 页"
|
||||||
|
},
|
||||||
|
"editRow": "编辑行",
|
||||||
|
"editRowDesc": "更新 {{table}} 表中此行的值。",
|
||||||
|
"primaryKey": "主键",
|
||||||
|
"type": "类型",
|
||||||
|
"notNull": "非空",
|
||||||
|
"default": "默认值",
|
||||||
|
"insert": "插入",
|
||||||
|
"newRowDesc": "向 {{table}} 表添加一行。",
|
||||||
|
"deleteRow": "删除行",
|
||||||
|
"deleteRowConfirm": "确定要删除此行吗?该操作无法撤销。",
|
||||||
|
"resetDatabaseTitle": "重置数据库",
|
||||||
|
"resetDatabaseDesc": "这将删除所有数据并使用默认结构重新创建数据库(agents、agent_runs、app_settings 为空表)。数据库将恢复到首次安装应用时的状态。该操作无法撤销。",
|
||||||
|
"resetWarning": "您所有的智能体、运行记录和设置都将被永久删除!",
|
||||||
|
"sqlEditorTitle": "SQL 查询编辑器",
|
||||||
|
"sqlEditorDesc": "在数据库上执行原始 SQL 语句,请谨慎使用。",
|
||||||
|
"sqlQueryPlaceholder": "SELECT * FROM agents LIMIT 10;",
|
||||||
|
"queryExecuted": "查询执行成功。",
|
||||||
|
"rowsAffected": "行受影响。",
|
||||||
|
"lastInsertId": "最后插入 ID",
|
||||||
|
"loadTablesFailed": "加载数据表失败",
|
||||||
|
"loadTableDataFailed": "加载数据失败",
|
||||||
|
"updateRowFailed": "更新行失败",
|
||||||
|
"deleteRowFailed": "删除行失败",
|
||||||
|
"insertRowFailed": "插入行失败",
|
||||||
|
"executeSqlFailed": "执行 SQL 失败",
|
||||||
|
"resetDatabaseFailed": "重置数据库失败",
|
||||||
|
"resetFailed": "重置失败,请重试。",
|
||||||
|
"resetSuccess": "数据库已重置为默认状态(agents、agent_runs、app_settings 为空表)。"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user