Files
Atlas/web/src/composables/useImageStudio.ts
T
renjue d440fb1ae7
CI / docker (push) Has been cancelled
实现 Atlas AI 平台一期能力并完成品牌化改造。
引入用户端对话/生图与管理端 Provider 配置,统一 invoke 编排、Gateway、请求日志与模型管理;前端按设计规范拆分布局,Go module 更名为 github.com/rose_cat707/Atlas;生图长任务 SSE 推送;管理端登录防爆破;协议与服务类型联动;Replicate 模型同步修复;用户端创作台 UI 重构;管理端创作历史替代请求日志。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-29 22:36:49 +08:00

101 lines
2.9 KiB
TypeScript

import { computed, onMounted, onUnmounted, ref } from 'vue'
import api from '@/api/client'
import { useUserMeta, type ModelInfo } from '@/composables/useUserMeta'
import type { TaskItem } from '@/composables/useInvoke'
const serviceType = 'image_generation'
const legacyImageServiceTypes = ['text_to_image', 'image_to_image'] as const
const terminalStatuses = new Set(['succeeded', 'failed', 'canceled'])
export function useImageStudio() {
const { load, getService } = useUserMeta()
const activeTasks = ref<TaskItem[]>([])
const streams = new Map<number, EventSource>()
const models = computed(() => (getService(serviceType)?.models ?? []) as ModelInfo[])
const defaultModel = computed(() => {
const id = getService(serviceType)?.default_model
if (id && models.value.some((m) => m.id === id)) return id
return models.value[0]?.id ?? ''
})
function upsertTask(task: TaskItem) {
const idx = activeTasks.value.findIndex((t) => t.id === task.id)
if (idx >= 0) activeTasks.value[idx] = task
else activeTasks.value.push(task)
}
function closeStream(taskId: number) {
const es = streams.get(taskId)
if (es) {
es.close()
streams.delete(taskId)
}
}
function watchTask(taskId: number) {
if (streams.has(taskId)) return
const es = new EventSource(`/api/v1/user/tasks/${taskId}/events`, { withCredentials: true })
streams.set(taskId, es)
es.addEventListener('task', (ev) => {
const task = JSON.parse(ev.data) as TaskItem
upsertTask(task)
if (terminalStatuses.has(task.status)) closeStream(taskId)
})
es.addEventListener('done', () => closeStream(taskId))
es.onerror = () => closeStream(taskId)
}
async function loadActiveTasks() {
const types = [serviceType, ...legacyImageServiceTypes]
const responses = await Promise.all(
types.map((st) =>
api.get<{ items: TaskItem[] }>('/user/tasks/active', { params: { service_type: st } }),
),
)
const items = responses.flatMap((res) => res.data.items)
activeTasks.value = items
for (const task of items) watchTask(task.id)
}
async function generate(params: Record<string, unknown>, model: string) {
try {
const res = await api.post('/user/generate', {
service_type: serviceType,
params,
model,
})
if (res.data.task_id) {
watchTask(res.data.task_id)
await loadActiveTasks()
}
return res.data
} catch (err: unknown) {
const msg =
(err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? '生成请求失败'
throw new Error(msg)
}
}
onMounted(loadActiveTasks)
onUnmounted(() => {
streams.forEach((es) => es.close())
streams.clear()
})
return {
load,
models,
defaultModel,
activeTasks,
generate,
loadActiveTasks,
}
}
export function modelKindLabel(kind?: ModelInfo['kind']) {
return kind === 'image_to_image' ? '图生图' : '文生图'
}