|
|
@@ -166,6 +166,27 @@
|
|
|
<input class="feedback-input" v-model="sectionFeedback" placeholder="如有修改意见请填写(可选)..." />
|
|
|
</view>
|
|
|
|
|
|
+ <!-- 对话式调整(HL-04/R4):自然语言调整意图 → 调后端调整接口 → 刷新板块 -->
|
|
|
+ <view class="dialogue-panel">
|
|
|
+ <view class="dialogue-head">
|
|
|
+ <text class="dialogue-title">💬 对话式调整</text>
|
|
|
+ <text class="dialogue-tip">输入调整意图(如「睡眠建议改温和一点」),AI 将在当前板块上修改</text>
|
|
|
+ </view>
|
|
|
+ <scroll-view scroll-y class="dialogue-list" v-if="dialogueRounds.length > 0">
|
|
|
+ <view v-for="(r, ri) in dialogueRounds" :key="ri" class="dialogue-row" :class="r.role === 'user' ? 'row-user' : 'row-ai'">
|
|
|
+ <view class="dialogue-bubble" :class="r.role === 'user' ? 'bubble-user' : 'bubble-ai'">
|
|
|
+ <text class="bubble-text">{{ r.text }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </scroll-view>
|
|
|
+ <view class="dialogue-input-row">
|
|
|
+ <input class="dialogue-input" v-model="dialogueIntent" placeholder="如:睡眠建议改温和一点..."
|
|
|
+ confirm-type="send" :disabled="loading" @confirm="sendDialogueAdjust" />
|
|
|
+ <button class="btn-dialogue-send" :disabled="!canSendDialogue"
|
|
|
+ @click="sendDialogueAdjust">{{ loading ? '处理中' : '发送' }}</button>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
<view class="section-actions">
|
|
|
<button class="btn-edit" @click="regenerateCurrentSection" :disabled="loading">🔄 重新生成</button>
|
|
|
<button class="btn-confirm" @click="confirmCurrentSection" :disabled="loading">保存</button>
|
|
|
@@ -226,7 +247,7 @@
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
-import { generateHealthPlan, regenerateSection, confirmPlan, saveHealthPlan, getHealthPlanDetail } from '../../utils/api'
|
|
|
+import { generateHealthPlan, regenerateSection, adjustPlanSection, confirmPlan, saveHealthPlan, getHealthPlanDetail } from '../../utils/api'
|
|
|
import { getHealthStatus, saveHealthStatus, getDietPreferences, saveDietPreferences, getMyMembership, saveHealthSignature } from '../../utils/api'
|
|
|
import MemberChipPicker from '@/pages/health/components/MemberChipPicker.vue'
|
|
|
import ComprehensiveSurvey from '@/pages/health/components/ComprehensiveSurvey.vue'
|
|
|
@@ -268,6 +289,9 @@ export default {
|
|
|
editing: false,
|
|
|
// 当前正在编辑的板块下标(0=nutrition,1=diet,2=exercise),逐板块确认时递增
|
|
|
editIndex: 0,
|
|
|
+ // 对话式调整(HL-04/R4):多轮对话记录 + 输入框
|
|
|
+ dialogueRounds: [],
|
|
|
+ dialogueIntent: '',
|
|
|
// 查看模式
|
|
|
viewMode: false,
|
|
|
planDetail: null,
|
|
|
@@ -310,6 +334,10 @@ export default {
|
|
|
currentSectionHint: function() {
|
|
|
return this.sectionHints[this.editIndex] || ''
|
|
|
},
|
|
|
+ // 对话式调整发送按钮可用性:有调整意图且不在请求中
|
|
|
+ canSendDialogue: function() {
|
|
|
+ return !this.loading && this.dialogueIntent && String(this.dialogueIntent).trim() !== ''
|
|
|
+ },
|
|
|
selectedMembers: function() {
|
|
|
var self = this
|
|
|
var out = []
|
|
|
@@ -1095,7 +1123,7 @@ selectedMembers: function() {
|
|
|
var params = {
|
|
|
section: key,
|
|
|
feedback: self.sectionFeedback || '',
|
|
|
- existing_section_content: existing,
|
|
|
+ planJson: existing,
|
|
|
memberIds: self.selectedMemberIds.join(','),
|
|
|
dimensions: 'body',
|
|
|
goal: self.userGoal.trim()
|
|
|
@@ -1116,6 +1144,50 @@ selectedMembers: function() {
|
|
|
uni.showToast({ title: '请求失败', icon: 'none' })
|
|
|
})
|
|
|
},
|
|
|
+ // 对话式调整(HL-04/R4):自然语言调整意图 → 调后端调整接口 → 刷新当前板块内容
|
|
|
+ sendDialogueAdjust: function() {
|
|
|
+ var self = this
|
|
|
+ var key = self.currentSectionKey
|
|
|
+ var intent = (self.dialogueIntent || '').trim()
|
|
|
+ if (!key || !intent || self.loading) return
|
|
|
+ var current = self.sections[key].content || ''
|
|
|
+ self.dialogueRounds.push({ role: 'user', text: intent })
|
|
|
+ self.dialogueIntent = ''
|
|
|
+ self.loading = true
|
|
|
+ var params = {
|
|
|
+ section: key,
|
|
|
+ intent: intent,
|
|
|
+ existing_section_content: current,
|
|
|
+ memberIds: self.selectedMemberIds.join(','),
|
|
|
+ dimensions: 'body',
|
|
|
+ goal: self.userGoal.trim()
|
|
|
+ }
|
|
|
+ adjustPlanSection(params).then(function(res) {
|
|
|
+ self.loading = false
|
|
|
+ if (res && res.code === 200 && res.data && res.data.content) {
|
|
|
+ self.sections[key].content = res.data.content
|
|
|
+ if (res.data.tasks) {
|
|
|
+ self.sections[key].tasks = res.data.tasks
|
|
|
+ }
|
|
|
+ self.dialogueRounds.push({ role: 'ai', text: '已按「' + intent + '」调整该板块' })
|
|
|
+ uni.showToast({ title: '调整成功', icon: 'success' })
|
|
|
+ } else {
|
|
|
+ var msg = res && res.message ? res.message : '调整失败,请重试'
|
|
|
+ self.dialogueRounds.push({ role: 'ai', text: msg })
|
|
|
+ uni.showToast({ title: msg, icon: 'none' })
|
|
|
+ }
|
|
|
+ }).catch(function() {
|
|
|
+ self.loading = false
|
|
|
+ var msg = '请求失败,请检查网络'
|
|
|
+ self.dialogueRounds.push({ role: 'ai', text: msg })
|
|
|
+ uni.showToast({ title: msg, icon: 'none' })
|
|
|
+ })
|
|
|
+ },
|
|
|
+ // 切换板块/退出编辑时清空对话记录(对话上下文随板块切换失效)
|
|
|
+ resetDialogue: function() {
|
|
|
+ this.dialogueRounds = []
|
|
|
+ this.dialogueIntent = ''
|
|
|
+ },
|
|
|
confirmCurrentSection: function() {
|
|
|
var self = this
|
|
|
var key = self.currentSectionKey
|
|
|
@@ -1130,8 +1202,10 @@ selectedMembers: function() {
|
|
|
if (nextIdx >= 0) {
|
|
|
self.editIndex = nextIdx
|
|
|
self.editing = true
|
|
|
+ self.resetDialogue()
|
|
|
} else {
|
|
|
self.editing = false
|
|
|
+ self.resetDialogue()
|
|
|
}
|
|
|
},
|
|
|
startEdit: function() {
|
|
|
@@ -1143,6 +1217,7 @@ selectedMembers: function() {
|
|
|
this.editIndex = idx
|
|
|
this.editing = true
|
|
|
this.sectionFeedback = ''
|
|
|
+ this.resetDialogue()
|
|
|
},
|
|
|
// 点击预览板块 → 直接打开该板块编辑(R1:方案预览板块可点击进入编辑)
|
|
|
openSectionEditor: function(i) {
|
|
|
@@ -1151,10 +1226,12 @@ selectedMembers: function() {
|
|
|
this.editIndex = idx
|
|
|
this.editing = true
|
|
|
this.sectionFeedback = ''
|
|
|
+ this.resetDialogue()
|
|
|
},
|
|
|
exitEditMode: function() {
|
|
|
this.editing = false
|
|
|
this.sectionFeedback = ''
|
|
|
+ this.resetDialogue()
|
|
|
},
|
|
|
submitPlan: function() {
|
|
|
var self = this
|
|
|
@@ -1592,6 +1669,89 @@ selectedMembers: function() {
|
|
|
flex: 1;
|
|
|
margin-bottom: 0;
|
|
|
}
|
|
|
+/* 对话式调整(HL-04/R4) */
|
|
|
+.dialogue-panel {
|
|
|
+ margin-top: 16rpx;
|
|
|
+ background: $app-bg;
|
|
|
+ border: 2rpx solid $facade-hairline-2;
|
|
|
+ border-radius: 16rpx;
|
|
|
+ padding: 20rpx;
|
|
|
+}
|
|
|
+.dialogue-head {
|
|
|
+ margin-bottom: 12rpx;
|
|
|
+}
|
|
|
+.dialogue-title {
|
|
|
+ font-size: calc(26rpx * var(--font-scale, 1));
|
|
|
+ font-weight: bold;
|
|
|
+ color: $facade-text-strong;
|
|
|
+}
|
|
|
+.dialogue-tip {
|
|
|
+ display: block;
|
|
|
+ font-size: calc(22rpx * var(--font-scale, 1));
|
|
|
+ color: $facade-text-muted;
|
|
|
+ margin-top: 4rpx;
|
|
|
+}
|
|
|
+.dialogue-list {
|
|
|
+ max-height: 320rpx;
|
|
|
+ margin-bottom: 12rpx;
|
|
|
+}
|
|
|
+.dialogue-row {
|
|
|
+ display: flex;
|
|
|
+ margin-bottom: 10rpx;
|
|
|
+}
|
|
|
+.dialogue-row.row-user { justify-content: flex-end; }
|
|
|
+.dialogue-row.row-ai { justify-content: flex-start; }
|
|
|
+.dialogue-bubble {
|
|
|
+ max-width: 88%;
|
|
|
+ border-radius: 16rpx;
|
|
|
+ padding: 14rpx 20rpx;
|
|
|
+}
|
|
|
+.bubble-user {
|
|
|
+ background: $brand-primary;
|
|
|
+ color: $app-surface;
|
|
|
+}
|
|
|
+.bubble-ai {
|
|
|
+ background: $app-surface;
|
|
|
+ border: 2rpx solid $facade-hairline-2;
|
|
|
+ color: $facade-text-strong;
|
|
|
+}
|
|
|
+.bubble-text {
|
|
|
+ font-size: calc(24rpx * var(--font-scale, 1));
|
|
|
+ line-height: 1.6;
|
|
|
+ word-break: break-all;
|
|
|
+}
|
|
|
+.dialogue-input-row {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12rpx;
|
|
|
+}
|
|
|
+.dialogue-input {
|
|
|
+ flex: 1;
|
|
|
+ height: 72rpx;
|
|
|
+ background: $app-surface;
|
|
|
+ border: 2rpx solid $brand-primary;
|
|
|
+ border-radius: 36rpx;
|
|
|
+ padding: 0 24rpx;
|
|
|
+ font-size: calc(26rpx * var(--font-scale, 1));
|
|
|
+ color: $facade-text-strong;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+.btn-dialogue-send {
|
|
|
+ flex-shrink: 0;
|
|
|
+ width: 128rpx;
|
|
|
+ height: 72rpx;
|
|
|
+ line-height: 72rpx;
|
|
|
+ padding: 0;
|
|
|
+ margin: 0;
|
|
|
+ background: linear-gradient(135deg, $brand-primary, $brand-primary-light);
|
|
|
+ color: $app-surface;
|
|
|
+ border: none;
|
|
|
+ border-radius: 36rpx;
|
|
|
+ font-size: calc(26rpx * var(--font-scale, 1));
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+.btn-dialogue-send[disabled] { opacity: 0.4; }
|
|
|
+.btn-dialogue-send::after { border: none; }
|
|
|
.back-link {
|
|
|
text-align: center;
|
|
|
font-size: calc(26rpx * var(--font-scale, 1));
|