mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-03-01 15:03:57 +08:00
feat(queue): implement queue scheduler service and API routes
- Added QueueSchedulerService to manage task queue lifecycle, including state machine, dependency resolution, and session management. - Implemented HTTP API endpoints for queue scheduling: - POST /api/queue/execute: Submit items to the scheduler. - GET /api/queue/scheduler/state: Retrieve full scheduler state. - POST /api/queue/scheduler/start: Start scheduling loop with items. - POST /api/queue/scheduler/pause: Pause scheduling. - POST /api/queue/scheduler/stop: Graceful stop of the scheduler. - POST /api/queue/scheduler/config: Update scheduler configuration. - Introduced types for queue items, scheduler state, and WebSocket messages to ensure type safety and compatibility with the backend. - Added static model lists for LiteLLM as a fallback for available models.
This commit is contained in:
@@ -260,7 +260,7 @@ export function CliStreamMonitorNew({ isOpen, onClose }: CliStreamMonitorNewProp
|
||||
{/* Main Panel */}
|
||||
<div
|
||||
className={cn(
|
||||
'fixed top-0 right-0 h-full w-[640px] bg-background border-l border-border shadow-2xl z-50 flex flex-col transition-transform duration-300 ease-in-out',
|
||||
'fixed top-0 right-0 h-full w-[1568px] bg-background border-l border-border shadow-2xl z-50 flex flex-col transition-transform duration-300 ease-in-out',
|
||||
isOpen ? 'translate-x-0' : 'translate-x-full'
|
||||
)}
|
||||
role="dialog"
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
// AssistantMessage Component
|
||||
// ========================================
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { Bot, ChevronDown, Copy, Check } from 'lucide-react';
|
||||
import { Bot, ChevronDown, Copy, Check, FileJson } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { JsonCard } from '../components/JsonCard';
|
||||
import { detectJsonInLine } from '../utils/jsonDetector';
|
||||
|
||||
// Status indicator component
|
||||
interface StatusIndicatorProps {
|
||||
@@ -94,6 +96,11 @@ export function AssistantMessage({
|
||||
const [isExpanded, setIsExpanded] = useState(true);
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
// Detect JSON in content
|
||||
const jsonDetection = useMemo(() => {
|
||||
return detectJsonInLine(content);
|
||||
}, [content]);
|
||||
|
||||
useEffect(() => {
|
||||
if (copied) {
|
||||
const timer = setTimeout(() => setCopied(false), 2000);
|
||||
@@ -126,6 +133,14 @@ export function AssistantMessage({
|
||||
{modelName}
|
||||
</span>
|
||||
|
||||
{/* JSON indicator badge */}
|
||||
{jsonDetection?.isJson && (
|
||||
<span className="flex items-center gap-0.5 text-[9px] text-violet-500 dark:text-violet-400 bg-violet-200/50 dark:bg-violet-800/50 px-1 rounded">
|
||||
<FileJson className="h-2.5 w-2.5" />
|
||||
JSON
|
||||
</span>
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-1.5 ml-auto">
|
||||
<StatusIndicator status={status} duration={duration} />
|
||||
<ChevronDown
|
||||
@@ -141,11 +156,21 @@ export function AssistantMessage({
|
||||
{isExpanded && (
|
||||
<>
|
||||
<div className="px-2.5 py-2 bg-violet-50/40 dark:bg-violet-950/30">
|
||||
<div className="bg-white/60 dark:bg-black/30 rounded border border-violet-200/40 dark:border-violet-800/30 p-2.5">
|
||||
<div className="text-xs text-foreground whitespace-pre-wrap break-words leading-relaxed">
|
||||
{content}
|
||||
{jsonDetection?.isJson && jsonDetection.parsed ? (
|
||||
/* JSON detected - use collapsible JsonCard */
|
||||
<JsonCard
|
||||
data={jsonDetection.parsed}
|
||||
type="stdout"
|
||||
onCopy={handleCopy}
|
||||
/>
|
||||
) : (
|
||||
/* Plain text content */
|
||||
<div className="bg-white/60 dark:bg-black/30 rounded border border-violet-200/40 dark:border-violet-800/30 p-2.5">
|
||||
<div className="text-xs text-foreground whitespace-pre-wrap break-words leading-relaxed">
|
||||
{content}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Metadata Footer - simplified */}
|
||||
|
||||
@@ -17,7 +17,7 @@ export function MessageExample() {
|
||||
<SystemMessage
|
||||
title="Session Started"
|
||||
timestamp={Date.now()}
|
||||
metadata="gemini-2.5-pro | Context: 28 files"
|
||||
metadata="Context: 28 files"
|
||||
content="CLI execution started: gemini (analysis mode)"
|
||||
/>
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
import { useCliExecutionDetail } from '@/hooks/useCliExecution';
|
||||
import { useNativeSession } from '@/hooks/useNativeSession';
|
||||
import type { ConversationRecord, ConversationTurn, NativeSessionTurn, NativeTokenInfo, NativeToolCall } from '@/lib/api';
|
||||
import { getToolVariant } from '@/lib/cli-tool-theme';
|
||||
|
||||
type ViewMode = 'per-turn' | 'concatenated' | 'native';
|
||||
type ConcatFormat = 'plain' | 'yaml' | 'json';
|
||||
@@ -69,16 +70,12 @@ function getStatusInfo(status: string) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get badge variant for tool name
|
||||
* Ensure prompt is a string (handle legacy object data)
|
||||
*/
|
||||
function getToolVariant(tool: string): 'default' | 'secondary' | 'outline' | 'success' | 'warning' | 'info' {
|
||||
const variants: Record<string, 'default' | 'secondary' | 'outline' | 'success' | 'warning' | 'info'> = {
|
||||
gemini: 'info',
|
||||
codex: 'success',
|
||||
qwen: 'warning',
|
||||
opencode: 'secondary',
|
||||
};
|
||||
return variants[tool] || 'secondary';
|
||||
function ensureString(value: unknown): string {
|
||||
if (typeof value === 'string') return value;
|
||||
if (value && typeof value === 'object') return JSON.stringify(value);
|
||||
return String(value ?? '');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,7 +92,7 @@ function buildConcatenatedPrompt(execution: ConversationRecord, format: ConcatFo
|
||||
for (const turn of turns) {
|
||||
parts.push(`--- Turn ${turn.turn} ---`);
|
||||
parts.push('USER:');
|
||||
parts.push(turn.prompt);
|
||||
parts.push(ensureString(turn.prompt));
|
||||
parts.push('');
|
||||
parts.push('ASSISTANT:');
|
||||
parts.push(turn.output.stdout || formatMessage({ id: 'cli-manager.streamPanel.noOutput' }));
|
||||
@@ -118,7 +115,7 @@ function buildConcatenatedPrompt(execution: ConversationRecord, format: ConcatFo
|
||||
yaml.push(` - turn: ${turn.turn}`);
|
||||
yaml.push(` timestamp: ${turn.timestamp}`);
|
||||
yaml.push(` prompt: |`);
|
||||
turn.prompt.split('\n').forEach(line => {
|
||||
ensureString(turn.prompt).split('\n').forEach(line => {
|
||||
yaml.push(` ${line}`);
|
||||
});
|
||||
yaml.push(` response: |`);
|
||||
@@ -140,7 +137,7 @@ function buildConcatenatedPrompt(execution: ConversationRecord, format: ConcatFo
|
||||
turns.map((t) => ({
|
||||
turn: t.turn,
|
||||
timestamp: t.timestamp,
|
||||
prompt: t.prompt,
|
||||
prompt: ensureString(t.prompt),
|
||||
response: t.output.stdout || '',
|
||||
})),
|
||||
null,
|
||||
@@ -212,7 +209,7 @@ function TurnSection({ turn, isLatest, isExpanded, onToggle }: TurnSectionProps)
|
||||
{formatMessage({ id: 'cli-manager.streamPanel.userPrompt' })}
|
||||
</h4>
|
||||
<pre className="p-3 bg-muted/50 rounded-lg text-sm whitespace-pre-wrap overflow-x-auto font-mono leading-relaxed">
|
||||
{turn.prompt}
|
||||
{ensureString(turn.prompt)}
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
@@ -462,7 +459,7 @@ function NativeTurnCard({ turn, isLatest, isExpanded, onToggle }: NativeTurnCard
|
||||
<div className="p-4 space-y-3">
|
||||
{turn.content && (
|
||||
<pre className="p-3 bg-background/50 rounded-lg text-sm whitespace-pre-wrap overflow-x-auto font-mono leading-relaxed max-h-80 overflow-y-auto">
|
||||
{turn.content}
|
||||
{ensureString(turn.content)}
|
||||
</pre>
|
||||
)}
|
||||
|
||||
|
||||
@@ -198,7 +198,9 @@ export function ConversationCard({
|
||||
|
||||
{/* Prompt preview */}
|
||||
<p className="text-sm text-foreground line-clamp-2 mb-2">
|
||||
{execution.prompt_preview}
|
||||
{typeof execution.prompt_preview === 'string'
|
||||
? execution.prompt_preview
|
||||
: JSON.stringify(execution.prompt_preview)}
|
||||
</p>
|
||||
|
||||
{/* Meta info */}
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
} from '@/components/ui/Dialog';
|
||||
import { useNativeSession } from '@/hooks/useNativeSession';
|
||||
import { SessionTimeline } from './SessionTimeline';
|
||||
import { getToolVariant } from '@/lib/cli-tool-theme';
|
||||
|
||||
// ========== Types ==========
|
||||
|
||||
@@ -35,19 +36,6 @@ export interface NativeSessionPanelProps {
|
||||
|
||||
// ========== Helpers ==========
|
||||
|
||||
/**
|
||||
* Get badge variant for tool name
|
||||
*/
|
||||
function getToolVariant(tool: string): 'default' | 'secondary' | 'outline' | 'success' | 'warning' | 'info' {
|
||||
const variants: Record<string, 'default' | 'secondary' | 'outline' | 'success' | 'warning' | 'info'> = {
|
||||
gemini: 'info',
|
||||
codex: 'success',
|
||||
qwen: 'warning',
|
||||
opencode: 'secondary',
|
||||
};
|
||||
return variants[tool?.toLowerCase()] || 'secondary';
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate a string to a max length with ellipsis
|
||||
*/
|
||||
|
||||
@@ -54,6 +54,15 @@ interface ToolCallPanelProps {
|
||||
|
||||
// ========== Helpers ==========
|
||||
|
||||
/**
|
||||
* Ensure content is a string (handle legacy object data like {text: "..."})
|
||||
*/
|
||||
function ensureString(value: unknown): string {
|
||||
if (typeof value === 'string') return value;
|
||||
if (value && typeof value === 'object') return JSON.stringify(value);
|
||||
return String(value ?? '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Format token number with compact notation
|
||||
*/
|
||||
@@ -319,7 +328,7 @@ function TurnNode({ turn, isLatest, isLast }: TurnNodeProps) {
|
||||
{turn.content && (
|
||||
<div className="p-4">
|
||||
<pre className="text-sm whitespace-pre-wrap overflow-x-auto font-mono leading-relaxed max-h-64 overflow-y-auto">
|
||||
{turn.content}
|
||||
{ensureString(turn.content)}
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user