From 2c18e7e79ec2ba235d71de7fb04c18a9f435cd70 Mon Sep 17 00:00:00 2001 From: jiajia Date: Wed, 8 Apr 2026 11:58:23 +0800 Subject: [PATCH] Document stable media URL behavior --- README.md | 8 +++ docs/project-relationship.md | 6 +- docs/stable-media-url-v1.md | 129 +++++++++++++++++++++++++++++++++++ 3 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 docs/stable-media-url-v1.md diff --git a/README.md b/README.md index 6720b96..06b4cc6 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,8 @@ See [docs/project-relationship.md](./docs/project-relationship.md). Persistence details for the current local backend are documented in [docs/persistence.md](./docs/persistence.md). +Stable media URL behavior and rollout notes are documented in +[docs/stable-media-url-v1.md](./docs/stable-media-url-v1.md). This note explains how `popiartcli`, `popiartServer`, and `PopiNewAPI` split responsibilities, and why routing, billing attribution, and provider access belong in the backend layers rather than the CLI. @@ -49,6 +51,8 @@ Use it from `popiartcli`: cd /Users/jiajia/popiartcli go run ./cmd/popiart --endpoint http://127.0.0.1:8080/v1 auth login --key go run ./cmd/popiart --endpoint http://127.0.0.1:8080/v1 skills list +go run ./cmd/popiart --endpoint http://127.0.0.1:8080/v1 media upload ./source.png +go run ./cmd/popiart --endpoint http://127.0.0.1:8080/v1 artifacts upload ./source.png --role source ``` Optional skillhub source: @@ -64,6 +68,10 @@ In local development, `/tmp/Popiart_skillhub` is used automatically when it exis - Local persistence is intentionally thin: `sessions`, `job refs`, and project route overrides live in SQLite. - The local backend now keeps a lightweight media store under `POPIART_DATA_DIR/media/` and exposes stable `media` URLs for uploaded files and persisted job outputs. - `artifact` metadata is still derived from `result_refs_json`, but new artifacts bind to a local `media_id` and a stable `url`. +- New endpoints now include: + - `POST /v1/media/upload` + - `GET /v1/media/:id` + - `GET /v1/media/:id/content` - `job logs` are synthesized from job state transitions instead of being stored as a separate table. - The local development backend verifies your login key against the local `PopiNewAPI`. - This server is a development backend, not the final production architecture. diff --git a/docs/project-relationship.md b/docs/project-relationship.md index af944ef..1d88414 100644 --- a/docs/project-relationship.md +++ b/docs/project-relationship.md @@ -9,7 +9,7 @@ | 项目 | 角色 | 应该负责 | 不应该负责 | |---|---|---|---| | `popiartcli` | 面向 coding agent 和创作者的统一 CLI 入口 | 登录、发现 skill、调用 skill、查看 jobs、拉取 artifacts、本地配置 | 不直接持有上游 provider key,不直接做模型路由,不直接做供应商计费 | -| `popiartServer` | PopiArt 产品后端 | 用户鉴权、项目权限、skill 注册表聚合、skill 执行、job 引用管理、artifact read-through、路由决策、计费归因 | 不把供应商细节暴露给 CLI,不把 skillhub 直接耦合到 CLI,不重复实现 `PopiNewAPI` 已有的模型网关能力 | +| `popiartServer` | PopiArt 产品后端 | 用户鉴权、项目权限、skill 注册表聚合、skill 执行、job 引用管理、artifact 与 media 管理、稳定 URL 生成、路由决策、计费归因 | 不把供应商细节暴露给 CLI,不把 skillhub 直接耦合到 CLI,不重复实现 `PopiNewAPI` 已有的模型网关能力 | | `PopiNewAPI` | 模型网关和通道管理层 | 管理上游渠道和 key、代理模型请求、记录原始用量、提供模型层能力 | 不承载 PopiArt 的 skill 业务语义,不负责 CLI 交互,不负责产品级项目上下文 | ## 标准调用链路 @@ -58,7 +58,8 @@ GitHub skillhub / skillhub.popi.art - 上游 provider key 管理:`PopiNewAPI` - 原始模型调用计量:`PopiNewAPI` - 面向 skill / project / user 的计费归因:`popiartServer` -- artifact 文件存储与 task 内容代理:优先复用 `PopiNewAPI`,`popiartServer` 只做 read-through +- artifact 与 media 的产品级持久化、稳定 URL 和生命周期:`popiartServer` +- provider 专属任务代理、task 内容代理与供应商差异适配:`PopiNewAPI` 一个重要原则是: @@ -96,6 +97,7 @@ CLI 只拿产品层 key;后端再用自己的方式调用 `PopiNewAPI`。 - 图生图 - 图生视频 +- 稳定媒体 URL 与 artifact/media 复用 - 更多供应商和项目级路由覆盖 ## 什么时候改哪个仓库 diff --git a/docs/stable-media-url-v1.md b/docs/stable-media-url-v1.md new file mode 100644 index 0000000..b5edc16 --- /dev/null +++ b/docs/stable-media-url-v1.md @@ -0,0 +1,129 @@ +# popiartServer Stable Media URL V1 + +这份文档只描述 `popiartServer` 这一层为了支持稳定媒体 URL 所做的职责扩展,不覆盖 `popiartcli` 的命令面,也不要求修改 `PopiNewAPI` 的现有通道实现。 + +## 背景 + +原有本地开发版 `popiartServer` 更偏向: + +- `artifact` read-through +- `result_refs_json` 存 `data_url` 或上游临时 `url` +- `GET /artifacts/:id/content` 由 server 当场去解码或代理下载 + +这种实现能跑通基本流程,但不适合多模态 skill 的复用: + +- `img2img` 经常还要重新拉流或转 base64 +- 上游签名 URL 可能过期 +- 前一个 job 的输出不能稳定地作为下一个 job 的 URL 输入 + +## V1 目标 + +`popiartServer` 在不依赖外部对象存储的前提下,先提供一套本地可运行的稳定媒体 URL 能力: + +- 本地上传文件可立即获得稳定 URL +- 新生成的 job 结果会被 re-host 到本地 media store +- 新 artifact 会携带 `media_id` 和 `url` +- `image2video` 的 `vidu*` 路由可以直接复用 artifact URL + +## 新接口 + +### `POST /v1/media/upload` + +上传一个本地文件,直接创建 media 记录并返回: + +- `id` +- `project_id` +- `filename` +- `content_type` +- `size_bytes` +- `created_at` +- `url` +- `visibility` +- `sha256` + +### `GET /v1/media/:id` + +读取 media 元数据。这个接口需要登录态,并要求 media 属于当前用户。 + +### `GET /v1/media/:id/content` + +读取稳定内容 URL。这个接口默认允许匿名 GET,以便模型提供商可以直接 fetch。 + +## Artifact 行为变化 + +`POST /v1/artifacts/upload` 现在不再只把内容塞成 `data_url`: + +1. server 先把上传文件写入本地 media store +2. 再创建本地 upload job +3. 最终 artifact 返回: + - `id` + - `media_id` + - `url` + - `visibility` + - `sha256` + - `storage_status` + +## Job 结果行为变化 + +对于新的 `text2image`、`img2img`、`image2video` 结果: + +1. 先从 `PopiNewAPI` 或上游临时结果读出真实内容 +2. re-host 到 `POPIART_DATA_DIR/media/` +3. 把 `local_path + media_id + stable url` 写回 `result_refs_json` + +这样新的 artifact 不再依赖上游临时 URL。 + +## 本地 media store + +V1 的本地开发版不引入 S3/R2/OSS,而是用本地文件系统: + +- blob 文件:`POPIART_DATA_DIR/media/blobs/` +- metadata JSON:`POPIART_DATA_DIR/media/meta/` + +如果 `POPIART_DATA_DIR` 没显式配置,但 `POPIART_SQLITE_PATH` 已配置,则 `DataDir` 会自动落到 SQLite 所在目录,避免测试把 media 文件写进源码目录。 + +## 当前已打通的 URL 复用 + +### Artifact / media + +- `media upload` -> stable URL +- `artifact upload` -> stable URL +- 新 `artifact` 的 `GET /artifacts/:id` -> `url` + +### Runtime output + +- `text2image` 输出会 re-host +- `img2img` 输出会 re-host +- `image2video` 输出会 re-host + +### URL-first dispatch + +当前已优先 URL 化的路径是: + +- `video.image2video` +- 当模型 ID 以 `vidu` 开头,且 reference 已有 stable URL 时,server 会优先用 JSON `images` 传给 `/v1/videos` + +这是为了先覆盖当前测试环境最常用的 `viduq2-pro-fast` 路由。 + +## 兼容策略 + +旧数据仍然兼容: + +- 如果 `result_ref.kind == data_url`,继续解码读取 +- 如果 `result_ref.kind == url`,继续代理下载 +- 如果 `result_ref.kind == local_path`,优先从本地 media store 读取 + +也就是说: + +- 历史 artifact 不会立即失效 +- 新 artifact 才开始享受稳定 URL + +## 后续阶段 + +后续如果要走生产化路线,可以保持接口不变,只把存储实现替换成对象存储: + +- 本地 `media/blobs/` -> S3 / R2 / OSS / COS +- metadata JSON -> 独立 media table 或对象存储元数据 +- `GET /v1/media/:id/content` -> CDN / 受控稳定 URL + +但这一步不是 V1 本地开发验证的前置条件。