refactor(ai): 小程序稿件与大纲页文案去掉接口术语与多余提示

与后台两页保持一致,页面不再暴露实现细节:

- 删除「提交说明」整块(含 `files`/`inputs`、接口路径、WebSocket 说明)
- 稿件页删除「接口摘要」区、模式说明 tip 与 `files` 字段说明,
  parseResult 不再产出 summary
- 大纲页删除 resultSummary 与 buildSummary,加载态压成一行
- 空态与占位提示统一改为一句人话
This commit is contained in:
2026-10-23 12:23:36 +08:00
parent 3309818160
commit f010fcd8ef
2 changed files with 14 additions and 162 deletions
+6 -77
View File
@@ -4,10 +4,7 @@
<view class="hero-card">
<view class="hero-kicker">AI EVENT PLANNING OUTLINE</view>
<view class="hero-title">活动策划大纲生成</view>
<view class="hero-desc">
输入活动主题、受众、时间和资金等信息,系统会调用 `/ai/activityOutlineGen`
生成结构化活动策划大纲,适合方案初稿、汇报材料和执行拆解场景。
</view>
<view class="hero-desc">填写活动主题、受众、时间和预算,AI 给出一份结构完整的策划大纲。</view>
<view class="hero-actions">
<view class="hero-action" @tap="fillExample('volunteer')">志愿服务</view>
<view class="hero-action" @tap="fillExample('salon')">主题沙龙</view>
@@ -96,13 +93,6 @@
{{ loading ? '生成中' : '生成大纲' }}
</view>
</view>
<view class="hint-block">
<view class="hint-title">提交说明</view>
<view class="hint-text">
必填字段会直接作为 `inputs` 透传给 AI,后端固定触发“活动策划大纲生成”指令,并返回整理后的正文结果。
</view>
</view>
</view>
<view class="panel-card result-card">
@@ -114,20 +104,12 @@
</view>
<view v-if="loading" class="loading-panel">
<view class="loading-kicker">OUTLINE GENERATING</view>
<view class="loading-title">AI 正在整理活动策划结构</view>
<view class="loading-desc">
正在综合活动主题、受众、时间与经费信息,生成可直接二次编辑的策划大纲。
</view>
<view class="loading-title">正在生成活动策划大纲…</view>
</view>
<view v-if="showThinkingIndicator" class="outline-thinking">
<view class="thinking-dot"></view>
<text>AI 正在生成正文内容,请稍候...</text>
</view>
<view v-if="resultSummary" class="result-alert">
{{ resultSummary }}
<text>正在生成正文…</text>
</view>
<view v-if="hasOutlineContent || loading" class="field-item">
@@ -135,7 +117,7 @@
<textarea
v-model="editableOutlineMarkdown"
class="result-textarea"
placeholder="填写左侧信息后开始生成活动策划大纲"
placeholder="这里可以直接修改大纲"
placeholder-style="color: #c0c4cc;"
maxlength="-1"
:disabled="loading"
@@ -143,7 +125,7 @@
</view>
<view v-else class="result-empty">
填写左侧信息后开始生成活动策划大纲
填写活动信息后开始生成
</view>
<view class="result-actions">
@@ -192,7 +174,6 @@
result: null,
outlineMarkdown: '',
editableOutlineMarkdown: '',
resultSummary: '',
activeConversationId: '',
activeMessageId: '',
socketTask: null,
@@ -332,16 +313,6 @@
// 现改为可读文案,原始载荷只进日志。
return resolveDisplayText({ payload, scope: 'activity-outline' })
},
buildSummary() {
const parts = []
if (this.activeConversationId) {
parts.push(`conversationId: ${this.activeConversationId}`)
}
if (this.activeMessageId) {
parts.push(`messageId: ${this.activeMessageId}`)
}
return parts.join(' | ')
},
clearStreamResolver() {
this.streamResolver = null
},
@@ -403,7 +374,6 @@
}
this.outlineMarkdown += chunk
this.syncEditableOutline()
this.resultSummary = this.buildSummary() || '正在通过 WebSocket 接收流式内容'
},
finishStream(conversationId, messageId) {
if (conversationId && conversationId !== '0') {
@@ -412,7 +382,6 @@
if (messageId && messageId !== '0') {
this.activeMessageId = messageId
}
this.resultSummary = this.buildSummary() || '已通过 WebSocket 接收生成结果'
this.loading = false
this.resolveStream()
this.disconnectWebSocket()
@@ -431,7 +400,6 @@
// 但语义上必须先清掉流式残片(见 issue 03)。
const reset = !!(data && (data.reset === 'true' || data.reset === true))
if (data && (data.loading === 'true' || data.loading === true)) {
this.resultSummary = 'AI 正在组织活动策划大纲结构...'
return
}
const chunk = this.pickRawString(data && data.notice, data && data.answer)
@@ -531,7 +499,6 @@
this.result = null
this.outlineMarkdown = ''
this.editableOutlineMarkdown = ''
this.resultSummary = ''
this.activeConversationId = ''
this.activeMessageId = ''
this.streamFailed = false
@@ -550,14 +517,12 @@
}
this.loading = true
this.clearResult()
this.resultSummary = '正在提交生成任务...'
try {
// 一律走 HTTP 流式(/stream),不再先连 WebSocket:
// 触发生成的那个 POST 在整段生成期间是零字节的,会被链路上的
// 60s 空闲超时掐成 504 Gateway Time-out。/stream 每 15s 有后端
// 心跳续命,帧内容与 WS 完全一致。
const payload = this.buildPayload()
this.resultSummary = '已提交生成任务,等待流式返回...'
const waitStream = this.createStreamWaiter()
await streamActivityOutlineGen(payload, {
onMessage: (raw) => this.handleSocketMessage(raw)
@@ -786,28 +751,6 @@
color: #24508f;
}
.hint-block,
.result-alert {
margin-top: 22rpx;
padding: 22rpx 24rpx;
border-radius: 24rpx;
background: #f5f8fd;
border: 1rpx solid rgba(51, 97, 164, 0.08);
font-size: 22rpx;
line-height: 1.75;
color: #6d7d93;
}
.hint-title {
font-size: 24rpx;
font-weight: 700;
color: #35527d;
}
.hint-text {
margin-top: 10rpx;
}
.result-card {
margin-bottom: 20rpx;
}
@@ -845,26 +788,12 @@
background: linear-gradient(135deg, #f5f9ff 0%, #eef4fb 100%);
}
.loading-kicker {
font-size: 20rpx;
letter-spacing: 3rpx;
color: #6682aa;
}
.loading-title {
margin-top: 10rpx;
font-size: 30rpx;
font-size: 26rpx;
font-weight: 700;
color: #23446f;
}
.loading-desc {
margin-top: 12rpx;
font-size: 22rpx;
line-height: 1.75;
color: #6d7d93;
}
.outline-thinking {
display: flex;
align-items: center;
+8 -85
View File
@@ -4,10 +4,7 @@
<view class="hero-card">
<view class="hero-kicker">AI CONTENT STUDIO</view>
<view class="hero-title">AI 内容创作与宣传助手</view>
<view class="hero-desc">
两种用法:不选模板(自由创作)时只需描述想写什么,由「AI 创意助手」生成;选了稿件类型后再展开活动信息等可选项,
由「内容创作与宣传助手」按体裁生成。结果通过 WebSocket 实时回传。
</view>
<view class="hero-desc">写新闻稿、推文、发言稿,或直接说明你的需求,AI 先给出一份初稿。</view>
<view class="hero-actions">
<view class="hero-action" @tap="fillDemo('news')">活动新闻稿</view>
<view class="hero-action" @tap="fillDemo('wechat')">公众号推文</view>
@@ -27,7 +24,6 @@
<text class="field-value">{{ form.docType || '请选择稿件类型' }}</text>
</view>
</picker>
<view class="field-tip">选「自由创作」= 不套稿件模板,只描述你想写什么;选具体模板后才会展开下方其它可选项。</view>
</view>
<template v-if="templateMode">
@@ -111,7 +107,6 @@
<view class="field-label">附件材料</view>
<view class="upload-bar">
<view class="upload-btn" @tap="chooseFiles">{{ uploading ? '上传中...' : '选择附件' }}</view>
<view class="upload-tip">支持图片、文档等附件,上传后将按 `files` 字段提交。</view>
</view>
<view v-if="uploadedFiles.length" class="file-list">
<view
@@ -143,9 +138,6 @@
maxlength="-1"
/>
</view>
<view class="free-tip">
自由模式不套用稿件模板,内容将交给「AI 创意助手」生成。想按新闻稿、推文等固定体裁写,请在上方选择对应模板。
</view>
</template>
</view>
@@ -156,13 +148,6 @@
</view>
</view>
<view class="hint-block">
<view class="hint-title">提交说明</view>
<view class="hint-text">
自由模式只提交一段内容(`docType` 留空),由后端转「AI 创意助手」;选了模板才提交 `docType`、活动类型、时间地点等结构化条件。
两种都会先 `POST /ai/manuscriptGen`,再通过 WebSocket 流式接收生成结果;如果后端直接返回正文,也会自动兼容显示。
</view>
</view>
</view>
<view class="panel-card result-card">
@@ -191,15 +176,10 @@
{{ markdownContent || 'AI 正在生成内容...' }}
</view>
<zero-markdown-view v-else-if="markdownContent" class="result-content" :markdown="markdownContent" />
<view v-else class="result-empty">填写左侧创作条件后开始生成</view>
<view v-else class="result-empty">填写创作条件后开始生成</view>
</view>
</view>
<view v-if="responseSummary" class="response-meta">
<view class="response-label">接口摘要</view>
<view class="response-value">{{ responseSummary }}</view>
</view>
<view class="result-actions">
<view class="result-btn" @tap="copyGenerated">复制正文</view>
<view class="result-btn" @tap="copyGeneratedTitle">复制标题</view>
@@ -258,7 +238,6 @@
wsConnected: false,
generatedTitle: '',
generatedContent: '',
responseSummary: '',
uploadedFiles: [],
activeConversationId: '',
socketTask: null,
@@ -415,16 +394,14 @@
// 序列化报文,其中的字段取不出来,按原文展示,不做失败推断。
return {
title: String(this.form.theme || '').trim(),
content: payload,
summary: '接口直接返回文本内容'
content: payload
}
}
if (isFailedPayload(payload)) {
return {
title: String(this.form.theme || '').trim(),
content: resolveDisplayText({ payload, scope: 'manuscript' }),
summary: '工作流执行失败'
content: resolveDisplayText({ payload, scope: 'manuscript' })
}
}
@@ -458,8 +435,7 @@
const resolved = content || resolveDisplayText({ payload, scope: 'manuscript' })
return {
title: title || String(this.form.theme || '').trim(),
content: resolved,
summary: content ? '已解析返回字段' : '接口已返回,但未解析到可展示的内容'
content: resolved
}
},
parseSerializedResult(payload) {
@@ -479,11 +455,10 @@
}
return payload
},
applyParsedResult(payload, defaultSummary) {
applyParsedResult(payload) {
const parsed = this.parseResult(payload)
this.generatedTitle = parsed.title || this.generatedTitle || String(this.form.theme || '').trim()
this.generatedContent = parsed.content
this.responseSummary = parsed.summary || defaultSummary
},
shouldTreatAsDirectResult(payload) {
if (!payload) {
@@ -558,14 +533,13 @@
this.streamFailed = true
}
this.generatedContent += chunk
this.responseSummary = '正在通过 WebSocket 接收流式内容'
},
finishStream(conversationId) {
if (conversationId) {
this.activeConversationId = conversationId
}
const payload = this.parseSerializedResult(this.generatedContent)
this.applyParsedResult(payload, '已通过 WebSocket 接收生成结果')
this.applyParsedResult(payload)
this.loading = false
this.resolveStream()
this.disconnectWebSocket()
@@ -682,7 +656,6 @@
clearGenerated() {
this.generatedTitle = ''
this.generatedContent = ''
this.responseSummary = ''
},
resetForm() {
this.form = createDefaultForm()
@@ -793,16 +766,12 @@
this.loading = true
this.clearGenerated()
this.generatedTitle = String(this.form.theme || '').trim()
this.responseSummary = '正在提交生成任务...'
try {
// 一律走 HTTP 流式(/stream),不再先连 WebSocket:
// 触发生成的那个 POST 在整段生成期间是零字节的,会被链路上的
// 60s 空闲超时掐成 504 Gateway Time-out(自由创作、不选文体时最先撞上)。
// /stream 每 15s 有后端心跳续命,帧内容与 WS 完全一致。
const payload = this.buildPayload()
this.responseSummary = this.freeMode
? '自由创作已提交给 AI 创意助手,生成完成后一次性返回...'
: '已提交生成任务,等待流式返回...'
const waitStream = this.createStreamWaiter()
await streamManuscriptGen(payload, {
onMessage: (raw) => this.handleStreamFrame(raw)
@@ -842,8 +811,7 @@
const result = frame.result
if (result && Number(result.code) === 0 && result.data) {
this.applyParsedResult(
typeof result.data === 'string' ? this.parseSerializedResult(result.data) : result.data,
'接口直接返回生成结果'
typeof result.data === 'string' ? this.parseSerializedResult(result.data) : result.data
)
return
}
@@ -999,21 +967,6 @@
min-height: 300rpx;
}
.field-tip,
.free-tip {
margin-top: 10rpx;
font-size: 22rpx;
line-height: 1.7;
color: #8f959e;
}
.free-tip {
padding: 18rpx 22rpx;
border-radius: 18rpx;
background: rgba(20, 150, 242, 0.06);
color: #6b7d8f;
}
.field-value {
color: #303133;
}
@@ -1052,12 +1005,6 @@
font-weight: 600;
}
.upload-tip {
font-size: 22rpx;
line-height: 1.7;
color: #8b817c;
}
.file-list {
display: flex;
flex-direction: column;
@@ -1158,30 +1105,6 @@
color: #0f7dd1;
}
.hint-block,
.response-meta {
margin-top: 22rpx;
padding: 22rpx 24rpx;
border-radius: 24rpx;
background: #f4faff;
border: 1rpx solid rgba(20, 150, 242, 0.08);
}
.hint-title,
.response-label {
font-size: 24rpx;
font-weight: 700;
color: #8b4a12;
}
.hint-text,
.response-value {
margin-top: 10rpx;
font-size: 22rpx;
line-height: 1.75;
color: #7b6d64;
}
.result-card {
margin-bottom: 20rpx;
}