mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-03-01 13:03:53 +08:00
Add API error monitoring tests and error context snapshots for various browsers
- Created error context snapshots for Firefox, WebKit, and Chromium to capture UI state during API error monitoring. - Implemented e2e tests for API error detection, including console errors, failed API requests, and proxy errors. - Added functionality to ignore specific API patterns in monitoring assertions. - Ensured tests validate the monitoring system's ability to detect and report errors effectively.
This commit is contained in:
@@ -116,7 +116,7 @@ export function FixSessionPage() {
|
||||
<div className="flex items-center gap-4">
|
||||
<Button variant="ghost" size="sm" disabled>
|
||||
<ArrowLeft className="h-4 w-4 mr-2" />
|
||||
{formatMessage({ id: 'common.back' })}
|
||||
{formatMessage({ id: 'common.actions.back' })}
|
||||
</Button>
|
||||
<div className="h-8 w-48 rounded bg-muted animate-pulse" />
|
||||
</div>
|
||||
@@ -158,7 +158,7 @@ export function FixSessionPage() {
|
||||
</p>
|
||||
<Button onClick={handleBack}>
|
||||
<ArrowLeft className="h-4 w-4 mr-2" />
|
||||
{formatMessage({ id: 'common.back' })}
|
||||
{formatMessage({ id: 'common.actions.back' })}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
@@ -170,7 +170,7 @@ export function FixSessionPage() {
|
||||
<div className="flex items-center gap-4">
|
||||
<Button variant="ghost" size="sm" onClick={handleBack}>
|
||||
<ArrowLeft className="h-4 w-4 mr-2" />
|
||||
{formatMessage({ id: 'common.back' })}
|
||||
{formatMessage({ id: 'common.actions.back' })}
|
||||
</Button>
|
||||
<div className="flex-1">
|
||||
<h1 className="text-2xl font-semibold text-foreground">{session.session_id}</h1>
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useHistory } from '@/hooks/useHistory';
|
||||
import { ConversationCard } from '@/components/shared/ConversationCard';
|
||||
import { CliStreamPanel } from '@/components/shared/CliStreamPanel';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import {
|
||||
@@ -35,6 +36,7 @@ import {
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuLabel,
|
||||
} from '@/components/ui/Dropdown';
|
||||
import type { CliExecution } from '@/lib/api';
|
||||
|
||||
/**
|
||||
* HistoryPage component - Display CLI execution history
|
||||
@@ -46,6 +48,8 @@ export function HistoryPage() {
|
||||
const [deleteDialogOpen, setDeleteDialogOpen] = React.useState(false);
|
||||
const [deleteType, setDeleteType] = React.useState<'single' | 'tool' | 'all' | null>(null);
|
||||
const [deleteTarget, setDeleteTarget] = React.useState<string | null>(null);
|
||||
const [selectedExecution, setSelectedExecution] = React.useState<string | null>(null);
|
||||
const [isPanelOpen, setIsPanelOpen] = React.useState(false);
|
||||
|
||||
const {
|
||||
executions,
|
||||
@@ -78,6 +82,12 @@ export function HistoryPage() {
|
||||
|
||||
const hasActiveFilters = searchQuery.length > 0 || toolFilter !== undefined;
|
||||
|
||||
// Card click handler - open execution details panel
|
||||
const handleCardClick = (execution: CliExecution) => {
|
||||
setSelectedExecution(execution.id);
|
||||
setIsPanelOpen(true);
|
||||
};
|
||||
|
||||
// Delete handlers
|
||||
const handleDeleteClick = (id: string) => {
|
||||
setDeleteType('single');
|
||||
@@ -263,6 +273,7 @@ export function HistoryPage() {
|
||||
<ConversationCard
|
||||
key={execution.id}
|
||||
execution={execution}
|
||||
onClick={handleCardClick}
|
||||
onDelete={handleDeleteClick}
|
||||
actionsDisabled={isDeleting}
|
||||
/>
|
||||
@@ -270,6 +281,13 @@ export function HistoryPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* CLI Stream Panel */}
|
||||
<CliStreamPanel
|
||||
executionId={selectedExecution || ''}
|
||||
open={isPanelOpen}
|
||||
onOpenChange={setIsPanelOpen}
|
||||
/>
|
||||
|
||||
{/* Delete Confirmation Dialog */}
|
||||
<Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
||||
<DialogContent>
|
||||
|
||||
@@ -88,7 +88,7 @@ export function LiteTaskDetailPage() {
|
||||
<div className="flex items-center gap-4">
|
||||
<Button variant="ghost" size="sm" disabled>
|
||||
<ArrowLeft className="h-4 w-4 mr-2" />
|
||||
{formatMessage({ id: 'common.back' })}
|
||||
{formatMessage({ id: 'common.actions.back' })}
|
||||
</Button>
|
||||
<div className="h-8 w-64 rounded bg-muted animate-pulse" />
|
||||
</div>
|
||||
@@ -126,7 +126,7 @@ export function LiteTaskDetailPage() {
|
||||
</p>
|
||||
<Button onClick={handleBack}>
|
||||
<ArrowLeft className="h-4 w-4 mr-2" />
|
||||
{formatMessage({ id: 'common.back' })}
|
||||
{formatMessage({ id: 'common.actions.back' })}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
@@ -143,7 +143,7 @@ export function LiteTaskDetailPage() {
|
||||
<div className="flex items-center gap-4">
|
||||
<Button variant="ghost" size="sm" onClick={handleBack}>
|
||||
<ArrowLeft className="h-4 w-4 mr-2" />
|
||||
{formatMessage({ id: 'common.back' })}
|
||||
{formatMessage({ id: 'common.actions.back' })}
|
||||
</Button>
|
||||
<div>
|
||||
<h1 className="text-2xl font-semibold text-foreground">
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
// ========================================
|
||||
// LiteTasksPage Component
|
||||
// ========================================
|
||||
// Lite-plan and lite-fix task list page with flowchart rendering
|
||||
// Lite-plan and lite-fix task list page with TaskDrawer
|
||||
|
||||
import * as React from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useIntl } from 'react-intl';
|
||||
import {
|
||||
ArrowLeft,
|
||||
@@ -18,12 +17,17 @@ import {
|
||||
Activity,
|
||||
Repeat,
|
||||
MessageCircle,
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
} from 'lucide-react';
|
||||
import { useLiteTasks } from '@/hooks/useLiteTasks';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
import { Card, CardContent } from '@/components/ui/Card';
|
||||
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/Tabs';
|
||||
import { TaskDrawer } from '@/components/shared/TaskDrawer';
|
||||
import type { LiteTask, LiteTaskSession } from '@/lib/api';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
type LiteTaskTab = 'lite-plan' | 'lite-fix' | 'multi-cli-plan';
|
||||
|
||||
@@ -37,13 +41,15 @@ function getI18nText(label: string | { en?: string; zh?: string } | undefined, f
|
||||
}
|
||||
|
||||
/**
|
||||
* LiteTasksPage component - Display lite-plan and lite-fix sessions
|
||||
* LiteTasksPage component - Display lite-plan and lite-fix sessions with expandable tasks
|
||||
*/
|
||||
export function LiteTasksPage() {
|
||||
const navigate = useNavigate();
|
||||
const { formatMessage } = useIntl();
|
||||
const { litePlan, liteFix, multiCliPlan, isLoading, error, refetch } = useLiteTasks();
|
||||
const [activeTab, setActiveTab] = React.useState<LiteTaskTab>('lite-plan');
|
||||
const [expandedSessionId, setExpandedSessionId] = React.useState<string | null>(null);
|
||||
const [selectedTask, setSelectedTask] = React.useState<LiteTask | null>(null);
|
||||
|
||||
const handleBack = () => {
|
||||
navigate('/sessions');
|
||||
@@ -66,53 +72,102 @@ export function LiteTasksPage() {
|
||||
return statusColors[status || ''] || 'secondary';
|
||||
};
|
||||
|
||||
// Render lite task card
|
||||
const renderLiteTaskCard = (session: { id: string; type: string; createdAt?: string; tasks?: unknown[] }) => {
|
||||
// Render lite task card with expandable tasks
|
||||
const renderLiteTaskCard = (session: LiteTaskSession) => {
|
||||
const isLitePlan = session.type === 'lite-plan';
|
||||
const taskCount = session.tasks?.length || 0;
|
||||
const isExpanded = expandedSessionId === session.id;
|
||||
|
||||
return (
|
||||
<Card
|
||||
key={session.id}
|
||||
className="cursor-pointer hover:shadow-md transition-shadow"
|
||||
onClick={() => navigate(`/lite-tasks/${session.id}`)}
|
||||
>
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="font-medium text-foreground text-sm">{session.id}</h3>
|
||||
<div key={session.id}>
|
||||
<Card
|
||||
className="cursor-pointer hover:shadow-md transition-shadow"
|
||||
onClick={() => setExpandedSessionId(isExpanded ? null : session.id)}
|
||||
>
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="flex items-center gap-3 flex-1 min-w-0">
|
||||
<div className="flex-shrink-0">
|
||||
{isExpanded ? (
|
||||
<ChevronDown className="h-5 w-5 text-muted-foreground" />
|
||||
) : (
|
||||
<ChevronRight className="h-5 w-5 text-muted-foreground" />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="font-medium text-foreground text-sm">{session.id}</h3>
|
||||
</div>
|
||||
</div>
|
||||
<Badge variant={isLitePlan ? 'secondary' : 'warning'} className="gap-1 flex-shrink-0">
|
||||
{isLitePlan ? <FileEdit className="h-3 w-3" /> : <Wrench className="h-3 w-3" />}
|
||||
{formatMessage({ id: isLitePlan ? 'liteTasks.type.plan' : 'liteTasks.type.fix' })}
|
||||
</Badge>
|
||||
</div>
|
||||
<Badge variant={isLitePlan ? 'info' : 'warning'} className="gap-1">
|
||||
{isLitePlan ? <FileEdit className="h-3 w-3" /> : <Wrench className="h-3 w-3" />}
|
||||
{formatMessage({ id: isLitePlan ? 'liteTasks.type.plan' : 'liteTasks.type.fix' })}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 text-xs text-muted-foreground">
|
||||
{session.createdAt && (
|
||||
<div className="flex items-center gap-3 text-xs text-muted-foreground">
|
||||
{session.createdAt && (
|
||||
<span className="flex items-center gap-1">
|
||||
<Calendar className="h-3.5 w-3.5" />
|
||||
{new Date(session.createdAt).toLocaleDateString()}
|
||||
</span>
|
||||
)}
|
||||
<span className="flex items-center gap-1">
|
||||
<Calendar className="h-3.5 w-3.5" />
|
||||
{new Date(session.createdAt).toLocaleDateString()}
|
||||
<ListChecks className="h-3.5 w-3.5" />
|
||||
{taskCount} {formatMessage({ id: 'session.tasks' })}
|
||||
</span>
|
||||
)}
|
||||
<span className="flex items-center gap-1">
|
||||
<ListChecks className="h-3.5 w-3.5" />
|
||||
{taskCount} {formatMessage({ id: 'session.tasks' })}
|
||||
</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Expanded tasks list */}
|
||||
{isExpanded && session.tasks && session.tasks.length > 0 && (
|
||||
<div className="mt-2 ml-6 space-y-2 pb-2">
|
||||
{session.tasks.map((task, index) => {
|
||||
const taskStatusColor = task.status === 'completed' ? 'success' :
|
||||
task.status === 'in_progress' ? 'warning' :
|
||||
task.status === 'failed' ? 'destructive' : 'secondary';
|
||||
|
||||
return (
|
||||
<Card
|
||||
key={task.id || index}
|
||||
className="cursor-pointer hover:shadow-sm hover:border-primary/50 transition-all border-border"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setSelectedTask(task);
|
||||
}}
|
||||
>
|
||||
<CardContent className="p-3">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className="text-xs font-mono text-muted-foreground">
|
||||
{task.task_id || `#${index + 1}`}
|
||||
</span>
|
||||
<Badge variant={taskStatusColor as 'success' | 'warning' | 'destructive' | 'secondary'} className="text-xs">
|
||||
{task.status}
|
||||
</Badge>
|
||||
</div>
|
||||
<h4 className="text-sm font-medium text-foreground">
|
||||
{task.title || 'Untitled Task'}
|
||||
</h4>
|
||||
{task.description && (
|
||||
<p className="text-xs text-muted-foreground mt-1 line-clamp-2">
|
||||
{task.description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Render multi-cli plan card
|
||||
const renderMultiCliCard = (session: {
|
||||
id: string;
|
||||
metadata?: Record<string, unknown>;
|
||||
latestSynthesis?: { title?: string | { en?: string; zh?: string }; status?: string };
|
||||
roundCount?: number;
|
||||
status?: string;
|
||||
createdAt?: string;
|
||||
}) => {
|
||||
const renderMultiCliCard = (session: LiteTaskSession) => {
|
||||
const metadata = session.metadata || {};
|
||||
const latestSynthesis = session.latestSynthesis || {};
|
||||
const roundCount = (metadata.roundId as number) || session.roundCount || 1;
|
||||
@@ -127,14 +182,23 @@ export function LiteTasksPage() {
|
||||
<Card
|
||||
key={session.id}
|
||||
className="cursor-pointer hover:shadow-md transition-shadow"
|
||||
onClick={() => navigate(`/lite-tasks/${session.id}`)}
|
||||
onClick={() => setExpandedSessionId(expandedSessionId === session.id ? null : session.id)}
|
||||
>
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="font-medium text-foreground text-sm">{session.id}</h3>
|
||||
<div className="flex items-center gap-3 flex-1 min-w-0">
|
||||
<div className="flex-shrink-0">
|
||||
{expandedSessionId === session.id ? (
|
||||
<ChevronDown className="h-5 w-5 text-muted-foreground" />
|
||||
) : (
|
||||
<ChevronRight className="h-5 w-5 text-muted-foreground" />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="font-medium text-foreground text-sm">{session.id}</h3>
|
||||
</div>
|
||||
</div>
|
||||
<Badge variant="info" className="gap-1">
|
||||
<Badge variant="secondary" className="gap-1 flex-shrink-0">
|
||||
<MessagesSquare className="h-3 w-3" />
|
||||
{formatMessage({ id: 'liteTasks.type.multiCli' })}
|
||||
</Badge>
|
||||
@@ -171,7 +235,7 @@ export function LiteTasksPage() {
|
||||
<div className="flex items-center gap-4">
|
||||
<Button variant="ghost" size="sm" disabled>
|
||||
<ArrowLeft className="h-4 w-4 mr-2" />
|
||||
{formatMessage({ id: 'common.back' })}
|
||||
{formatMessage({ id: 'common.actions.back' })}
|
||||
</Button>
|
||||
<div className="h-8 w-64 rounded bg-muted animate-pulse" />
|
||||
</div>
|
||||
@@ -205,7 +269,7 @@ export function LiteTasksPage() {
|
||||
<div className="flex items-center gap-4">
|
||||
<Button variant="ghost" size="sm" onClick={handleBack}>
|
||||
<ArrowLeft className="h-4 w-4 mr-2" />
|
||||
{formatMessage({ id: 'common.back' })}
|
||||
{formatMessage({ id: 'common.actions.back' })}
|
||||
</Button>
|
||||
<div>
|
||||
<h1 className="text-2xl font-semibold text-foreground">
|
||||
@@ -295,6 +359,13 @@ export function LiteTasksPage() {
|
||||
)}
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
{/* TaskDrawer */}
|
||||
<TaskDrawer
|
||||
task={selectedTask}
|
||||
isOpen={!!selectedTask}
|
||||
onClose={() => setSelectedTask(null)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -209,7 +209,7 @@ export function ReviewSessionPage() {
|
||||
<div className="flex items-center gap-4">
|
||||
<Button variant="ghost" size="sm" disabled>
|
||||
<ArrowLeft className="h-4 w-4 mr-2" />
|
||||
{formatMessage({ id: 'common.back' })}
|
||||
{formatMessage({ id: 'common.actions.back' })}
|
||||
</Button>
|
||||
<div className="h-8 w-64 rounded bg-muted animate-pulse" />
|
||||
</div>
|
||||
@@ -247,7 +247,7 @@ export function ReviewSessionPage() {
|
||||
</p>
|
||||
<Button onClick={handleBack}>
|
||||
<ArrowLeft className="h-4 w-4 mr-2" />
|
||||
{formatMessage({ id: 'common.back' })}
|
||||
{formatMessage({ id: 'common.actions.back' })}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
@@ -263,7 +263,7 @@ export function ReviewSessionPage() {
|
||||
<div className="flex items-center gap-4">
|
||||
<Button variant="ghost" size="sm" onClick={handleBack}>
|
||||
<ArrowLeft className="h-4 w-4 mr-2" />
|
||||
{formatMessage({ id: 'common.back' })}
|
||||
{formatMessage({ id: 'common.actions.back' })}
|
||||
</Button>
|
||||
<div>
|
||||
<h1 className="text-2xl font-semibold text-foreground">
|
||||
|
||||
@@ -18,9 +18,11 @@ import { useSessionDetail } from '@/hooks/useSessionDetail';
|
||||
import { TaskListTab } from './session-detail/TaskListTab';
|
||||
import { ContextTab } from './session-detail/ContextTab';
|
||||
import { SummaryTab } from './session-detail/SummaryTab';
|
||||
import { TaskDrawer } from '@/components/shared/TaskDrawer';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/Tabs';
|
||||
import type { TaskData } from '@/types/store';
|
||||
|
||||
type TabValue = 'tasks' | 'context' | 'summary';
|
||||
|
||||
@@ -33,6 +35,7 @@ export function SessionDetailPage() {
|
||||
const { formatMessage } = useIntl();
|
||||
const { sessionDetail, isLoading, error, refetch } = useSessionDetail(sessionId!);
|
||||
const [activeTab, setActiveTab] = React.useState<TabValue>('tasks');
|
||||
const [selectedTask, setSelectedTask] = React.useState<TaskData | null>(null);
|
||||
|
||||
const handleBack = () => {
|
||||
navigate('/sessions');
|
||||
@@ -45,7 +48,7 @@ export function SessionDetailPage() {
|
||||
<div className="flex items-center gap-4">
|
||||
<Button variant="ghost" size="sm" disabled>
|
||||
<ArrowLeft className="h-4 w-4 mr-2" />
|
||||
{formatMessage({ id: 'common.back' })}
|
||||
{formatMessage({ id: 'common.actions.back' })}
|
||||
</Button>
|
||||
<div className="h-8 w-64 rounded bg-muted animate-pulse" />
|
||||
</div>
|
||||
@@ -83,7 +86,7 @@ export function SessionDetailPage() {
|
||||
</p>
|
||||
<Button onClick={handleBack}>
|
||||
<ArrowLeft className="h-4 w-4 mr-2" />
|
||||
{formatMessage({ id: 'common.back' })}
|
||||
{formatMessage({ id: 'common.actions.back' })}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
@@ -100,7 +103,7 @@ export function SessionDetailPage() {
|
||||
<div className="flex items-center gap-4">
|
||||
<Button variant="ghost" size="sm" onClick={handleBack}>
|
||||
<ArrowLeft className="h-4 w-4 mr-2" />
|
||||
{formatMessage({ id: 'common.back' })}
|
||||
{formatMessage({ id: 'common.actions.back' })}
|
||||
</Button>
|
||||
<div>
|
||||
<h1 className="text-2xl font-semibold text-foreground">
|
||||
@@ -158,7 +161,7 @@ export function SessionDetailPage() {
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="tasks" className="mt-4">
|
||||
<TaskListTab session={session} />
|
||||
<TaskListTab session={session} onTaskClick={setSelectedTask} />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="context" className="mt-4">
|
||||
@@ -179,6 +182,13 @@ export function SessionDetailPage() {
|
||||
<p className="text-sm text-muted-foreground">{session.description}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* TaskDrawer */}
|
||||
<TaskDrawer
|
||||
task={selectedTask}
|
||||
isOpen={!!selectedTask}
|
||||
onClose={() => setSelectedTask(null)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import { useIntl } from 'react-intl';
|
||||
import {
|
||||
Settings,
|
||||
Moon,
|
||||
Sun,
|
||||
Bell,
|
||||
Cpu,
|
||||
RefreshCw,
|
||||
@@ -28,6 +27,7 @@ import { Card } from '@/components/ui/Card';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
import { ThemeSelector } from '@/components/shared/ThemeSelector';
|
||||
import { useTheme } from '@/hooks';
|
||||
import { useHooks, useRules, useToggleHook, useToggleRule } from '@/hooks';
|
||||
import { useConfigStore, selectCliTools, selectDefaultCliTool, selectUserPreferences } from '@/stores/configStore';
|
||||
@@ -430,39 +430,33 @@ export function SettingsPage() {
|
||||
<Moon className="w-5 h-5" />
|
||||
{formatMessage({ id: 'settings.sections.appearance' })}
|
||||
</h2>
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-6">
|
||||
{/* Multi-Theme Selector */}
|
||||
<div>
|
||||
<p className="font-medium text-foreground mb-1">
|
||||
{formatMessage({ id: 'settings.appearance.theme' })}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
{formatMessage({ id: 'settings.appearance.description' })}
|
||||
</p>
|
||||
<ThemeSelector />
|
||||
</div>
|
||||
|
||||
{/* System Theme Toggle (Backward Compatibility) */}
|
||||
<div className="flex items-center justify-between pt-4 border-t border-border">
|
||||
<div>
|
||||
<p className="font-medium text-foreground">{formatMessage({ id: 'settings.appearance.theme' })}</p>
|
||||
<p className="font-medium text-foreground">系统跟随</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{formatMessage({ id: 'settings.appearance.description' })}
|
||||
使用系统的深色/浅色模式设置
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant={theme === 'light' ? 'default' : 'outline'}
|
||||
size="sm"
|
||||
onClick={() => setTheme('light')}
|
||||
>
|
||||
<Sun className="w-4 h-4 mr-2" />
|
||||
{formatMessage({ id: 'settings.appearance.themeOptions.light' })}
|
||||
</Button>
|
||||
<Button
|
||||
variant={theme === 'dark' ? 'default' : 'outline'}
|
||||
size="sm"
|
||||
onClick={() => setTheme('dark')}
|
||||
>
|
||||
<Moon className="w-4 h-4 mr-2" />
|
||||
{formatMessage({ id: 'settings.appearance.themeOptions.dark' })}
|
||||
</Button>
|
||||
<Button
|
||||
variant={theme === 'system' ? 'default' : 'outline'}
|
||||
size="sm"
|
||||
onClick={() => setTheme('system')}
|
||||
>
|
||||
{formatMessage({ id: 'settings.appearance.themeOptions.system' })}
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
variant={theme === 'system' ? 'default' : 'outline'}
|
||||
size="sm"
|
||||
onClick={() => setTheme('system')}
|
||||
>
|
||||
{formatMessage({ id: 'settings.appearance.themeOptions.system' })}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@@ -13,10 +13,11 @@ import {
|
||||
} from 'lucide-react';
|
||||
import { Card, CardContent } from '@/components/ui/Card';
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
import type { SessionMetadata } from '@/types/store';
|
||||
import type { SessionMetadata, TaskData } from '@/types/store';
|
||||
|
||||
export interface TaskListTabProps {
|
||||
session: SessionMetadata;
|
||||
onTaskClick?: (task: TaskData) => void;
|
||||
}
|
||||
|
||||
// Status configuration
|
||||
@@ -51,7 +52,7 @@ const taskStatusConfig: Record<string, { label: string; variant: 'default' | 'se
|
||||
/**
|
||||
* TaskListTab component - Display tasks in a list format
|
||||
*/
|
||||
export function TaskListTab({ session }: TaskListTabProps) {
|
||||
export function TaskListTab({ session, onTaskClick }: TaskListTabProps) {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
const tasks = session.tasks || [];
|
||||
@@ -104,7 +105,8 @@ export function TaskListTab({ session }: TaskListTabProps) {
|
||||
return (
|
||||
<Card
|
||||
key={task.task_id || index}
|
||||
className="hover:shadow-sm transition-shadow"
|
||||
className={`hover:shadow-sm transition-shadow ${onTaskClick ? 'cursor-pointer hover:shadow-md' : ''}`}
|
||||
onClick={() => onTaskClick?.(task as TaskData)}
|
||||
>
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
|
||||
Reference in New Issue
Block a user