feat(imitation): add article imitation create flow

Introduce 仿写创作: new list and create views, backend imitation service
and prompt templates, worker task routing for imitation jobs, and one-click
rewrite triggers from question citation sources. Surface source article
URL/title on article list/detail, and restrict failed articles to delete-only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-25 21:36:44 +08:00
parent 46d0a7aea0
commit 40d9a6cc63
29 changed files with 2678 additions and 120 deletions
+8
View File
@@ -7,6 +7,8 @@ import type {
ArticleListResponse,
CreateArticleRequest,
ArticleVersion,
GenerateImitationRequest,
GenerateImitationResponse,
AuthTokens,
Brand,
BrandLibrarySummary,
@@ -559,6 +561,12 @@ export const articlesApi = {
create(payload: CreateArticleRequest = {}) {
return apiClient.post<ArticleDetail, CreateArticleRequest>("/api/tenant/articles", payload);
},
generateImitation(payload: GenerateImitationRequest) {
return apiClient.post<GenerateImitationResponse, GenerateImitationRequest>(
"/api/tenant/articles/imitations/generate",
payload,
);
},
detail(id: number) {
return apiClient.get<ArticleDetail>(`/api/tenant/articles/${id}`);
},
+11 -1
View File
@@ -19,7 +19,7 @@ export function resolveArticleActionState(generateStatus?: string | null): Artic
};
}
if (generateStatus === "draft" || generateStatus === "failed") {
if (generateStatus === "draft") {
return {
showPublish: false,
showEdit: true,
@@ -29,6 +29,16 @@ export function resolveArticleActionState(generateStatus?: string | null): Artic
};
}
if (generateStatus === "failed") {
return {
showPublish: false,
showEdit: false,
showPreview: false,
showCopy: false,
showDelete: true,
};
}
return {
showPublish: false,
showEdit: false,
+3
View File
@@ -77,6 +77,9 @@ export function getSourceTypeLabel(sourceType?: string | null, generationMode?:
if (sourceType === "free_create") {
return i18n.global.t("status.sourceType.free_create");
}
if (sourceType === "imitation") {
return i18n.global.t("status.sourceType.imitation");
}
if (sourceType === "kol") {
return i18n.global.t("status.sourceType.kol");
}
+1 -1
View File
@@ -29,7 +29,7 @@ const errorMessageMap: Record<string, string> = {
article_generating: "文章仍在生成中",
article_not_editable: "只有已完成文章才可编辑",
article_lookup_failed: "文章状态读取失败",
draft_not_editable: "只有草稿或生成失败的文章才可继续在向导中编辑",
draft_not_editable: "只有草稿文章才可继续在向导中编辑",
article_title_required: "请输入文章标题",
invalid_image: "请选择图片文件",
image_upload_too_large: "图片不能超过 10MB",