| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481 |
- import { defineStore } from 'pinia'
- import { computed, reactive, readonly, ref } from 'vue'
- import { dayjs } from 'element-plus'
- export type RunnerStatus = 'idle' | 'connecting' | 'running' | 'finished' | 'error' | 'suspended'
- export type NodeStatus = 'idle' | 'running' | 'success' | 'failed' | 'suspended'
- export interface RunnerNodeInfo {
- nodeId: string
- nodeName: string
- nodeType: string
- }
- export interface RunnerNodeState extends RunnerNodeInfo {
- status: NodeStatus
- lastUpdateTime?: string
- track?: any
- }
- export interface RunnerExecution {
- runnerKey: string
- status: RunnerStatus
- startedAt: string | null
- finishedAt: string | null
- nodes: RunnerNodeState[]
- result?: any
- }
- interface AgentRunnerMessageBase {
- cmd: string
- time?: string
- }
- interface ConnectErrorMessage extends AgentRunnerMessageBase {
- cmd: 'CMD_CONNECT_ERROR_MSG'
- errorMsg: string
- }
- interface WelcomeMessage extends AgentRunnerMessageBase {
- cmd: 'CMD_WELCOME_MSG'
- }
- interface HeartbeatMessage extends AgentRunnerMessageBase {
- cmd: 'CMD_HEARTBEAT_MSG'
- }
- interface AgentRunningMessage extends AgentRunnerMessageBase {
- cmd: 'CMD_AGENT_RUNNING_MSG'
- }
- interface NodeRunningMessage extends AgentRunnerMessageBase {
- cmd: 'CMD_NODE_RUNNING_MSG'
- node: RunnerNodeInfo
- }
- interface NodeFinishMessage extends AgentRunnerMessageBase {
- cmd: 'CMD_NODE_FINISH_MSG'
- node: RunnerNodeInfo
- track: any
- }
- interface NodeIterationRunningMessage extends AgentRunnerMessageBase {
- cmd: 'CMD_NODE_ITERATION_RUNNING_MSG'
- count: number
- node: RunnerNodeInfo
- }
- interface NodeIterationStepMessage extends AgentRunnerMessageBase {
- cmd: 'CMD_NODE_ITERATION_STEP_MSG'
- count: number
- index: number
- node: RunnerNodeInfo
- }
- interface NodeIterationFinishMessage extends AgentRunnerMessageBase {
- cmd: 'CMD_NODE_ITERATION_FINISH_MSG'
- node: RunnerNodeInfo
- }
- interface AgentFinishMessage extends AgentRunnerMessageBase {
- cmd: 'CMD_AGENT_FINISH_MSG'
- result: any
- }
- interface AgentErrorMessage extends AgentRunnerMessageBase {
- cmd: 'CMD_AGENT_ERROR_MSG'
- errorMsg: string
- }
- interface AgentSuspendMessage extends AgentRunnerMessageBase {
- cmd: 'CMD_AGENT_SUSPEND_MSG'
- }
- export type AgentRunnerMessage =
- | ConnectErrorMessage
- | WelcomeMessage
- | HeartbeatMessage
- | AgentRunningMessage
- | NodeRunningMessage
- | NodeFinishMessage
- | NodeIterationRunningMessage
- | NodeIterationStepMessage
- | NodeIterationFinishMessage
- | AgentFinishMessage
- | AgentErrorMessage
- | AgentSuspendMessage
- const AGENT_RUNNER_WS_BASE = `wss://${import.meta.env.VITE_BASE_URL}/api/ws/agentRunner`
- export const useRunnerStore = defineStore('runner', () => {
- const currentRunnerKey = ref<string | null>(null)
- const currentStartNodeId = ref<string | null>(null)
- const status = ref<RunnerStatus>('idle')
- const errorMsg = ref<string | null>(null)
- const connected = ref(false)
- const lastHeartbeatAt = ref<number | null>(null)
- const agentResult = ref<any>(null)
- const nodesMap = reactive<Record<string, RunnerNodeState>>({})
- const executions = ref<RunnerExecution[]>([])
- const socket = ref<WebSocket | null>(null)
- const heartbeatTimer = ref<number | null>(null)
- const nodes = computed(() => Object.values(nodesMap))
- const getCurrentExecution = () => {
- if (!currentRunnerKey.value) return null
- return executions.value.find((item) => item.runnerKey === currentRunnerKey.value) || null
- }
- const clearHeartbeat = () => {
- if (heartbeatTimer.value) {
- window.clearInterval(heartbeatTimer.value)
- heartbeatTimer.value = null
- }
- }
- const resetState = () => {
- status.value = 'idle'
- errorMsg.value = null
- connected.value = false
- lastHeartbeatAt.value = null
- agentResult.value = null
- Object.keys(nodesMap).forEach((key) => {
- delete nodesMap[key]
- })
- }
- const closeSocket = () => {
- clearHeartbeat()
- if (socket.value) {
- try {
- socket.value.close()
- } catch {
- // ignore
- }
- socket.value = null
- }
- }
- const updateNodeState = (info: RunnerNodeInfo, partial: Partial<RunnerNodeState>) => {
- const existing = nodesMap[info.nodeId]
- const nextState: RunnerNodeState = {
- nodeId: info.nodeId,
- nodeName: info.nodeName,
- nodeType: info.nodeType,
- status: existing?.status ?? 'idle',
- ...existing,
- ...partial
- }
- nodesMap[info.nodeId] = nextState
- const execution = getCurrentExecution()
- if (execution) {
- const index = execution.nodes.findIndex((item) => item.nodeId === info.nodeId)
- if (index === -1) {
- execution.nodes.push({ ...nextState })
- } else {
- execution.nodes[index] = { ...nextState }
- }
- }
- }
- const handleMessage = (raw: MessageEvent<string>) => {
- let data: AgentRunnerMessage | null = null
- try {
- data = JSON.parse(raw.data)
- } catch {
- return
- }
- if (!data || typeof data !== 'object' || !('cmd' in data)) return
- switch (data.cmd) {
- /**
- * 连接运行器失败
- */
- case 'CMD_CONNECT_ERROR_MSG': {
- const msg = data as ConnectErrorMessage
- status.value = 'error'
- errorMsg.value = msg.errorMsg || '连接运行器失败'
- connected.value = false
- const execution = getCurrentExecution()
- if (execution) {
- execution.status = 'error'
- execution.finishedAt = msg.time || new Date().toISOString()
- }
- closeSocket()
- break
- }
- /**
- * 连接运行器成功
- */
- case 'CMD_WELCOME_MSG': {
- status.value = 'running'
- connected.value = true
- const execution = getCurrentExecution()
- if (execution) {
- execution.status = 'running'
- }
- break
- }
- /**
- * 心跳消息
- */
- case 'CMD_HEARTBEAT_MSG': {
- lastHeartbeatAt.value = Date.now()
- break
- }
- /**
- * 智能体运行消息
- */
- case 'CMD_AGENT_RUNNING_MSG': {
- status.value = 'running'
- const execution = getCurrentExecution()
- if (execution) {
- execution.status = 'running'
- }
- break
- }
- /**
- * 节点运行消息
- */
- case 'CMD_NODE_RUNNING_MSG': {
- const msg = data as NodeRunningMessage
- updateNodeState(msg.node, {
- status: 'running',
- lastUpdateTime: msg.time
- })
- break
- }
- /**
- * 节点运行完成消息
- */
- case 'CMD_NODE_FINISH_MSG': {
- const msg = data as NodeFinishMessage
- const isSuccess = !!msg.track?.is_success
- updateNodeState(msg.node, {
- status: isSuccess ? 'success' : 'failed',
- lastUpdateTime: msg.time,
- track: msg.track
- })
- break
- }
- /**
- * 节点迭代运行消息
- */
- case 'CMD_NODE_ITERATION_RUNNING_MSG': {
- const msg = data as NodeIterationRunningMessage
- updateNodeState(msg.node, {
- status: 'running',
- lastUpdateTime: msg.time
- })
- break
- }
- /**
- * 节点迭代运行步骤消息
- */
- case 'CMD_NODE_ITERATION_STEP_MSG': {
- const msg = data as NodeIterationStepMessage
- updateNodeState(msg.node, {
- status: 'running',
- lastUpdateTime: msg.time
- })
- break
- }
- /**
- * 节点迭代运行完成消息
- */
- case 'CMD_NODE_ITERATION_FINISH_MSG': {
- const msg = data as NodeIterationFinishMessage
- updateNodeState(msg.node, {
- status: 'success',
- lastUpdateTime: msg.time
- })
- break
- }
- /**
- * 智能体运行完成消息
- */
- case 'CMD_AGENT_FINISH_MSG': {
- const msg = data as AgentFinishMessage
- status.value = 'finished'
- agentResult.value = msg.result
- const execution = getCurrentExecution()
- if (execution) {
- execution.status = 'finished'
- execution.finishedAt = msg.time || new Date().toISOString()
- execution.result = msg.result
- }
- closeSocket()
- break
- }
- /**
- * 智能体运行异常失败
- */
- case 'CMD_AGENT_ERROR_MSG': {
- const msg = data as AgentErrorMessage
- status.value = 'error'
- agentResult.value = msg.errorMsg || '智能体运行异常失败'
- const execution = getCurrentExecution()
- if (execution) {
- execution.status = 'finished'
- execution.finishedAt = msg.time || new Date().toISOString()
- execution.result = msg.errorMsg || '智能体运行异常失败'
- }
- closeSocket()
- break
- }
- /**
- * 智能体运行被挂起
- */
- case 'CMD_AGENT_SUSPEND_MSG': {
- status.value = 'suspended'
- const execution = getCurrentExecution()
- if (execution) {
- execution.status = 'suspended'
- }
- Object.values(nodesMap).forEach((node) => {
- if (node.status === 'running') {
- updateNodeState(node, {
- status: 'suspended',
- lastUpdateTime: data?.time || node.lastUpdateTime
- })
- }
- })
- break
- }
- default:
- break
- }
- }
- const startHeartbeat = () => {
- clearHeartbeat()
- heartbeatTimer.value = window.setInterval(() => {
- if (!socket.value || socket.value.readyState !== WebSocket.OPEN) return
- socket.value.send(JSON.stringify({ cmd: 'CMD_HEARTBEAT_MSG' }))
- }, 10000)
- }
- const startRunner = (runnerKey: string, startNodeId?: string) => {
- if (!runnerKey) return
- closeSocket()
- resetState()
- currentRunnerKey.value = runnerKey
- currentStartNodeId.value = startNodeId || null
- status.value = 'connecting'
- executions.value.unshift({
- runnerKey,
- status: 'connecting',
- startedAt: dayjs().format('YYYY-MM-DD HH:mm:ss'),
- finishedAt: null,
- nodes: [],
- result: undefined
- })
- const url = `${AGENT_RUNNER_WS_BASE}?agentRunnerKey=${encodeURIComponent(runnerKey)}`
- const ws = new WebSocket(url)
- socket.value = ws
- ws.onopen = () => {
- connected.value = true
- status.value = 'running'
- startHeartbeat()
- const execution = getCurrentExecution()
- if (execution) {
- execution.status = 'running'
- }
- }
- ws.onmessage = (event) => {
- handleMessage(event as MessageEvent<string>)
- }
- ws.onerror = () => {
- status.value = 'error'
- errorMsg.value = errorMsg.value || '运行器连接异常'
- const execution = getCurrentExecution()
- if (execution) {
- execution.status = 'error'
- if (!execution.finishedAt) {
- execution.finishedAt = dayjs().format('YYYY-MM-DD HH:mm:ss')
- }
- }
- }
- ws.onclose = () => {
- connected.value = false
- clearHeartbeat()
- if (status.value === 'running' || status.value === 'connecting') {
- status.value = 'finished'
- const execution = getCurrentExecution()
- if (execution && !execution.finishedAt) {
- execution.status = 'finished'
- execution.finishedAt = dayjs().format('YYYY-MM-DD HH:mm:ss')
- }
- }
- }
- }
- const stopRunner = () => {
- closeSocket()
- status.value = 'finished'
- const execution = getCurrentExecution()
- if (execution && !execution.finishedAt) {
- execution.status = 'finished'
- execution.finishedAt = dayjs().format('YYYY-MM-DD HH:mm:ss')
- }
- }
- const resetRunner = () => {
- closeSocket()
- currentRunnerKey.value = null
- currentStartNodeId.value = null
- resetState()
- }
- return {
- currentRunnerKey: readonly(currentRunnerKey),
- currentStartNodeId: readonly(currentStartNodeId),
- status,
- errorMsg,
- connected,
- lastHeartbeatAt,
- nodes,
- agentResult,
- executions,
- startRunner,
- stopRunner,
- resetRunner
- }
- })
|