refactor(ai): 小程序稿件与大纲页文案去掉接口术语与多余提示
与后台两页保持一致,页面不再暴露实现细节: - 删除「提交说明」整块(含 `files`/`inputs`、接口路径、WebSocket 说明) - 稿件页删除「接口摘要」区、模式说明 tip 与 `files` 字段说明, parseResult 不再产出 summary - 大纲页删除 resultSummary 与 buildSummary,加载态压成一行 - 空态与占位提示统一改为一句人话
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user