主要功能: 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>
68 lines
1.7 KiB
TypeScript
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);
|
|
} |