import { defineStore } from 'pinia' import { ref } from 'vue' import { workspacesApi } from '@/api/workspaces' import type { Workspace, Worktree, FileStatus, CredentialMeta, CredentialUpsertBody, CreateWorkspaceBody, UpdateWorkspaceBody, CreateWorktreeBody, CommitBody, } from '@/api/types' const DEFAULT_POLL_INTERVAL_MS = 1500 const DEFAULT_POLL_MAX_MS = 5 * 60 * 1000 export const useWorkspacesStore = defineStore('workspaces', () => { const list = ref([]) const current = ref(null) const worktrees = ref([]) const credential = ref(null) const loading = ref(false) async function fetchByProject(projectSlug: string) { loading.value = true try { list.value = await workspacesApi.list(projectSlug) } finally { loading.value = false } } async function fetchOne(wsID: string) { current.value = await workspacesApi.get(wsID) } async function create(projectSlug: string, body: CreateWorkspaceBody) { const ws = await workspacesApi.create(projectSlug, body) list.value.unshift(ws) return ws } async function update(wsID: string, body: UpdateWorkspaceBody) { const ws = await workspacesApi.update(wsID, body) if (current.value?.id === wsID) current.value = ws // 若 wsID 不在 list 中(如深链直入 Detail 页未加载列表),map 静默 no-op;调用方按需 fetchByProject。 list.value = list.value.map((w) => (w.id === wsID ? ws : w)) return ws } async function remove(wsID: string) { await workspacesApi.delete(wsID) list.value = list.value.filter((w) => w.id !== wsID) if (current.value?.id === wsID) current.value = null } async function sync(wsID: string) { const ws = await workspacesApi.sync(wsID) if (current.value?.id === wsID) current.value = ws // 同 update:list 中无此 wsID 时 map 是 no-op,调用方负责拉列表。 list.value = list.value.map((w) => (w.id === wsID ? ws : w)) return ws } /** * 轮询 workspace.sync_status 直到落到 idle/error,用于 Phase 9 创建/Sync 后等克隆完成。 * * 调用方约定: * - 若已知 ws 在 list 中,调用前请确保该项已加载(否则 list.map 静默 no-op,但 current 仍会刷新)。 * - 失败抛出 Error,message 含最后一次观测到的 sync_status,便于 UI 区分超时类型。 * - 调用方应自行处理并发:同一 wsID 多次并发轮询不会共享请求。 * * @param wsID 目标 workspace 的 UUID。 * @param intervalMs 轮询间隔,默认 1500ms。 * @param maxMs 总超时上限,默认 5 分钟(覆盖中等仓库克隆耗时)。 */ async function pollUntilSettled( wsID: string, intervalMs = DEFAULT_POLL_INTERVAL_MS, maxMs = DEFAULT_POLL_MAX_MS, ): Promise { const start = Date.now() let lastStatus: Workspace['sync_status'] | 'unknown' = 'unknown' while (Date.now() - start < maxMs) { const ws = await workspacesApi.get(wsID) lastStatus = ws.sync_status if (current.value?.id === wsID) current.value = ws // 同 update:list 中无此 wsID 时 map 是 no-op,仍刷新 current.value。 list.value = list.value.map((w) => (w.id === wsID ? ws : w)) if (ws.sync_status !== 'cloning' && ws.sync_status !== 'syncing') return ws await new Promise((r) => setTimeout(r, intervalMs)) } throw new Error(`sync polling timed out for workspace ${wsID} (last status: ${lastStatus})`) } // ===== credential ===== async function fetchCredential(wsID: string) { credential.value = await workspacesApi.getCredential(wsID) } async function upsertCredential(wsID: string, body: CredentialUpsertBody) { credential.value = await workspacesApi.upsertCredential(wsID, body) } // ===== worktrees ===== async function fetchWorktrees(wsID: string) { worktrees.value = await workspacesApi.listWorktrees(wsID) } async function createWorktree(wsID: string, body: CreateWorktreeBody) { const wt = await workspacesApi.createWorktree(wsID, body) worktrees.value.unshift(wt) return wt } async function deleteWorktree(wtID: string) { await workspacesApi.deleteWorktree(wtID) worktrees.value = worktrees.value.filter((w) => w.id !== wtID) } async function acquire(wtID: string) { const wt = await workspacesApi.acquire(wtID) worktrees.value = worktrees.value.map((w) => (w.id === wtID ? wt : w)) return wt } async function release(wtID: string) { const wt = await workspacesApi.release(wtID) worktrees.value = worktrees.value.map((w) => (w.id === wtID ? wt : w)) return wt } // ===== git ops(直透 api,store 仅负责状态变化的端点;status/commit/push 不缓存)===== async function statusOnMain(wsID: string): Promise { return workspacesApi.statusOnMain(wsID) } async function statusOnWorktree(wtID: string): Promise { return workspacesApi.statusOnWorktree(wtID) } async function commitOnMain(wsID: string, body: CommitBody) { return workspacesApi.commitOnMain(wsID, body) } async function commitOnWorktree(wtID: string, body: CommitBody) { return workspacesApi.commitOnWorktree(wtID, body) } async function pushOnMain(wsID: string) { return workspacesApi.pushOnMain(wsID) } async function pushOnWorktree(wtID: string) { return workspacesApi.pushOnWorktree(wtID) } return { list, current, worktrees, credential, loading, fetchByProject, fetchOne, create, update, remove, sync, pollUntilSettled, fetchCredential, upsertCredential, fetchWorktrees, createWorktree, deleteWorktree, acquire, release, statusOnMain, statusOnWorktree, commitOnMain, commitOnWorktree, pushOnMain, pushOnWorktree, } })