202 lines
6.3 KiB
TypeScript
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) };
|
|
}
|
|
}
|
|
}
|
|
});
|