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:
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { CopyOutlined } from "@ant-design/icons-vue";
|
||||
import { CopyOutlined, LinkOutlined } from "@ant-design/icons-vue";
|
||||
import { useQuery, useQueryClient } from "@tanstack/vue-query";
|
||||
import { message } from "ant-design-vue";
|
||||
import type { TableColumnsType } from "ant-design-vue";
|
||||
@@ -122,6 +122,8 @@ const generateMeta = computed(() =>
|
||||
getGenerateStatusMeta(streamStatus.value || detail.value?.generate_status),
|
||||
);
|
||||
const publishMeta = computed(() => getPublishStatusMeta(detail.value?.publish_status));
|
||||
const sourceArticleTitle = computed(() => detail.value?.source_title?.trim() || "");
|
||||
const sourceArticleURL = computed(() => detail.value?.source_url?.trim() || "");
|
||||
|
||||
function handleClose(): void {
|
||||
activeTab.value = "content";
|
||||
@@ -284,6 +286,27 @@ async function copyPublishLink(record: PublishRecord): Promise<void> {
|
||||
<a-descriptions-item :label="t('article.meta.wordCount')">
|
||||
{{ detail.word_count || "--" }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item
|
||||
v-if="sourceArticleURL"
|
||||
:label="t('article.meta.sourceArticle')"
|
||||
:span="2"
|
||||
>
|
||||
<a
|
||||
class="article-drawer__source-link"
|
||||
:href="sourceArticleURL"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
:title="sourceArticleURL"
|
||||
>
|
||||
<LinkOutlined />
|
||||
<span class="article-drawer__source-copy">
|
||||
<span v-if="sourceArticleTitle" class="article-drawer__source-title">
|
||||
{{ sourceArticleTitle }}
|
||||
</span>
|
||||
<span class="article-drawer__source-url">{{ sourceArticleURL }}</span>
|
||||
</span>
|
||||
</a>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</section>
|
||||
|
||||
@@ -423,6 +446,37 @@ async function copyPublishLink(record: PublishRecord): Promise<void> {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.article-drawer__source-link {
|
||||
display: inline-flex;
|
||||
max-width: 100%;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
color: #1677ff;
|
||||
}
|
||||
|
||||
.article-drawer__source-copy {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.article-drawer__source-title,
|
||||
.article-drawer__source-url {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.article-drawer__source-title {
|
||||
color: #1f2937;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.article-drawer__source-url {
|
||||
color: #1677ff;
|
||||
}
|
||||
|
||||
.article-drawer__alert {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
AppstoreOutlined,
|
||||
CalendarOutlined,
|
||||
ClockCircleOutlined,
|
||||
CopyOutlined,
|
||||
EditOutlined,
|
||||
TeamOutlined,
|
||||
ThunderboltOutlined,
|
||||
@@ -24,7 +25,7 @@ const sourceLabel = computed(() =>
|
||||
const createdAtLabel = computed(() => formatDateTime(props.createdAt));
|
||||
|
||||
const sourceVariant = computed<
|
||||
"template" | "custom" | "instant" | "schedule" | "free-create" | "kol" | "default"
|
||||
"template" | "custom" | "instant" | "schedule" | "free-create" | "imitation" | "kol" | "default"
|
||||
>(() => {
|
||||
if (props.sourceType === "template") {
|
||||
return "template";
|
||||
@@ -32,6 +33,9 @@ const sourceVariant = computed<
|
||||
if (props.sourceType === "free_create") {
|
||||
return "free-create";
|
||||
}
|
||||
if (props.sourceType === "imitation") {
|
||||
return "imitation";
|
||||
}
|
||||
if (props.sourceType === "kol") {
|
||||
return "kol";
|
||||
}
|
||||
@@ -59,6 +63,8 @@ const badgeIcon = computed<Component>(() => {
|
||||
return ThunderboltOutlined;
|
||||
case "free-create":
|
||||
return EditOutlined;
|
||||
case "imitation":
|
||||
return CopyOutlined;
|
||||
case "kol":
|
||||
return TeamOutlined;
|
||||
default:
|
||||
@@ -133,6 +139,12 @@ const badgeIcon = computed<Component>(() => {
|
||||
color: #18794e;
|
||||
}
|
||||
|
||||
.article-source-meta__badge--imitation {
|
||||
border: 1px solid #bfdbfe;
|
||||
background: linear-gradient(180deg, #f8fbff 0%, #e6f0ff 100%);
|
||||
color: #1d4ed8;
|
||||
}
|
||||
|
||||
.article-source-meta__badge--kol {
|
||||
border: 1px solid #d6e4ff;
|
||||
background: linear-gradient(180deg, #f5f8ff 0%, #eaf1ff 100%);
|
||||
|
||||
@@ -508,13 +508,6 @@ function normalizePlatformUid(value?: string | null): string {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<a-alert
|
||||
type="info"
|
||||
show-icon
|
||||
message="发布调度规则"
|
||||
description="客户端在线时会立即开始消费;客户端离线时也允许提交,任务会先写入 SaaS 队列,客户端恢复在线后继续执行。"
|
||||
/>
|
||||
|
||||
<section class="publish-modal__section">
|
||||
<div class="publish-modal__section-header">
|
||||
<h3><span class="required-star">*</span> 目标账号</h3>
|
||||
|
||||
Reference in New Issue
Block a user