176 lines
5.8 KiB
JavaScript
176 lines
5.8 KiB
JavaScript
/**
|
|
* 乐读派 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>}
|
|
*
|
|
* 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)
|
|
}
|
|
}
|