diff --git a/src/pages/assistant/index.vue b/src/pages/assistant/index.vue
index 59345ca..95e0d4c 100644
--- a/src/pages/assistant/index.vue
+++ b/src/pages/assistant/index.vue
@@ -153,6 +153,88 @@
+
+
+
+ 写作参数
+ 可选填,填了会一并提交
+ 已填 {{ writingParamCount }} 项
+ {{ writingParamsOpen ? '收起' : '展开' }}
+
+
+
+
+ 类型
+ 不限
+ {{ item }}
+
+
+
+ 主题
+
+
+
+ 时间
+
+
+
+ 地点
+
+
+
+ 人员
+
+
+
+
+
0
)
},
canPickImageFiles() {
@@ -350,6 +450,16 @@
writingTemplateMode() {
return this.writingDocType !== WRITING_DOC_TYPE_FREE
},
+ /**
+ * 写作参数里用户真正填了几项。
+ * 不算活动类型:它带着默认值「校园活动」,算进去会一打开就显示「已填 1 项」。
+ */
+ writingParamCount() {
+ const params = this.writingParams || {}
+ return ['theme', 'time', 'location', 'participants'].filter((key) =>
+ String(params[key] || '').trim()
+ ).length
+ },
assistantTitle() {
if (this.activeChatMode === 'writing') {
return 'AI 写作助手'
@@ -373,6 +483,9 @@
return '请先登录后使用 AI 助手'
}
if (this.activeChatMode === 'writing') {
+ if (this.writingTemplateMode) {
+ return '写清活动亮点、流程、成果等关键信息,会作为「亮点内容」提交;标题地点等可在上方「写作参数」里补'
+ }
return '描述你想创作的内容,例如:帮我写一段迎新晚会的主持人开场词,口语化一点(选了文体后才需要再定字数)'
}
if (this.activeChatMode === 'image') {
@@ -968,8 +1081,9 @@
* 两条路,都不解析自然语言:
* - 自由创作(未选文体):原话整段作为 `userContent`,后端见 `docType` 为空
* 就转「AI 创意助手」(只吃 `user_content`);
- * - 选了文体:原话整段作为 `highlights`(核心亮点与具体要求),只下发界面
- * 上显式选过的 docType / number,其余可选字段留空交给上游。
+ * - 选了文体:原话整段作为 `highlights`(核心亮点与具体要求),
+ * 「写作参数」里填了的活动类型 / 主题 / 时间 / 地点 / 人员原样下发,
+ * 没填的不带(后端 buildManuscriptInputs 会跳过空串)。
*
* 过去这里用 `parseWritingRequest` 从原话猜 doc_type / 字数 / 主题,
* 猜错会把垃圾值当主控指令(issue 13);现已废弃该解析,见 `./writing-params`。
@@ -984,11 +1098,34 @@
highlights: content,
files: []
}
+ const params = this.writingParams || {}
+ const optionalFields = {
+ eventType: params.eventType,
+ theme: params.theme,
+ time: params.time,
+ location: params.location,
+ participants: params.participants
+ }
+ Object.keys(optionalFields).forEach((key) => {
+ const value = String(optionalFields[key] || '').trim()
+ if (value) {
+ payload[key] = value
+ }
+ })
if (this.writingWordCount !== 'auto') {
payload.number = Number(this.writingWordCount)
}
return payload
},
+ /** 没写正文、只填了写作参数时,气泡上给一句说明,不留空消息 */
+ writingRequestSummary() {
+ const params = this.writingParams || {}
+ const parts = [
+ this.writingDocType,
+ params.theme || params.location || params.time
+ ].filter((item) => String(item || '').trim())
+ return `(按写作参数生成:${parts.join(' · ')})`
+ },
formatWritingResult(payload) {
if (typeof payload === 'string') {
return payload
@@ -1095,6 +1232,18 @@
}
this.writingWordCount = value
},
+ toggleWritingParams() {
+ if (this.loading) {
+ return
+ }
+ this.writingParamsOpen = !this.writingParamsOpen
+ },
+ pickWritingEventType(value) {
+ if (this.loading) {
+ return
+ }
+ this.writingParams.eventType = value
+ },
/**
* 选参考素材。
*
@@ -1398,6 +1547,12 @@
return
}
const query = this.draft.trim()
+ // 只填了写作参数、没写正文时,气泡上仍要有一句话,否则是一条空消息
+ const displayQuery =
+ query ||
+ (this.activeChatMode === 'writing' && this.writingTemplateMode
+ ? this.writingRequestSummary()
+ : query)
this.loading = true
try {
const assistantId = this.createMessageId('assistant')
@@ -1406,7 +1561,7 @@
id: this.createMessageId('user'),
anchorId: this.createMessageId('anchor'),
role: 'user',
- content: query,
+ content: displayQuery,
time: userTime
})
this.messageList.push({
@@ -1864,6 +2019,100 @@
opacity: 0.5;
}
+ /* 「写作参数」:只在选了文体时出现,默认收起,展开后按行堆叠(手机屏窄,不做两列) */
+ .writing-params {
+ margin-bottom: 12rpx;
+ padding: 12rpx 14rpx;
+ border-radius: 16rpx;
+ border: 1rpx dashed rgba(20, 150, 242, 0.28);
+ background: rgba(255, 255, 255, 0.86);
+ }
+
+ .writing-params--open {
+ border-style: solid;
+ background: rgba(255, 255, 255, 0.96);
+ }
+
+ .writing-params-head {
+ display: flex;
+ align-items: center;
+ gap: 10rpx;
+ }
+
+ .writing-params-title {
+ flex-shrink: 0;
+ font-size: 22rpx;
+ font-weight: 700;
+ color: #1496f2;
+ }
+
+ .writing-params-hint {
+ flex: 1;
+ min-width: 0;
+ overflow: hidden;
+ font-size: 20rpx;
+ color: #a59a94;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ .writing-params-count {
+ flex-shrink: 0;
+ padding: 2rpx 12rpx;
+ border-radius: 999rpx;
+ background: rgba(20, 150, 242, 0.12);
+ color: #1496f2;
+ font-size: 20rpx;
+ font-weight: 600;
+ }
+
+ .writing-params-action {
+ flex-shrink: 0;
+ font-size: 20rpx;
+ color: #1496f2;
+ }
+
+ /*
+ 展开后限高 + 内部滚动:四个输入框 + 类型行全展开约 360rpx,
+ 不限高会把输入框顶到 tabbar 后面(H5 与小程序都会)。
+ */
+ .writing-params-body {
+ max-height: 340rpx;
+ margin-top: 12rpx;
+ padding-top: 12rpx;
+ border-top: 1rpx solid rgba(20, 150, 242, 0.12);
+ }
+
+ .param-field {
+ display: flex;
+ align-items: center;
+ gap: 12rpx;
+ margin-bottom: 8rpx;
+ }
+
+ .param-field-label {
+ flex-shrink: 0;
+ width: 56rpx;
+ font-size: 22rpx;
+ color: #8a7f79;
+ }
+
+ .param-field-input {
+ flex: 1;
+ min-width: 0;
+ height: 56rpx;
+ padding: 0 16rpx;
+ border-radius: 12rpx;
+ border: 1rpx solid rgba(20, 150, 242, 0.2);
+ background: #fff;
+ font-size: 22rpx;
+ color: #4a5560;
+ }
+
+ .param-placeholder {
+ color: #b8ada7;
+ }
+
.attach-list {
margin-bottom: 12rpx;
}
diff --git a/src/pages/assistant/writing-params.js b/src/pages/assistant/writing-params.js
index 2c584f3..5d7278a 100644
--- a/src/pages/assistant/writing-params.js
+++ b/src/pages/assistant/writing-params.js
@@ -38,3 +38,19 @@ export const WRITING_WORD_COUNT_OPTIONS = [
{ label: '1000字', value: '1000' },
{ label: '1500字', value: '1500' }
]
+
+/**
+ * 活动类型(上游 `event_type`)的候选值。
+ *
+ * 上游开始节点把 event_type 声明成普通 text-input,没有枚举约束,这里给一组常用值
+ * 只是为了让用户不用手打;与写作页 `pages/manuscript/index.vue` 的
+ * `EVENT_TYPE_OPTIONS`、web 侧 `writing-params.ts` 保持一致。
+ */
+export const WRITING_EVENT_TYPE_OPTIONS = [
+ '校园活动',
+ '志愿服务',
+ '会议论坛',
+ '社会实践',
+ '文体活动',
+ '品牌项目'
+]