agent-Specialization/static/src/stores/backgroundCommand.ts

202 lines
6.3 KiB
TypeScript

import { defineStore } from 'pinia';
import { useUiStore } from './ui';
interface BackgroundCommand {
command_id: string;
status?: string;
command?: string;
notice_pending?: boolean;
created_at?: number;
updated_at?: number;
finished_at?: number | null;
timeout?: number;
return_code?: number | null;
}
interface BackgroundCommandDetail extends BackgroundCommand {
output?: string;
message?: string;
}
interface BackgroundCommandState {
commands: BackgroundCommand[];
pollTimer: ReturnType<typeof setInterval> | null;
detailPollTimer: ReturnType<typeof setInterval> | null;
activeCommand: BackgroundCommand | null;
stoppingCommandIds: Record<string, boolean>;
activeDetail: BackgroundCommandDetail | null;
detailLoading: boolean;
detailError: string | null;
}
const TERMINAL_STATUSES = new Set(['completed', 'failed', 'timeout', 'cancelled']);
export const useBackgroundCommandStore = defineStore('backgroundCommand', {
state: (): BackgroundCommandState => ({
commands: [],
pollTimer: null,
detailPollTimer: null,
activeCommand: null,
stoppingCommandIds: {},
activeDetail: null,
detailLoading: false,
detailError: null
}),
actions: {
async fetchCommands() {
try {
const resp = await fetch('/api/background_commands?limit=200');
if (!resp.ok) {
throw new Error(await resp.text());
}
const data = await resp.json();
if (data.success) {
this.commands = Array.isArray(data.data) ? data.data : [];
const activeId = this.activeCommand?.command_id;
if (activeId) {
const latest = this.commands.find((item) => item.command_id === activeId);
if (latest) {
this.activeCommand = { ...latest };
}
}
}
} catch (error) {
console.error('获取后台指令列表失败:', error);
}
},
startPolling() {
if (this.pollTimer) {
return;
}
const uiStore = useUiStore();
this.pollTimer = setInterval(() => {
if (uiStore.panelMode === 'backgroundCommands') {
this.fetchCommands();
}
}, 5000);
},
stopPolling() {
if (this.pollTimer) {
clearInterval(this.pollTimer);
this.pollTimer = null;
}
},
openCommand(command: BackgroundCommand) {
if (!command || !command.command_id) {
return;
}
this.activeCommand = command;
this.activeDetail = null;
this.detailError = null;
this.fetchCommandDetail(command.command_id);
this.startDetailPolling();
},
closeCommand() {
this.stopDetailPolling();
this.activeCommand = null;
this.activeDetail = null;
this.detailError = null;
this.detailLoading = false;
},
startDetailPolling() {
if (this.detailPollTimer) {
return;
}
this.detailPollTimer = setInterval(() => {
const commandId = this.activeCommand?.command_id;
if (commandId) {
this.fetchCommandDetail(commandId);
}
}, 2000);
},
stopDetailPolling() {
if (this.detailPollTimer) {
clearInterval(this.detailPollTimer);
this.detailPollTimer = null;
}
},
async fetchCommandDetail(commandId: string) {
if (!commandId) return;
this.detailLoading = true;
try {
const resp = await fetch(`/api/background_commands/${encodeURIComponent(commandId)}`);
if (!resp.ok) {
throw new Error(await resp.text());
}
const data = await resp.json();
if (data && data.success && data.data) {
this.activeDetail = data.data;
const current = this.commands.find((item) => item.command_id === commandId);
if (current) {
Object.assign(current, data.data);
}
if (this.activeCommand && this.activeCommand.command_id === commandId) {
this.activeCommand = {
...this.activeCommand,
...data.data
};
}
const status = (data.data.status || '').toString();
if (TERMINAL_STATUSES.has(status)) {
this.stopDetailPolling();
}
}
} catch (error: any) {
this.detailError = error?.message || String(error);
console.error('获取后台指令详情失败:', error);
} finally {
this.detailLoading = false;
}
},
async cancelCommand(commandId: string) {
const normalizedId = (commandId || '').toString().trim();
if (!normalizedId) {
return { success: false, error: 'command_id 不能为空' };
}
this.stoppingCommandIds = {
...this.stoppingCommandIds,
[normalizedId]: true
};
try {
const resp = await fetch(
`/api/background_commands/${encodeURIComponent(normalizedId)}/cancel`,
{ method: 'POST' }
);
const data = await resp.json().catch(() => ({}));
if (!resp.ok || !data?.success) {
throw new Error(data?.error || `HTTP ${resp.status}`);
}
await this.fetchCommands();
if (this.activeCommand?.command_id === normalizedId) {
await this.fetchCommandDetail(normalizedId);
}
return { success: true, data: data?.data || null };
} catch (error: any) {
const message = error?.message || String(error);
return { success: false, error: message };
} finally {
const next = { ...this.stoppingCommandIds };
delete next[normalizedId];
this.stoppingCommandIds = next;
}
},
async stopAllCommands(conversationId?: string): Promise<{ success: boolean; stoppedCount?: number; error?: string }> {
try {
const resp = await fetch('/api/background_commands/stop_all', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(conversationId ? { conversation_id: conversationId } : {})
});
const data = await resp.json().catch(() => ({}));
if (!resp.ok || !data?.success) {
throw new Error(data?.error || `HTTP ${resp.status}`);
}
await this.fetchCommands();
return { success: true, stoppedCount: data?.data?.stopped_count || 0 };
} catch (error: any) {
return { success: false, error: error?.message || String(error) };
}
}
}
});