feat: glassy chat UI (liquid glass) + collapsed assistant messages + expand; workspace selector + input bar

This commit is contained in:
2026-09-01 22:21:07 +08:00
parent 44c209cd1b
commit 1dbf3810ad
+287 -22
View File
@@ -3,16 +3,43 @@
<swiper class="swiper" index="{{currentIndex}}" indicator="false" loop="false"
previousmargin="14px" nextmargin="14px" onchange="onSwipeChange">
<!-- 左页:会话 -->
<!-- 左页:对话(Harness 式) -->
<div class="page-item left-page">
<text class="page-title">会话</text>
<div class="session-list">
<div class="session-row" for="{{sessions}}" onclick="selectSession($index)">
<text class="session-title">{{$item.title}}</text>
<text class="session-dot" if="{{$item.running}}">●</text>
<!-- 当前会话 / 点开选工作区 -->
<text class="page-title" onclick="toggleSelect">对话 · {{currentConv}}</text>
<!-- 工作区/会话选择器 -->
<div class="select-overlay" if="{{showSelect}}">
<div class="select-ws" for="{{workspaces}}" onclick="toggleSelect">
<text class="select-name">{{$item.name}}</text>
<div class="select-conv" for="{{$item.conversations}}" onclick="selectConversation($index,$item)">
<text class="select-conv-text">{{$item}}</text>
</div>
</div>
</div>
<text class="page-hint" if="{{!sessions.length}}">暂无会话</text>
<!-- 消息流 -->
<div class="chat-list">
<div class="chat-msg {{$item.role === 'user' ? 'msg-user' : 'msg-assistant'}}" for="{{messages}}">
<div class="think-chip" if="{{$item.think}}" onclick="toggleThink($index)">
<text>💭 思考…</text>
</div>
<text class="think-text" if="{{$item.showThink}}">{{$item.think}}</text>
<text class="chat-text">{{$item.collapsed ? $item.preview : $item.text}}</text>
<text class="expand-chip" if="{{$item.collapsed}}" onclick="toggleCollapse($index)">展开 ▾</text>
<text class="expand-chip" if="{{!$item.collapsed}}" onclick="toggleCollapse($index)">收起 ▴</text>
<div class="approve-box" if="{{$item.kind === 'approval'}}">
<input class="approve-btn allow" type="button" value="允许" onclick="approve($index)" />
<input class="approve-btn deny" type="button" value="拒绝" onclick="deny($index)" />
</div>
</div>
</div>
<!-- 底部输入栏 -->
<div class="input-bar">
<input class="input-field" type="text" value="{{inputText}}" placeholder="说点什么…" onchange="onInputChange" />
<input class="input-send" type="button" value="输入" onclick="openInput" />
</div>
</div>
<!-- 中页:主页 / 吉祥物(无按钮,点击互动) -->
@@ -29,15 +56,18 @@
<text class="stats">今日 {{todayTokens}} tokens · 预估 ¥{{todayCost}}</text>
</div>
<!-- 右页:设置 -->
<!-- 右页:丰富设置 -->
<div class="page-item right-page">
<text class="page-title">设置</text>
<text class="setting-label">服务器</text>
<input class="setting-input" type="text" value="{{settings.serverUrl}}" onchange="onServerChange" />
<text class="setting-label">用户ID</text>
<input class="setting-input" type="text" value="{{settings.userId}}" onchange="onUserChange" />
<text class="setting-label">Token</text>
<input class="setting-input" type="text" value="{{settings.token}}" onchange="onTokenChange" />
<div class="setting-row" for="{{settingsList}}" onclick="toggleSetting($index)">
<text class="setting-label">{{$item.label}}</text>
<text class="setting-value">{{$item.value}}</text>
</div>
<text class="section-title">手表端设置</text>
<div class="setting-row" for="{{watchSettings}}" onclick="toggleSetting($index)">
<text class="setting-label">{{$item.label}}</text>
<text class="setting-value">{{$item.value}}</text>
</div>
<text class="conn-text">{{connText}}</text>
</div>
</swiper>
@@ -78,11 +108,19 @@ function playDuckSound() {
export default {
private: {
currentIndex: 1,
sessions: [
{ title: '原型演示会话', running: false },
{ title: 'UI 对齐中', running: true },
{ title: '待接入 Harness', running: false }
messages: [
{ role:'user', text:'帮我看看今天有什么任务', preview:'', think:null, kind:'normal', showThink:false, collapsed:false },
{ role:'assistant', text:'好的,今天三件事:1) 回复 DSHWatch 进度;2) 调整手环 UI;3) 准备部署。', preview:'好的,今天三件事:1) 回复 DSHWatch 进度;2) 调整手环…', think:'正在从工作区任务列表整理…', kind:'normal', showThink:true, collapsed:true },
{ role:'assistant', text:'需要一个权限:允许运行网络请求到 https://api.deepseek.com/user/balance', preview:'', think:null, kind:'approval', showThink:false, collapsed:false }
],
workspaces: [
{ name:'DeepSeek_Harness_Watch', conversations:['小米手环10 Pro远程控制','DSHWatch 验收'] },
{ name:'reactor', conversations:['任务-001','任务-002'] },
{ name:'truestudio.online', conversations:['评估-今天'] }
],
currentConv: '小米手环10 Pro远程控制',
showSelect: false,
inputText: '',
isPeak: false,
peakLabel: '谷时 · 半价',
balance: '--',
@@ -99,6 +137,21 @@ export default {
token: ''
},
connText: '手机/手表未连接',
settingsList: [
{ label:'模型', value:'DeepSeek-V4-Flash' },
{ label:'提供方', value:'DeepSeek 官方' },
{ label:'推理强度', value:'高' },
{ label:'权限模式', value:'Workspace Write' },
{ label:'对话模式', value:'深神模式' },
{ label:'语言', value:'中文' },
{ label:'外观', value:'深色' },
{ label:'Enter 行为', value:'插话发送' }
],
watchSettings: [
{ label:'手环连接', value:'未连接(占位)' },
{ label:'服务器', value:'ws://127.0.0.1:8787' },
{ label:'用户ID', value:'--' }
],
bubblePresets: [
'我算得比你快,但没你可爱。',
'上轮消耗 ¥0.08,钱包还撑得住。',
@@ -137,6 +190,50 @@ export default {
}
},
toggleSelect() {
this.showSelect = !this.showSelect
},
selectConversation(convIndex, convName) {
this.currentConv = convName || this.currentConv
this.showSelect = false
},
toggleThink(index) {
const m = this.messages[index]
if (m) m.showThink = !m.showThink
},
toggleCollapse(index) {
const m = this.messages[index]
if (m) m.collapsed = !m.collapsed
},
approve(index) {
const m = this.messages[index]
if (m) { m.kind = 'done'; m.text = '已允许 ✓' }
},
deny(index) {
const m = this.messages[index]
if (m) { m.kind = 'done'; m.text = '已拒绝 ✕' }
},
openInput() {
// 占位:后续调用官方输入法组件
this.inputText = ''
},
onInputChange(e) {
this.inputText = e && e.value || ''
},
toggleSetting(index) {
const all = [...this.settingsList, ...this.watchSettings]
const row = all[index]
if (row) row.value += ' ·'
},
refreshPeak() {
// 用北京时间计算峰谷;周六/周日全天谷时
const now = Date.now()
@@ -326,13 +423,14 @@ export default {
flex-direction: column;
padding: 18px 20px;
border-radius: 22px;
background-color: #ffffff;
background-color: rgba(20, 32, 52, 0.86);
border: 1px solid rgba(255, 255, 255, 0.14);
}
.bubble-text {
color: #1c2c47;
font-size: 18px;
line-height: 26px;
color: #e6f0ff;
font-size: 17px;
line-height: 24px;
text-align: center;
}
@@ -380,4 +478,171 @@ export default {
border-radius: 4px;
background-color: #33b4ff;
}
/* ===== 左页:对话 ===== */
.chat-list {
flex: 1;
width: 100%;
flex-direction: column;
overflow: hidden;
}
.chat-msg {
width: 100%;
margin-bottom: 6px;
padding: 7px 9px;
border-radius: 10px;
}
.msg-user {
background-color: rgba(51, 180, 255, 0.18);
border: 1px solid rgba(120, 200, 255, 0.28);
}
.msg-assistant {
background-color: rgba(22, 34, 56, 0.58);
border: 1px solid rgba(255, 255, 255, 0.10);
}
.chat-text {
color: #f0f6ff;
font-size: 15px;
line-height: 22px;
}
.think-chip {
background-color: rgba(28, 44, 71, 0.7);
border: 1px solid rgba(255,255,255,0.08);
border-radius: 6px;
padding: 4px;
margin-bottom: 4px;
color: #9db3d8;
font-size: 12px;
}
.think-text {
color: #9db3d8;
font-size: 12px;
line-height: 18px;
margin-bottom: 4px;
}
.expand-chip {
margin-top: 4px;
color: #33b4ff;
font-size: 12px;
}
.approve-box {
flex-direction: row;
margin-top: 6px;
gap: 8px;
}
.approve-btn {
width: 82px;
height: 34px;
border-radius: 17px;
font-size: 14px;
text-align: center;
}
.approve-btn.allow {
background-color: #33b4ff;
color: #08131f;
}
.approve-btn.deny {
background-color: #ff5b5b;
color: #ffffff;
}
.input-bar {
width: 100%;
flex-direction: row;
align-items: center;
margin-top: 6px;
gap: 6px;
}
.input-field {
flex: 1;
height: 36px;
border-radius: 8px;
background-color: #152238;
color: #e6f0ff;
font-size: 14px;
padding: 0 8px;
}
.input-send {
width: 64px;
height: 36px;
border-radius: 18px;
background-color: #33b4ff;
color: #08131f;
font-size: 15px;
text-align: center;
}
.select-overlay {
width: 100%;
margin-bottom: 8px;
padding: 8px;
border-radius: 10px;
background-color: #0f1a2c;
}
.select-ws {
margin-bottom: 8px;
}
.select-name {
color: #e6f0ff;
font-size: 15px;
font-weight: bold;
}
.select-conv {
padding: 6px 8px;
margin-top: 4px;
border-radius: 8px;
background-color: #152238;
}
.select-conv-text {
color: #b7c8e8;
font-size: 14px;
}
/* ===== 右页:设置 ===== */
.setting-row {
width: 100%;
height: 34px;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 0 8px;
border-radius: 8px;
background-color: #152238;
margin-bottom: 4px;
}
.setting-label {
color: #8fa4c9;
font-size: 13px;
}
.setting-value {
color: #dbe8ff;
font-size: 13px;
}
.section-title {
color: #33b4ff;
font-size: 14px;
font-weight: bold;
margin-top: 8px;
margin-bottom: 4px;
}
</style>