forked from b2894lxlx/tuanwei-admin
refactor(ai): 稿件与大纲页文案去掉接口术语与多余提示
面向学生的页面不再暴露实现细节: - 删除两页底部「提交说明」整块(含 `docType`/`inputs`、接口路径、 WebSocket 流式说明及对应样式) - 稿件页删除 `user_content`、`files` 字段说明;大纲页不再把 conversationId/messageId 拼成「接口摘要」展示,随之删除 buildSummary、resultSummary 及摘要渲染 - 加载态由三段式压成一行,空态与附件说明各留一句人话
This commit is contained in:
@@ -4,11 +4,7 @@
|
||||
<div>
|
||||
<div class="hero-kicker">AI EVENT PLANNING OUTLINE</div>
|
||||
<h1>活动策划大纲生成</h1>
|
||||
<p>
|
||||
输入活动主题、受众、时间和资金等信息,系统将调用
|
||||
`/ai/activityOutlineGen`
|
||||
生成结构化活动策划大纲,适合方案初稿、汇报材料和执行拆解场景。
|
||||
</p>
|
||||
<p>填写活动主题、受众、时间和预算,AI 给出一份结构完整的策划大纲,可直接改、复制或导出。</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -91,13 +87,6 @@
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<div class="hint-block">
|
||||
<div class="hint-title">提交说明</div>
|
||||
<div class="hint-text">
|
||||
必填字段会直接作为 `inputs` 透传给
|
||||
AI,后端固定触发“活动策划大纲生成”指令,并返回整理后的正文结果。
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
|
||||
@@ -140,31 +129,19 @@
|
||||
|
||||
<a-empty
|
||||
v-if="!loading && !hasOutlineContent"
|
||||
description="填写左侧信息后开始生成活动策划大纲"
|
||||
description="填写活动信息后开始生成"
|
||||
/>
|
||||
|
||||
<div v-else class="result-shell">
|
||||
<div v-if="loading" class="loading-panel">
|
||||
<div class="loading-kicker">OUTLINE GENERATING</div>
|
||||
<div class="loading-title">AI 正在整理活动策划结构</div>
|
||||
<div class="loading-desc">
|
||||
正在综合活动主题、受众、时间与经费信息,生成可直接二次编辑的策划大纲。
|
||||
</div>
|
||||
<div class="loading-title">正在生成活动策划大纲…</div>
|
||||
</div>
|
||||
|
||||
<div v-if="showThinkingIndicator" class="outline-thinking">
|
||||
<span class="thinking-dot" aria-hidden="true"></span>
|
||||
<span>AI 正在生成正文内容,请稍候...</span>
|
||||
<span>正在生成正文…</span>
|
||||
</div>
|
||||
|
||||
<a-alert
|
||||
v-if="resultSummary"
|
||||
class="result-alert"
|
||||
type="info"
|
||||
show-icon
|
||||
:message="resultSummary"
|
||||
/>
|
||||
|
||||
<ByteMdEditor
|
||||
v-if="hasOutlineContent"
|
||||
v-model:value="editableOutlineMarkdown"
|
||||
@@ -236,7 +213,6 @@
|
||||
const result = ref<ActivityOutlineGenResponse | null>(null);
|
||||
const outlineMarkdown = ref('');
|
||||
const editableOutlineMarkdown = ref('');
|
||||
const resultSummary = ref('');
|
||||
const activeConversationId = ref<string>();
|
||||
const activeMessageId = ref<string>();
|
||||
const webSocketRef = ref<WebSocket | null>(null);
|
||||
@@ -412,17 +388,6 @@
|
||||
});
|
||||
};
|
||||
|
||||
const buildSummary = () => {
|
||||
const parts = [] as string[];
|
||||
if (activeConversationId.value) {
|
||||
parts.push(`conversationId: ${activeConversationId.value}`);
|
||||
}
|
||||
if (activeMessageId.value) {
|
||||
parts.push(`messageId: ${activeMessageId.value}`);
|
||||
}
|
||||
return parts.join(' | ');
|
||||
};
|
||||
|
||||
const clearStreamResolver = () => {
|
||||
streamResolverRef.value = null;
|
||||
};
|
||||
@@ -485,7 +450,6 @@
|
||||
outlineMarkdown.value += chunk;
|
||||
}
|
||||
syncEditableOutline();
|
||||
resultSummary.value = buildSummary() || '正在通过 WebSocket 接收流式内容';
|
||||
};
|
||||
|
||||
const finishStream = (conversationId?: string, messageId?: string) => {
|
||||
@@ -495,7 +459,6 @@
|
||||
if (messageId && messageId !== '0') {
|
||||
activeMessageId.value = messageId;
|
||||
}
|
||||
resultSummary.value = buildSummary() || '已通过 WebSocket 接收生成结果';
|
||||
loading.value = false;
|
||||
resolveStream();
|
||||
disconnectWebSocket();
|
||||
@@ -512,7 +475,6 @@
|
||||
return;
|
||||
}
|
||||
if (data?.loading === 'true' || data?.loading === true) {
|
||||
resultSummary.value = 'AI 正在组织活动策划大纲结构...';
|
||||
return;
|
||||
}
|
||||
const chunk = pickRawString(data?.notice, data?.answer);
|
||||
@@ -608,7 +570,6 @@
|
||||
result.value = null;
|
||||
outlineMarkdown.value = '';
|
||||
editableOutlineMarkdown.value = '';
|
||||
resultSummary.value = '';
|
||||
activeConversationId.value = undefined;
|
||||
activeMessageId.value = undefined;
|
||||
};
|
||||
@@ -631,13 +592,11 @@
|
||||
}
|
||||
loading.value = true;
|
||||
clearResult();
|
||||
resultSummary.value = '正在提交生成任务...';
|
||||
try {
|
||||
// 一律走 HTTP 流式(/stream),不再先连 WebSocket:
|
||||
// 触发生成的那个 POST 在整段生成期间是零字节的,会被链路上的 60s 空闲
|
||||
// 超时掐成 504 Gateway Time-out。/stream 每 15s 有后端心跳续命。
|
||||
const payload = buildPayload();
|
||||
resultSummary.value = '已提交生成任务,等待流式返回...';
|
||||
const waitStream = createStreamWaiter();
|
||||
await streamActivityOutlineGen(
|
||||
payload as unknown as Record<string, unknown>,
|
||||
@@ -895,27 +854,6 @@
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.hint-block {
|
||||
margin-top: 18px;
|
||||
padding: 16px 18px;
|
||||
border-radius: 16px;
|
||||
background: #f6f9fe;
|
||||
border: 1px solid #d9e6fb;
|
||||
}
|
||||
|
||||
.hint-title {
|
||||
margin-bottom: 8px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #274f84;
|
||||
}
|
||||
|
||||
.hint-text {
|
||||
color: #5f6f84;
|
||||
line-height: 1.8;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.result-card :deep(.ant-card-head) {
|
||||
border-bottom: 1px solid #edf2f8;
|
||||
}
|
||||
@@ -944,29 +882,12 @@
|
||||
border: 1px solid #d9e8fb;
|
||||
}
|
||||
|
||||
.loading-kicker {
|
||||
margin-bottom: 10px;
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.18em;
|
||||
color: #4875b0;
|
||||
}
|
||||
|
||||
.loading-title {
|
||||
font-size: 22px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #183b68;
|
||||
}
|
||||
|
||||
.loading-desc {
|
||||
margin-top: 10px;
|
||||
line-height: 1.8;
|
||||
color: #607086;
|
||||
}
|
||||
|
||||
.result-alert {
|
||||
border-radius: 14px;
|
||||
}
|
||||
|
||||
.outline-thinking {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -4,10 +4,7 @@
|
||||
<div>
|
||||
<div class="hero-kicker">AI CONTENT STUDIO</div>
|
||||
<h1>AI内容创作与宣传助手</h1>
|
||||
<p>
|
||||
面向团委宣传、活动报道、新闻稿和推文草稿的快速生成场景。输入创作条件后,通过
|
||||
`/ai/manuscriptGen` 发起生成任务,并通过 WebSocket 实时接收返回内容,支持继续编辑与复制。
|
||||
</p>
|
||||
<p>写新闻稿、推文、发言稿,或直接说明你的需求,AI 先给出一份初稿,可以接着改。</p>
|
||||
</div>
|
||||
<div class="hero-actions">
|
||||
<a-button type="primary" ghost @click="goChatPage">进入问答助手</a-button>
|
||||
@@ -41,9 +38,6 @@
|
||||
:auto-size="{ minRows: 6, maxRows: 12 }"
|
||||
placeholder="例如:帮我写一段迎新晚会的主持人开场词,口语化一点,时长两分钟左右"
|
||||
/>
|
||||
<div class="upload-tip">
|
||||
不套模板时,这段内容会作为 `user_content` 交给「AI 创意助手」;选上面的文稿模板后可填写活动信息等可选参数。
|
||||
</div>
|
||||
</a-form-item>
|
||||
<template v-if="templateMode">
|
||||
<a-form-item label="稿件标题">
|
||||
@@ -111,9 +105,6 @@
|
||||
上传附件
|
||||
</a-button>
|
||||
</a-upload>
|
||||
<div class="upload-tip">
|
||||
支持图片、文档等附件,上传后按 `files` 字段一并提交给接口。
|
||||
</div>
|
||||
<div v-if="uploadedFiles.length" class="file-list">
|
||||
<div
|
||||
v-for="(item, index) in uploadedFiles"
|
||||
@@ -145,15 +136,6 @@
|
||||
生成内容
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<div class="hint-block">
|
||||
<div class="hint-title">提交说明</div>
|
||||
<div class="hint-text">
|
||||
未选模板(自由创作)时只提交一段内容,后端转「AI 创意助手」;选了模板才提交
|
||||
`docType` 等结构化条件。两种都会先 `POST /ai/manuscriptGen`,再按 `/ai` 页面的方式
|
||||
通过 WebSocket 流式接收生成结果;如果后端仍然直接返回正文,页面也会自动兼容解析。
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
|
||||
@@ -176,7 +158,7 @@
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<a-empty v-if="!markdownContent && !loading" description="填写左侧创作条件后开始生成" />
|
||||
<a-empty v-if="!markdownContent && !loading" description="填写创作条件后开始生成" />
|
||||
|
||||
<template v-else>
|
||||
<a-form layout="vertical">
|
||||
@@ -201,11 +183,6 @@
|
||||
</div>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<!-- <div v-if="responseSummary" class="response-meta">-->
|
||||
<!-- <div class="meta-label">接口摘要</div>-->
|
||||
<!-- <div class="meta-value">{{ responseSummary }}</div>-->
|
||||
<!-- </div>-->
|
||||
</template>
|
||||
</a-card>
|
||||
</a-col>
|
||||
@@ -257,7 +234,6 @@
|
||||
interface ParsedResult {
|
||||
title: string;
|
||||
content: string;
|
||||
summary: string;
|
||||
}
|
||||
|
||||
type DemoType = 'news' | 'wechat' | 'speech' | 'free';
|
||||
@@ -278,7 +254,6 @@
|
||||
const wsConnected = ref(false);
|
||||
const generatedTitle = ref('');
|
||||
const generatedContent = ref('');
|
||||
const responseSummary = ref('');
|
||||
const uploadedFiles = ref<Partial<FileRecord>[]>([]);
|
||||
const activeConversationId = ref<string>();
|
||||
const webSocketRef = ref<WebSocket | null>(null);
|
||||
@@ -458,8 +433,7 @@
|
||||
if (typeof payload === 'string') {
|
||||
return {
|
||||
title: form.theme.trim(),
|
||||
content: payload,
|
||||
summary: '接口直接返回文本内容'
|
||||
content: payload
|
||||
};
|
||||
}
|
||||
|
||||
@@ -468,8 +442,7 @@
|
||||
if (isFailedPayload(payload)) {
|
||||
return {
|
||||
title: form.theme.trim(),
|
||||
content: FAILED_TEXT,
|
||||
summary: '上游工作流执行失败'
|
||||
content: FAILED_TEXT
|
||||
};
|
||||
}
|
||||
|
||||
@@ -497,10 +470,6 @@
|
||||
payload.choices?.[0]?.message?.content
|
||||
);
|
||||
|
||||
const summary = Object.keys(payload).length
|
||||
? `已解析返回字段:${Object.keys(payload).join('、')}`
|
||||
: '返回对象为空';
|
||||
|
||||
return {
|
||||
title: title || form.theme.trim(),
|
||||
// 不再 stringifyUnknown(payload):整包会带出 taskId / workflowRunId 等
|
||||
@@ -509,8 +478,7 @@
|
||||
payload,
|
||||
scope: 'gxmu/manuscriptGen.parseResult',
|
||||
candidates: []
|
||||
}),
|
||||
summary
|
||||
})
|
||||
};
|
||||
};
|
||||
|
||||
@@ -532,14 +500,10 @@
|
||||
return payload;
|
||||
};
|
||||
|
||||
const applyParsedResult = (
|
||||
payload: ManuscriptGenResult,
|
||||
defaultSummary = '已通过 WebSocket 接收生成结果'
|
||||
) => {
|
||||
const applyParsedResult = (payload: ManuscriptGenResult) => {
|
||||
const parsed = parseResult(payload);
|
||||
generatedTitle.value = parsed.title || generatedTitle.value || form.theme.trim();
|
||||
generatedContent.value = parsed.content;
|
||||
responseSummary.value = parsed.summary || defaultSummary;
|
||||
};
|
||||
|
||||
const shouldTreatAsDirectResult = (payload: unknown) => {
|
||||
@@ -620,7 +584,6 @@
|
||||
} else {
|
||||
generatedContent.value += chunk;
|
||||
}
|
||||
responseSummary.value = '正在通过 WebSocket 接收流式内容';
|
||||
};
|
||||
|
||||
const finishStream = (conversationId?: string) => {
|
||||
@@ -628,7 +591,7 @@
|
||||
activeConversationId.value = conversationId;
|
||||
}
|
||||
const payload = parseSerializedResult(generatedContent.value);
|
||||
applyParsedResult(payload, '已通过 WebSocket 接收生成结果');
|
||||
applyParsedResult(payload);
|
||||
loading.value = false;
|
||||
resolveStream();
|
||||
disconnectWebSocket();
|
||||
@@ -741,7 +704,6 @@
|
||||
const clearGenerated = () => {
|
||||
generatedTitle.value = '';
|
||||
generatedContent.value = '';
|
||||
responseSummary.value = '';
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
@@ -850,7 +812,6 @@
|
||||
loading.value = true;
|
||||
clearGenerated();
|
||||
generatedTitle.value = form.theme.trim();
|
||||
responseSummary.value = '正在提交生成任务...';
|
||||
try {
|
||||
// 一律走 HTTP 流式(/stream),不再先连 WebSocket:
|
||||
// 触发生成的那个 POST 在整段生成期间是零字节的,会被链路上的 60s 空闲
|
||||
@@ -858,9 +819,6 @@
|
||||
// /stream 每 15s 有后端心跳续命,正文逐段喂给原来的帧处理,
|
||||
// 结构化结果从末帧 {type:'final'} 取。
|
||||
const payload = buildPayload();
|
||||
responseSummary.value = templateMode.value
|
||||
? '已提交生成任务,等待流式返回...'
|
||||
: '自由创作已提交给 AI 创意助手,生成完成后一次性返回...';
|
||||
const waitStream = createStreamWaiter();
|
||||
const result = await runManuscriptGenStream(
|
||||
payload as unknown as Record<string, unknown>,
|
||||
@@ -871,8 +829,7 @@
|
||||
applyParsedResult(
|
||||
typeof result === 'string'
|
||||
? parseSerializedResult(result)
|
||||
: (result as ManuscriptGenResult),
|
||||
'接口直接返回生成结果'
|
||||
: (result as ManuscriptGenResult)
|
||||
);
|
||||
}
|
||||
// 兜底:流结束但没收到 __END__ 时也别把页面卡在加载中
|
||||
@@ -1107,13 +1064,6 @@
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.upload-tip {
|
||||
margin-top: 10px;
|
||||
color: #5f7f9f;
|
||||
font-size: 12px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.file-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -1173,26 +1123,6 @@
|
||||
color: #1590ff;
|
||||
}
|
||||
|
||||
.hint-block {
|
||||
margin-top: 18px;
|
||||
padding: 14px 16px;
|
||||
border-radius: 14px;
|
||||
background: #f5fbff;
|
||||
border: 1px solid #c7e8ff;
|
||||
}
|
||||
|
||||
.hint-title {
|
||||
margin-bottom: 6px;
|
||||
font-weight: 600;
|
||||
color: #1590ff;
|
||||
}
|
||||
|
||||
.hint-text {
|
||||
color: #5f7f9f;
|
||||
line-height: 1.7;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.result-card {
|
||||
min-height: 100%;
|
||||
}
|
||||
@@ -1203,12 +1133,6 @@
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.response-meta {
|
||||
padding: 12px 14px;
|
||||
border-radius: 14px;
|
||||
background: #f5fbff;
|
||||
}
|
||||
|
||||
.markdown-shell {
|
||||
min-height: 360px;
|
||||
border: 1px solid #c7e8ff;
|
||||
@@ -1244,18 +1168,6 @@
|
||||
color: #1f2d3d;
|
||||
}
|
||||
|
||||
.meta-label {
|
||||
margin-bottom: 4px;
|
||||
font-weight: 600;
|
||||
color: #1f2d3d;
|
||||
}
|
||||
|
||||
.meta-value {
|
||||
color: #5f7f9f;
|
||||
line-height: 1.7;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user