Files
bw-expo-app/lib/api/template-runs.ts
imeepos 26cd0139bf feat: 完整的生成流程 - 登录检查、文件上传、支付凭证验证
主要功能:
1.  添加 canGenerate() - 检查登录、metered 订阅、余额
2.  添加 uploadBlobFiles() - 自动上传所有 blob URL 到服务器
3.  支付凭证强制验证 - identifier 缺失时报错"支付失败"
4.  新增 lib/api/upload.ts - 文件上传 API
5.  更新 useTemplateRun hook 支持 identifier 参数

完整流程(7步骤):
  1. 表单验证
  2. canGenerate 检查(登录 + 订阅 + 余额)
  3. 上传 blob 文件到服务器
  4. recordTokenUsage 扣费并获取 identifier
  5. 验证所有 URL 都是服务器 URL
  6. 转换数据格式
  7. runTemplate(id, data, identifier) 创建任务

安全保障:
-  无 identifier → 抛出错误"支付凭证缺失"
-  blob URL 未上传 → 报错"存在未上传的文件"
-  余额不足 → 引导用户充值

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-11 18:51:31 +08:00

68 lines
1.7 KiB
TypeScript

import { apiClient } from './client';
import { RunTemplateResponse, TemplateGenerationResponse, RunTemplateData } from '../types/template-run';
export async function runTemplate(
templateId: string,
data: RunTemplateData,
identifier: string
): Promise<RunTemplateResponse> {
// identifier 是必需的支付凭证,如果没有提供说明支付失败
if (!identifier) {
throw new Error('支付凭证缺失,无法创建生成任务');
}
return apiClient<RunTemplateResponse>(`/api/templates/${templateId}/run`, {
method: 'POST',
body: JSON.stringify({
data,
identifier,
}),
});
}
export async function getTemplateGeneration(generationId: string): Promise<TemplateGenerationResponse> {
return apiClient<TemplateGenerationResponse>(`/api/template-generations/${generationId}`);
}
export async function pollTemplateGeneration(
generationId: string,
onComplete: (result: any) => void,
onError: (error: Error) => void,
maxAttempts: number = 100,
intervalMs: number = 3000
): Promise<void> {
let attempts = 0;
const poll = async () => {
try {
attempts++;
if (attempts > maxAttempts) {
throw new Error('轮询超时,请稍后重试');
}
const response = await getTemplateGeneration(generationId);
const generation = response.data;
// 检查是否完成
if (generation.status === 'completed') {
onComplete(generation);
return;
}
// 检查是否失败
if (generation.status === 'failed') {
throw new Error('任务执行失败');
}
// 继续轮询
setTimeout(poll, intervalMs);
} catch (error) {
onError(error as Error);
}
};
// 开始轮询
setTimeout(poll, intervalMs);
}