You've already forked agentic-coding-workflow
178 lines
5.8 KiB
TypeScript
178 lines
5.8 KiB
TypeScript
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<Workspace[]>([])
|
|
const current = ref<Workspace | null>(null)
|
|
const worktrees = ref<Worktree[]>([])
|
|
const credential = ref<CredentialMeta | null>(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<Workspace> {
|
|
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<FileStatus[]> {
|
|
return workspacesApi.statusOnMain(wsID)
|
|
}
|
|
async function statusOnWorktree(wtID: string): Promise<FileStatus[]> {
|
|
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,
|
|
}
|
|
})
|