feat: glassy chat UI (liquid glass) + collapsed assistant messages + expand; workspace selector + input bar
This commit is contained in:
+287
-22
@@ -3,16 +3,43 @@
|
|||||||
<swiper class="swiper" index="{{currentIndex}}" indicator="false" loop="false"
|
<swiper class="swiper" index="{{currentIndex}}" indicator="false" loop="false"
|
||||||
previousmargin="14px" nextmargin="14px" onchange="onSwipeChange">
|
previousmargin="14px" nextmargin="14px" onchange="onSwipeChange">
|
||||||
|
|
||||||
<!-- 左页:会话 -->
|
<!-- 左页:对话(Harness 式) -->
|
||||||
<div class="page-item left-page">
|
<div class="page-item left-page">
|
||||||
<text class="page-title">会话</text>
|
<!-- 当前会话 / 点开选工作区 -->
|
||||||
<div class="session-list">
|
<text class="page-title" onclick="toggleSelect">对话 · {{currentConv}}</text>
|
||||||
<div class="session-row" for="{{sessions}}" onclick="selectSession($index)">
|
|
||||||
<text class="session-title">{{$item.title}}</text>
|
<!-- 工作区/会话选择器 -->
|
||||||
<text class="session-dot" if="{{$item.running}}">●</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>
|
||||||
</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>
|
</div>
|
||||||
|
|
||||||
<!-- 中页:主页 / 吉祥物(无按钮,点击互动) -->
|
<!-- 中页:主页 / 吉祥物(无按钮,点击互动) -->
|
||||||
@@ -29,15 +56,18 @@
|
|||||||
<text class="stats">今日 {{todayTokens}} tokens · 预估 ¥{{todayCost}}</text>
|
<text class="stats">今日 {{todayTokens}} tokens · 预估 ¥{{todayCost}}</text>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 右页:设置 -->
|
<!-- 右页:丰富设置 -->
|
||||||
<div class="page-item right-page">
|
<div class="page-item right-page">
|
||||||
<text class="page-title">设置</text>
|
<text class="page-title">设置</text>
|
||||||
<text class="setting-label">服务器</text>
|
<div class="setting-row" for="{{settingsList}}" onclick="toggleSetting($index)">
|
||||||
<input class="setting-input" type="text" value="{{settings.serverUrl}}" onchange="onServerChange" />
|
<text class="setting-label">{{$item.label}}</text>
|
||||||
<text class="setting-label">用户ID</text>
|
<text class="setting-value">{{$item.value}}</text>
|
||||||
<input class="setting-input" type="text" value="{{settings.userId}}" onchange="onUserChange" />
|
</div>
|
||||||
<text class="setting-label">Token</text>
|
<text class="section-title">手表端设置</text>
|
||||||
<input class="setting-input" type="text" value="{{settings.token}}" onchange="onTokenChange" />
|
<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>
|
<text class="conn-text">{{connText}}</text>
|
||||||
</div>
|
</div>
|
||||||
</swiper>
|
</swiper>
|
||||||
@@ -78,11 +108,19 @@ function playDuckSound() {
|
|||||||
export default {
|
export default {
|
||||||
private: {
|
private: {
|
||||||
currentIndex: 1,
|
currentIndex: 1,
|
||||||
sessions: [
|
messages: [
|
||||||
{ title: '原型演示会话', running: false },
|
{ role:'user', text:'帮我看看今天有什么任务', preview:'', think:null, kind:'normal', showThink:false, collapsed:false },
|
||||||
{ title: 'UI 对齐中', running: true },
|
{ role:'assistant', text:'好的,今天三件事:1) 回复 DSHWatch 进度;2) 调整手环 UI;3) 准备部署。', preview:'好的,今天三件事:1) 回复 DSHWatch 进度;2) 调整手环…', think:'正在从工作区任务列表整理…', kind:'normal', showThink:true, collapsed:true },
|
||||||
{ title: '待接入 Harness', running: false }
|
{ 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,
|
isPeak: false,
|
||||||
peakLabel: '谷时 · 半价',
|
peakLabel: '谷时 · 半价',
|
||||||
balance: '--',
|
balance: '--',
|
||||||
@@ -99,6 +137,21 @@ export default {
|
|||||||
token: ''
|
token: ''
|
||||||
},
|
},
|
||||||
connText: '手机/手表未连接',
|
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: [
|
bubblePresets: [
|
||||||
'我算得比你快,但没你可爱。',
|
'我算得比你快,但没你可爱。',
|
||||||
'上轮消耗 ¥0.08,钱包还撑得住。',
|
'上轮消耗 ¥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() {
|
refreshPeak() {
|
||||||
// 用北京时间计算峰谷;周六/周日全天谷时
|
// 用北京时间计算峰谷;周六/周日全天谷时
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
@@ -326,13 +423,14 @@ export default {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 18px 20px;
|
padding: 18px 20px;
|
||||||
border-radius: 22px;
|
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 {
|
.bubble-text {
|
||||||
color: #1c2c47;
|
color: #e6f0ff;
|
||||||
font-size: 18px;
|
font-size: 17px;
|
||||||
line-height: 26px;
|
line-height: 24px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -380,4 +478,171 @@ export default {
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background-color: #33b4ff;
|
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>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user