/** * 乐读派 AI 创作系统 — C 端 API 封装 * * 调用流程: * 1. B1 校验额度 → 2. A1/A2/A3 创作 → 3. 进度追踪 → 4. B2 获取结果 * * 所有 API 均需 HMAC-SHA256 签名(除 B6 画风列表) */ import { generateSignHeaders } from './HmacSigner.js' export default class CreationApi { /** * @param {string} serverUrl - 服务器地址 * @param {string} appKey - 机构 App Key (orgId) * @param {string} appSecret - 机构密钥 */ constructor(serverUrl, appKey, appSecret) { this.serverUrl = serverUrl this.appKey = appKey this.appSecret = appSecret } // ─── 通用请求方法 ───────────────────────────────────────────────── async _get(path, queryParams = {}) { const signHeaders = generateSignHeaders(this.appKey, this.appSecret, queryParams) const qs = Object.entries(queryParams).map(([k, v]) => `${k}=${encodeURIComponent(v)}`).join('&') const url = `${this.serverUrl}${path}${qs ? '?' + qs : ''}` const res = await fetch(url, { method: 'GET', headers: { ...signHeaders, 'Content-Type': 'application/json' } }) return this._handleResponse(res) } async _post(path, body, queryParams = {}) { // POST 的 JSON body 不参与签名,只有 query params 参与 const signHeaders = generateSignHeaders(this.appKey, this.appSecret, queryParams) const qs = Object.entries(queryParams).map(([k, v]) => `${k}=${encodeURIComponent(v)}`).join('&') const url = `${this.serverUrl}${path}${qs ? '?' + qs : ''}` const res = await fetch(url, { method: 'POST', headers: { ...signHeaders, 'Content-Type': 'application/json' }, body: JSON.stringify(body) }) return this._handleResponse(res) } async _handleResponse(res) { if (!res.ok) { throw new Error(`HTTP ${res.status}: ${res.statusText}`) } const json = await res.json() if (json.code !== 200) { const err = new Error(json.msg || 'API Error') err.code = json.code throw err } return json.data } // ─── B 类接口(查询) ───────────────────────────────────────────── /** * B1 - 创作请求校验(检查额度是否充足) * @param {string} orgId * @param {string} phone * @returns {Promise<{valid: boolean, reason?: string, remainQuota?: number}>} */ async validate(orgId, phone) { return this._post('/api/v1/query/validate', { orgId, phone }) } /** * B2 - 查询作品详情(含实时进度) * @param {string} workId * @returns {Promise} * * CreationResultVO 结构: * { * workId, status, title, author, pages, style, * videoStatus, videoUrl, failReason, durationMs, retryCount, * progress, // 0-100 进度百分比,-1 表示失败 * progressMessage, // "正在绘制第3/7页..." * pageList: [{pageNum, text, imageUrl, audioUrl}] // 仅 COMPLETED 时有值 * } */ async getWork(workId) { return this._get(`/api/v1/query/work/${workId}`, { orgId: this.appKey }) } /** * B3 - 作品列表 */ async listWorks(phone, page = 1, pageSize = 20) { return this._get('/api/v1/query/works', { orgId: this.appKey, phone, page, pageSize }) } /** * B6 - 画风列表(无需签名) */ async getStyles() { const res = await fetch(`${this.serverUrl}/api/v1/query/styles`) return this._handleResponse(res) } // ─── A 类接口(创作) ───────────────────────────────────────────── /** * A1 - 一句话创作 * @param {Object} params * @param {string} params.orgId * @param {string} params.phone * @param {string} params.sentence - 故事描述 * @param {string} params.style - 画风标识 (如 style_cartoon) * @param {boolean} [params.enableVoice=false] - 是否配音 * @param {string} [params.author] - 作者名 * @param {number} [params.age] - 适龄 * @returns {Promise<{workId: string, status: string}>} */ async createBySentence(params) { return this._post('/api/v1/creation/one-sentence', params) } /** * A2 - 四要素创作 * @param {Object} params * @param {string} params.orgId * @param {string} params.phone * @param {string} params.time - 时间 * @param {string} params.place - 地点 * @param {string} params.character - 角色 * @param {string} params.event - 事件 * @param {string} params.style * @param {boolean} [params.enableVoice=false] * @returns {Promise<{workId: string, status: string}>} */ async createByElements(params) { return this._post('/api/v1/creation/scene-elements', params) } /** * A3 - 图片故事创作 * @param {Object} params * @param {string} params.orgId * @param {string} params.phone * @param {string} params.imageUrl - 参考图片 URL * @param {string} [params.storyHint] - 故事方向提示 * @param {string} params.style * @param {boolean} [params.enableVoice=false] * @param {string} [params.heroCharId] - 主角 charId (来自 A5/A6) * @param {Array<{charId, imageUrl, name}>} [params.characterRefs] - 角色引用列表 * @returns {Promise<{workId: string, status: string}>} */ async createByImage(params) { return this._post('/api/v1/creation/image-story', params) } /** * A4 - 视频合成 * @param {Object} params * @param {string} params.orgId * @param {string} params.phone * @param {string} params.workId * @returns {Promise<{workId: string}>} */ async createVideo(params) { return this._post('/api/v1/creation/video', params) } }