1415 lines
50 KiB
XML
1415 lines
50 KiB
XML
<template>
|
||
<div class="page">
|
||
<swiper class="swiper" id="mainSwiper" index="{{currentIndex}}" indicator="false" loop="false"
|
||
previousmargin="0px" nextmargin="0px" onchange="onSwipeChange">
|
||
|
||
<!-- 最左页:选择对话 / 工作区(一行一条,标题左 / 时间右) -->
|
||
<div class="page-item left-page">
|
||
<text class="page-title">选择对话</text>
|
||
<input class="new-session" type="button" value="+ 新会话" onclick="newSession" />
|
||
<text class="page-title small">工作区</text>
|
||
<text class="ws-name">📁 DeepSeek_Harness_Watch</text>
|
||
<div class="sel-conv {{active0 ? 'sel-active' : ''}}">
|
||
<text class="sel-title fl{{fontLevel}}" onclick="pickConv0">小米手环10 Pro远程控制</text>
|
||
<text class="sel-time" onclick="pickConv0">刚刚</text>
|
||
</div>
|
||
<div class="sel-conv {{active1 ? 'sel-active' : ''}}">
|
||
<text class="sel-title fl{{fontLevel}}" onclick="pickConv1">DSHWatch 验收</text>
|
||
<text class="sel-time" onclick="pickConv1">1小时</text>
|
||
</div>
|
||
<text class="ws-name">📁 reactor</text>
|
||
<div class="sel-conv {{active2 ? 'sel-active' : ''}}">
|
||
<text class="sel-title fl{{fontLevel}}" onclick="pickConv2">排查Git文件乱码问题</text>
|
||
<text class="sel-time" onclick="pickConv2">15天</text>
|
||
</div>
|
||
<text class="ws-name">📁 project</text>
|
||
<div class="sel-conv {{active3 ? 'sel-active' : ''}}">
|
||
<text class="sel-title fl{{fontLevel}}" onclick="pickConv3">修改API配置为固定密钥</text>
|
||
<text class="sel-time" onclick="pickConv3">8天</text>
|
||
</div>
|
||
<div class="sel-conv {{active4 ? 'sel-active' : ''}}">
|
||
<text class="sel-title fl{{fontLevel}}" onclick="pickConv4">检测DeepSeek API可用性</text>
|
||
<text class="sel-time" onclick="pickConv4">9天</text>
|
||
</div>
|
||
<text class="ws-name">📁 truestudio.online</text>
|
||
<div class="sel-conv {{active5 ? 'sel-active' : ''}}">
|
||
<text class="sel-title fl{{fontLevel}}" onclick="pickConv5">评估一下项目状态</text>
|
||
<text class="sel-time" onclick="pickConv5">9天</text>
|
||
</div>
|
||
<text class="ws-name">📁 tama-project</text>
|
||
<div class="sel-conv {{active6 ? 'sel-active' : ''}}">
|
||
<text class="sel-title fl{{fontLevel}}" onclick="pickConv6">旧站交接与维护指南</text>
|
||
<text class="sel-time" onclick="pickConv6">2天</text>
|
||
</div>
|
||
<div class="sel-conv {{active7 ? 'sel-active' : ''}}">
|
||
<text class="sel-title fl{{fontLevel}}" onclick="pickConv7">O-COM 云端模式开发交接</text>
|
||
<text class="sel-time" onclick="pickConv7">1小时</text>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 左页:对话(Harness 式) -->
|
||
<div class="page-item left-page">
|
||
<!-- 当前会话 / 点开选工作区 -->
|
||
<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>
|
||
|
||
<!-- 消息流 -->
|
||
<scroll class="chat-scroll" scroll-y="true">
|
||
<div class="msg-row {{$item.role == 'user' ? 'row-user' : 'row-assistant'}}" for="{{messages}}">
|
||
<div class="chat-msg {{$item.role == 'user' ? 'msg-user' : 'msg-assistant'}}">
|
||
<div class="think-chip" if="{{$item.think}}">
|
||
<text>💭 思考…</text>
|
||
</div>
|
||
<text class="think-text fl{{fontLevel}}" if="{{$item.showThink}}">{{$item.think}}</text>
|
||
<text class="chat-text fl{{fontLevel}}">{{$item.collapsed ? $item.preview : $item.text}}</text>
|
||
<text class="expand-chip" if="{{$item.role == 'assistant' && $item.collapsed}}">展开 ▾</text>
|
||
<text class="expand-chip" if="{{$item.role == 'assistant' && !$item.collapsed}}">收起 ▴</text>
|
||
<image class="chat-img" src="/common/dsniang.png" if="{{$item.kind == 'image'}}" />
|
||
<div class="approve-box" if="{{$item.kind == 'approval'}}">
|
||
<input class="approve-btn allow" type="button" value="允许" />
|
||
<input class="approve-btn deny" type="button" value="拒绝" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</scroll>
|
||
|
||
<!-- 底部输入栏 -->
|
||
<div class="input-bar">
|
||
<input class="input-field" type="text" value="{{inputText}}" placeholder="说点什么…" onclick="openInput" />
|
||
<input class="input-send" type="button" value="输入" onclick="openInput" />
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 中页:主页 / 吉祥物(无按钮,点击互动) -->
|
||
<div class="page-item center-page">
|
||
<!-- 气泡区:始终占位,让气泡在头像正上方居中;只显示趣味/上轮消耗信息 -->
|
||
<div class="bubble-zone">
|
||
<div class="bubble" if="{{bubbleVisible}}">
|
||
<text class="bubble-text">{{bubbleText}}</text>
|
||
</div>
|
||
</div>
|
||
<image class="mascot step{{animStep}}" src="/common/dsniang.png" onclick="onTap" />
|
||
<text class="peak {{isPeak ? 'peak-red' : 'peak-green'}}">{{peakLabel}}</text>
|
||
<text class="stats">余额 {{balance}}</text>
|
||
<text class="stats">今日 {{todayTokens}} tokens · 预估 ¥{{todayCost}}</text>
|
||
</div>
|
||
|
||
<!-- 右页:丰富设置 -->
|
||
<div class="page-item right-page">
|
||
<text class="page-title">设置</text>
|
||
<div class="setting-row" onclick="toggleModel">
|
||
<text class="setting-label fl{{fontLevel}}">模型</text>
|
||
<text class="setting-value fl{{fontLevel}}">{{modelValue}}</text>
|
||
</div>
|
||
<div class="setting-row" onclick="toggleProvider">
|
||
<text class="setting-label fl{{fontLevel}}">提供方</text>
|
||
<text class="setting-value fl{{fontLevel}}">{{providerValue}}</text>
|
||
</div>
|
||
<div class="setting-row" onclick="toggleReason">
|
||
<text class="setting-label fl{{fontLevel}}">推理强度</text>
|
||
<text class="setting-value fl{{fontLevel}}">{{reasonValue}}</text>
|
||
</div>
|
||
<div class="setting-row" onclick="togglePerm">
|
||
<text class="setting-label fl{{fontLevel}}">权限模式</text>
|
||
<text class="setting-value fl{{fontLevel}}">{{permValue}}</text>
|
||
</div>
|
||
<div class="setting-row" onclick="toggleDialog">
|
||
<text class="setting-label fl{{fontLevel}}">对话模式</text>
|
||
<text class="setting-value fl{{fontLevel}}">{{dialogModeValue}}</text>
|
||
</div>
|
||
<text class="section-title fl{{fontLevel}}">手表端设置</text>
|
||
<div class="setting-row" onclick="toggleWatchConn">
|
||
<text class="setting-label fl{{fontLevel}}">手环连接</text>
|
||
<text class="setting-value fl{{fontLevel}}">{{watchConnValue}}</text>
|
||
</div>
|
||
<div class="setting-row" onclick="toggleServer">
|
||
<text class="setting-label fl{{fontLevel}}">服务器</text>
|
||
<text class="setting-value fl{{fontLevel}}">{{serverValue}}</text>
|
||
</div>
|
||
<div class="setting-row" onclick="openFontPicker">
|
||
<text class="setting-label fl{{fontLevel}}">字体大小</text>
|
||
<text class="setting-value fl{{fontLevel}}">{{fontLabel}}</text>
|
||
</div>
|
||
<text class="conn-text">{{connText}}</text>
|
||
</div>
|
||
</swiper>
|
||
|
||
<div class="dots">
|
||
<div class="dot {{currentIndex == 0 ? 'dot-active' : ''}}"></div>
|
||
<div class="dot {{currentIndex == 1 ? 'dot-active' : ''}}"></div>
|
||
<div class="dot {{currentIndex == 2 ? 'dot-active' : ''}}"></div>
|
||
<div class="dot {{currentIndex == 3 ? 'dot-active' : ''}}"></div>
|
||
</div>
|
||
|
||
<!-- 软键盘弹层:点空白收起;中/英切换;汉字为常用字(非预置句) -->
|
||
<div class="kb-backdrop" if="{{showKeyboard}}" onclick="kbClose"></div>
|
||
<div class="keyboard" if="{{showKeyboard}}">
|
||
<div class="kb-input">
|
||
<text class="kb-text">{{inputText}}</text>
|
||
</div>
|
||
<div class="kb-pinyin" if="{{pinyinMode && pinyinBuf}}"><text>{{pinyinBuf}}</text></div>
|
||
<div class="kb-cands" if="{{pinyinMode && candidates.length}}">
|
||
<text class="kb-cand" onclick="pickCand0">{{candidates[0]}}</text>
|
||
<text class="kb-cand" onclick="pickCand1">{{candidates[1]}}</text>
|
||
<text class="kb-cand" onclick="pickCand2">{{candidates[2]}}</text>
|
||
<text class="kb-cand" onclick="pickCand3">{{candidates[3]}}</text>
|
||
</div>
|
||
<div class="kb-keys">
|
||
<!-- 英文模式 -->
|
||
<div class="kb-row" if="{{kbMode == 'en'}}">
|
||
<text class="kb-key" onclick="kbKey('q')">q</text><text class="kb-key" onclick="kbKey('w')">w</text><text class="kb-key" onclick="kbKey('e')">e</text><text class="kb-key" onclick="kbKey('r')">r</text><text class="kb-key" onclick="kbKey('t')">t</text><text class="kb-key" onclick="kbKey('y')">y</text><text class="kb-key" onclick="kbKey('u')">u</text><text class="kb-key" onclick="kbKey('i')">i</text><text class="kb-key" onclick="kbKey('o')">o</text><text class="kb-key" onclick="kbKey('p')">p</text>
|
||
</div>
|
||
<div class="kb-row" if="{{kbMode == 'en'}}">
|
||
<text class="kb-key" onclick="kbKey('a')">a</text><text class="kb-key" onclick="kbKey('s')">s</text><text class="kb-key" onclick="kbKey('d')">d</text><text class="kb-key" onclick="kbKey('f')">f</text><text class="kb-key" onclick="kbKey('g')">g</text><text class="kb-key" onclick="kbKey('h')">h</text><text class="kb-key" onclick="kbKey('j')">j</text><text class="kb-key" onclick="kbKey('k')">k</text><text class="kb-key" onclick="kbKey('l')">l</text>
|
||
</div>
|
||
<div class="kb-row" if="{{kbMode == 'en'}}">
|
||
<text class="kb-key" onclick="kbKey('z')">z</text><text class="kb-key" onclick="kbKey('x')">x</text><text class="kb-key" onclick="kbKey('c')">c</text><text class="kb-key" onclick="kbKey('v')">v</text><text class="kb-key" onclick="kbKey('b')">b</text><text class="kb-key" onclick="kbKey('n')">n</text><text class="kb-key" onclick="kbKey('m')">m</text>
|
||
</div>
|
||
<div class="kb-row" if="{{kbMode == 'en'}}">
|
||
<text class="kb-key" onclick="kbKey('1')">1</text><text class="kb-key" onclick="kbKey('2')">2</text><text class="kb-key" onclick="kbKey('3')">3</text><text class="kb-key" onclick="kbKey('4')">4</text><text class="kb-key" onclick="kbKey('5')">5</text><text class="kb-key" onclick="kbKey('6')">6</text><text class="kb-key" onclick="kbKey('7')">7</text><text class="kb-key" onclick="kbKey('8')">8</text><text class="kb-key" onclick="kbKey('9')">9</text><text class="kb-key" onclick="kbKey('0')">0</text>
|
||
</div>
|
||
<!-- 中文常用字模式 -->
|
||
<div class="kb-row" if="{{kbMode == 'zh'}}">
|
||
<text class="kb-key kb-char" onclick="kbChar('一')">一</text><text class="kb-key kb-char" onclick="kbChar('的')">的</text><text class="kb-key kb-char" onclick="kbChar('是')">是</text><text class="kb-key kb-char" onclick="kbChar('了')">了</text><text class="kb-key kb-char" onclick="kbChar('我')">我</text><text class="kb-key kb-char" onclick="kbChar('不')">不</text><text class="kb-key kb-char" onclick="kbChar('人')">人</text><text class="kb-key kb-char" onclick="kbChar('在')">在</text><text class="kb-key kb-char" onclick="kbChar('他')">他</text><text class="kb-key kb-char" onclick="kbChar('有')">有</text>
|
||
</div>
|
||
<div class="kb-row" if="{{kbMode == 'zh'}}">
|
||
<text class="kb-key kb-char" onclick="kbChar('这')">这</text><text class="kb-key kb-char" onclick="kbChar('中')">中</text><text class="kb-key kb-char" onclick="kbChar('大')">大</text><text class="kb-key kb-char" onclick="kbChar('为')">为</text><text class="kb-key kb-char" onclick="kbChar('上')">上</text><text class="kb-key kb-char" onclick="kbChar('国')">国</text><text class="kb-key kb-char" onclick="kbChar('个')">个</text><text class="kb-key kb-char" onclick="kbChar('到')">到</text><text class="kb-key kb-char" onclick="kbChar('说')">说</text><text class="kb-key kb-char" onclick="kbChar('们')">们</text>
|
||
</div>
|
||
<div class="kb-row">
|
||
<text class="kb-key kb-fn" onclick="kbTogglePy">{{pinyinMode ? 'Abc' : '拼'}}</text>
|
||
<text class="kb-key kb-fn" onclick="kbSpace">空格</text>
|
||
<text class="kb-key kb-fn" onclick="kbBack">退格</text>
|
||
<text class="kb-key kb-fn" onclick="kbClose">收起</text>
|
||
<text class="kb-key kb-fn kb-send" onclick="kbSend">发送</text>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 选项选择弹窗 -->
|
||
<div class="popup" if="{{showOptionPicker}}">
|
||
<div class="popup-box">
|
||
<text class="popup-title">{{pickerTitle}}</text>
|
||
<div class="picker-option {{pickerIndex == 0 ? 'picker-active' : ''}}" if="{{pickerOptions[0]}}" onclick="pick0"><text>{{pickerOptions[0]}}</text></div>
|
||
<div class="picker-option {{pickerIndex == 1 ? 'picker-active' : ''}}" if="{{pickerOptions[1]}}" onclick="pick1"><text>{{pickerOptions[1]}}</text></div>
|
||
<div class="picker-option {{pickerIndex == 2 ? 'picker-active' : ''}}" if="{{pickerOptions[2]}}" onclick="pick2"><text>{{pickerOptions[2]}}</text></div>
|
||
<div class="picker-option {{pickerIndex == 3 ? 'picker-active' : ''}}" if="{{pickerOptions[3]}}" onclick="pick3"><text>{{pickerOptions[3]}}</text></div>
|
||
<div class="popup-actions">
|
||
<input class="popup-btn" type="button" value="确定" onclick="applyPicker" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 字体大小弹窗 -->
|
||
<div class="popup" if="{{showFontPicker}}">
|
||
<div class="popup-box">
|
||
<text class="popup-title">字体大小</text>
|
||
<slider class="font-slider" min="14" max="22" step="2" value="{{fontValue}}" onchange="onFontChange" />
|
||
<text class="popup-value">{{fontLabel}}</text>
|
||
<div class="popup-actions">
|
||
<input class="popup-btn" type="button" value="确定" onclick="confirmFont" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import bridge from '../../common/bridge.js'
|
||
|
||
// 懒加载 @system.audio,避免模拟器/无扬声器时崩
|
||
let audioModule = null
|
||
let audioTried = false
|
||
function loadAudio() {
|
||
if (audioTried) return audioModule
|
||
audioTried = true
|
||
try {
|
||
audioModule = require('@system.audio')
|
||
} catch (e) {
|
||
audioModule = null
|
||
}
|
||
return audioModule
|
||
}
|
||
function playDuckSound() {
|
||
const a = loadAudio()
|
||
if (!a) return
|
||
try {
|
||
a.src = '/common/Ya1.mp3'
|
||
a.play()
|
||
} catch (e) {}
|
||
}
|
||
|
||
export default {
|
||
private: {
|
||
currentIndex: 2,
|
||
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:'user', text:'第一件事具体说说', preview:'', think:null, kind:'normal', showThink:false, collapsed:false },
|
||
{ role:'assistant', text:'已把进度整理成:1)修复白屏(已提交) 2)布局放大(已提交) 3)待真机验证', preview:'已把进度整理成:1)修复白屏…', think:'汇总最近 5 次提交…', kind:'normal', showThink:true, collapsed:true },
|
||
{ role:'assistant', text:'需要一个权限:允许运行网络请求到 https://api.deepseek.com/user/balance', preview:'', think:null, kind:'approval', showThink:false, collapsed:false },
|
||
{ role:'user', text:'允许', preview:'', think:null, kind:'normal', showThink:false, collapsed:false },
|
||
{ role:'assistant', text:'已查询到余额 ¥6.20,今日已用 ¥12.08。', preview:'', think:'正在调用 DeepSeek user/balance…', kind:'normal', showThink:true, collapsed:false },
|
||
{ role:'user', text:'帮我生成一张手环 UI 草图', preview:'', think:null, kind:'normal', showThink:false, collapsed:false },
|
||
{ role:'assistant', text:'生成了一张「手表主界面」草图,点击可放大。', preview:'生成了一张「手表主界面」草图…', think:'正在渲染图像…', kind:'image', showThink:true, collapsed:false },
|
||
{ role:'assistant', text:'另外建议把左边做成对话流、右边放设置,中间吉祥物可交互——这样最贴合你说的“把 Harness 串流过来”。', preview:'另外建议把左边做成对话流、右边放设置…', think:'正在思考布局建议…', kind:'normal', showThink:true, collapsed:true },
|
||
{ role:'system', text:'会话已切换工作区:reactor', preview:'', think:null, kind:'normal', showThink:false, collapsed:false },
|
||
{ role:'assistant', text:'正在调用工具 bash 执行 npm run build…', preview:'正在调用工具 bash…', think:null, kind:'tool', tag:'🔧 工具调用', showThink:false, collapsed:true },
|
||
{ role:'assistant', text:'构建失败:UxLoader 错误,缺少 system.storage 特性。', preview:'构建失败:UxLoader 错误…', think:null, kind:'error', tag:'❌ 错误', showThink:false, collapsed:false },
|
||
{ role:'assistant', text:'方案一:三页结构;方案二:四页+浮动层。', preview:'方案一:三页结构;方案二:四页+浮动层…', think:'正在比较两种布局…', kind:'plan', tag:'🧭 计划', showThink:true, collapsed:true },
|
||
{ role:'assistant', text:'const a = 1;\nconsole.log(a);', preview:'const a = 1;…', think:null, kind:'code', tag:'💻 代码', showThink:false, collapsed:true },
|
||
{ role:'assistant', text:'模型 | 价格 | 状态\nFlash | 0.1 | 可用', preview:'模型 | 价格 | 状态…', think:null, kind:'table', tag:'📊 表格', showThink:false, collapsed:true },
|
||
{ role:'assistant', text:'「把 Harness 串流过来」这个目标,建议先做最小闭环。', preview:'「把 Harness 串流过来」…', think:null, kind:'normal', showThink:false, collapsed:true }
|
||
],
|
||
workspaces: [
|
||
{ name:'DeepSeek_Harness_Watch', conversations:['小米手环10 Pro远程控制','DSHWatch 验收'] },
|
||
{ name:'reactor', conversations:['任务-001','任务-002'] },
|
||
{ name:'truestudio.online', conversations:['评估-今天'] }
|
||
],
|
||
workTree: [
|
||
{ name:'DeepSeek_Harness_Watch', convs:[ { title:'小米手环10 Pro远程控制', time:'刚刚' }, { title:'DSHWatch 验收', time:'1小时' } ] },
|
||
{ name:'reactor', convs:[ { title:'排查Git文件乱码问题', time:'15天' } ] },
|
||
{ name:'project', convs:[ { title:'修改API配置为固定密钥', time:'8天' }, { title:'检测DeepSeek API可用性', time:'9天' } ] },
|
||
{ name:'truestudio.online', convs:[ { title:'评估一下项目状态', time:'9天' } ] }
|
||
],
|
||
currentConv: '小米手环10 Pro远程控制',
|
||
convMsgs: {},
|
||
active0: true,
|
||
active1: false,
|
||
active2: false,
|
||
active3: false,
|
||
active4: false,
|
||
active5: false,
|
||
active6: false,
|
||
active7: false,
|
||
showSelect: false,
|
||
inputText: '',
|
||
isPeak: false,
|
||
peakLabel: '谷时 · 半价',
|
||
balance: '--',
|
||
todayTokens: '--',
|
||
todayCost: '--',
|
||
bubbleVisible: false,
|
||
bubbleText: '',
|
||
bubbleIndex: 0,
|
||
animStep: 0,
|
||
animLock: false,
|
||
settings: {
|
||
serverUrl: 'ws://127.0.0.1:8787',
|
||
userId: '',
|
||
token: ''
|
||
},
|
||
connText: '手机/手表未连接',
|
||
modelOptions: ['DeepSeek-V4-Flash','DeepSeek-V4-Pro','deepseek-chat','deepseek-reasoner'],
|
||
modelIdx: 0,
|
||
modelValue: 'DeepSeek-V4-Flash',
|
||
providerOptions: ['DeepSeek 官方','Ollama','OpenAI 兼容','DeepSeek 中转'],
|
||
providerIdx: 0,
|
||
providerValue: 'DeepSeek 官方',
|
||
reasonOptions: ['高','中','低','关闭'],
|
||
reasonIdx: 0,
|
||
reasonValue: '高',
|
||
permOptions: ['Workspace Write','Read Only','Ask Once','Full Access'],
|
||
permIdx: 0,
|
||
permValue: 'Workspace Write',
|
||
dialogOptions: ['对话模式','插话模式','深神模式'],
|
||
dialogIdx: 0,
|
||
dialogModeValue: '对话模式',
|
||
connOptions: ['未连接','已连接'],
|
||
connIdx: 0,
|
||
watchConnValue: '未连接',
|
||
serverOptions: ['ws://127.0.0.1:8787','ws://frp-host:18321'],
|
||
serverIdx: 0,
|
||
serverValue: 'ws://127.0.0.1:8787',
|
||
showFontPicker: false,
|
||
showKeyboard: false,
|
||
kbMode: 'en',
|
||
pinyinMode: false,
|
||
pinyinBuf: '',
|
||
candidates: [],
|
||
pinDict: {
|
||
'ni': ['你','呢','尼','泥'],
|
||
'hao': ['好','号','豪','耗'],
|
||
'wo': ['我','握','沃','卧'],
|
||
'shi': ['是','时','事','十','使','式'],
|
||
'de': ['的','得','地','德'],
|
||
'zai': ['在','再','载','仔'],
|
||
'bu': ['不','部','布','步'],
|
||
'le': ['了','乐','勒','累'],
|
||
'you': ['有','又','由','油','游'],
|
||
'he': ['和','合','河','喝','盒'],
|
||
'ta': ['他','她','它','塔'],
|
||
'men': ['们','门','闷','焖'],
|
||
'zhong': ['中','种','重','钟','衷'],
|
||
'guo': ['国','过','果','裹'],
|
||
'ge': ['个','各','歌','格','隔'],
|
||
'zhang': ['长','张','章','涨','账'],
|
||
'xiang': ['想','向','相','项','像'],
|
||
'kan': ['看','砍','坎','侃'],
|
||
'xian': ['先','现','线','县','显'],
|
||
'huo': ['或','活','火','伙','货'],
|
||
'qing': ['请','情','清','轻','青'],
|
||
'qu': ['去','取','区','曲','趣'],
|
||
'shuo': ['说','硕','烁','朔'],
|
||
'wen': ['问','文','闻','温','稳'],
|
||
'shen': ['什','深','身','神','甚'],
|
||
'me': ['么','吗','么','摩'],
|
||
'zhi': ['之','只','知','直','制'],
|
||
'dao': ['到','道','倒','岛','导'],
|
||
'jin': ['今','金','进','近','仅'],
|
||
'tian': ['天','田','添','填','甜'],
|
||
'xing': ['行','星','型','形','姓'],
|
||
'neng': ['能','宁','凝'],
|
||
'can': ['参','残','餐','灿'],
|
||
'yi': ['一','以','已','意','义','易'],
|
||
'er': ['二','而','儿','耳','尔'],
|
||
'san': ['三','散','伞','伞'],
|
||
'ri': ['日','入','肉'],
|
||
'yue': ['月','越','乐','阅'],
|
||
'zhe': ['这','着','哲','折','者'],
|
||
'xia': ['下','夏','吓','虾'],
|
||
'shang': ['上','尚','商','伤'],
|
||
'liao': ['了','聊','料','廖'],
|
||
'wenxin': ['问心','文心'],
|
||
'youxiu': ['优秀','优修'],
|
||
'nihao': ['你好','尼豪']
|
||
},
|
||
showOptionPicker: false,
|
||
pickerTitle: '',
|
||
pickerOptions: [],
|
||
pickerIndex: 0,
|
||
pickerTarget: '',
|
||
fontValue: 18,
|
||
fontLevel: 1,
|
||
fontLabel: '中',
|
||
bubblePresets: [
|
||
'我算得比你快,但没你可爱。',
|
||
'上轮消耗 ¥0.08,钱包还撑得住。',
|
||
'峰时了,省着点用哦~',
|
||
'谷时来了,冲就完事!',
|
||
'今天也要一起聪明地偷懒。',
|
||
'点我一下,解压一下。',
|
||
'上轮余额 ¥12.34,别浪~',
|
||
'思考中…其实是发呆。'
|
||
]
|
||
},
|
||
|
||
onInit() {
|
||
this.refreshPeak()
|
||
this.convMsgs = {
|
||
'小米手环10 Pro远程控制': this.messages,
|
||
'DSHWatch 验收': [
|
||
{ role:'user', text:'验收一下 DSHWatch 今天改了什么', preview:'', think:null, kind:'normal', showThink:false, collapsed:false },
|
||
{ role:'assistant', text:'今天改了 5 次:玻璃气泡、字体放大、拼音输入、会话选择、键盘收起。', preview:'今天改了 5 次:玻璃气泡…', think:'正在比对最近提交…', kind:'normal', showThink:true, collapsed:true },
|
||
{ role:'user', text:'发一张验收截图', preview:'', think:null, kind:'normal', showThink:false, collapsed:false },
|
||
{ role:'assistant', text:'已生成「验收」截图。', preview:'已生成「验收」截图…', think:'正在渲染图像…', kind:'image', showThink:true, collapsed:false },
|
||
{ role:'assistant', text:'正在调用工具 adb 截图并回传…', preview:'正在调用工具 adb 截图…', think:null, kind:'tool', tag:'🔧 工具调用', showThink:false, collapsed:true },
|
||
{ role:'system', text:'构建通过,可上模拟器。', preview:'', think:null, kind:'normal', showThink:false, collapsed:false },
|
||
{ role:'user', text:'有没有 bug', preview:'', think:null, kind:'normal', showThink:false, collapsed:false },
|
||
{ role:'assistant', text:'发现一处白屏问题,待修复。', preview:'发现一处白屏问题…', think:'正在排查…', kind:'error', tag:'❌ 错误', showThink:true, collapsed:false }
|
||
],
|
||
'排查Git文件乱码问题': [
|
||
{ role:'user', text:'Git 文件乱码了,为什么?', preview:'', think:null, kind:'normal', showThink:false, collapsed:false },
|
||
{ role:'assistant', text:'多半是换行符/编码问题(CRLF vs LF、GBK vs UTF-8)。', preview:'多半是换行符/编码问题…', think:'正在检查 .gitattributes…', kind:'normal', showThink:true, collapsed:true },
|
||
{ role:'assistant', text:'git config core.autocrlf false\ngit config core.quotepath false', preview:'git config core.autocrlf false…', think:null, kind:'code', tag:'💻 代码', showThink:false, collapsed:true },
|
||
{ role:'user', text:'那怎么批量修', preview:'', think:null, kind:'normal', showThink:false, collapsed:false },
|
||
{ role:'assistant', text:'文件 | 状态 | 处理\na.txt | GBK | 转 UTF-8', preview:'文件 | 状态 | 处理…', think:null, kind:'table', tag:'📊 表格', showThink:false, collapsed:true },
|
||
{ role:'assistant', text:'建议一次性统一换行符并提交。', preview:'建议一次性统一换行符…', think:'正在评估影响面…', kind:'normal', showThink:true, collapsed:false }
|
||
],
|
||
'修改API配置为固定密钥': [
|
||
{ role:'user', text:'把 API 密钥固定到配置里', preview:'', think:null, kind:'normal', showThink:false, collapsed:false },
|
||
{ role:'assistant', text:'需要一个权限:写入 keystore.properties', preview:'', think:null, kind:'approval', showThink:false, collapsed:false },
|
||
{ role:'user', text:'允许', preview:'', think:null, kind:'normal', showThink:false, collapsed:false },
|
||
{ role:'assistant', text:'正在调用工具写入密钥…', preview:'正在写入密钥…', think:null, kind:'tool', tag:'🔧 工具调用', showThink:false, collapsed:true },
|
||
{ role:'assistant', text:'已写入(已加入 .gitignore)。', preview:'已写入(已 gitignore)…', think:'正在落盘…', kind:'normal', showThink:true, collapsed:false },
|
||
{ role:'user', text:'确认没泄露到仓库', preview:'', think:null, kind:'normal', showThink:false, collapsed:false },
|
||
{ role:'assistant', text:'已检查,未纳入版本控制。', preview:'已检查,未纳入版本控制…', think:'正在扫描提交历史…', kind:'normal', showThink:true, collapsed:true }
|
||
],
|
||
'检测DeepSeek API可用性': [
|
||
{ role:'user', text:'检测一下 DeepSeek 余额接口', preview:'', think:null, kind:'normal', showThink:false, collapsed:false },
|
||
{ role:'assistant', text:'正在调用 GET /user/balance…', preview:'正在调用余额接口…', think:'正在发起请求…', kind:'tool', tag:'🔧 工具调用', showThink:true, collapsed:true },
|
||
{ role:'assistant', text:'⚠️ API 不响应:请求超时(30s)', preview:'API 不响应:请求超时…', think:null, kind:'error', tag:'❌ 错误', showThink:false, collapsed:false },
|
||
{ role:'user', text:'重试一下', preview:'', think:null, kind:'normal', showThink:false, collapsed:false },
|
||
{ role:'assistant', text:'重试成功:200,is_available=true,余额 ¥6.20。', preview:'重试成功:200 …', think:'已完成重试…', kind:'normal', showThink:true, collapsed:false }
|
||
],
|
||
'评估一下项目状态': [
|
||
{ role:'assistant', text:'项目当前状态:UI 完成 70%,插件/服务器已通,待真机联调。', preview:'项目当前状态:UI 完成 70%…', think:'正在汇总各模块…', kind:'normal', showThink:true, collapsed:true },
|
||
{ role:'user', text:'请补充风险', preview:'', think:null, kind:'normal', showThink:false, collapsed:false },
|
||
{ role:'assistant', text:'建议先做最小闭环:插件→服务器→手机→手表。', preview:'建议先做最小闭环…', think:'正在规划优先级…', kind:'plan', tag:'🧭 计划', showThink:true, collapsed:true },
|
||
{ role:'assistant', text:'模块 | 状态\n插件 | 已通\n服务器 | 已通', preview:'模块 | 状态…', think:null, kind:'table', tag:'📊 表格', showThink:false, collapsed:true },
|
||
{ role:'assistant', text:'整体可行,下一步接真实数据。', preview:'整体可行…', think:null, kind:'normal', showThink:false, collapsed:false }
|
||
],
|
||
'旧站交接与维护指南': [
|
||
{ role:'assistant', text:'旧站维护要点:先备份,再改,最后回归。', preview:'旧站维护要点:先备份…', think:'正在检索交接文档…', kind:'normal', showThink:true, collapsed:true },
|
||
{ role:'user', text:'数据库怎么迁移', preview:'', think:null, kind:'normal', showThink:false, collapsed:false },
|
||
{ role:'assistant', text:'建议停机窗口内做全量导出+增量补丁。', preview:'建议停机窗口内…', think:'正在评估影响…', kind:'plan', tag:'🧭 计划', showThink:true, collapsed:true },
|
||
{ role:'assistant', text:'mysqldump --single-transaction old > old.sql', preview:'mysqldump --single-transaction…', think:null, kind:'code', tag:'💻 代码', showThink:false, collapsed:true }
|
||
],
|
||
'O-COM 云端模式开发交接': [
|
||
{ role:'user', text:'云端模式怎么远程控制', preview:'', think:null, kind:'normal', showThink:false, collapsed:false },
|
||
{ role:'assistant', text:'需要服务器 + 手机网关 + 手表三端。', preview:'需要服务器+手机网关+手表…', think:'正在梳理链路…', kind:'normal', showThink:true, collapsed:false },
|
||
{ role:'assistant', text:'⚠️ 服务器未部署,暂用 FRP 本地直连测试。', preview:'服务器未部署…', think:null, kind:'error', tag:'❌ 错误', showThink:false, collapsed:false },
|
||
{ role:'user', text:'那先本地串通', preview:'', think:null, kind:'normal', showThink:false, collapsed:false },
|
||
{ role:'assistant', text:'已串通:插件直连 → 手机转发 → 手表。', preview:'已串通:插件直连→手机→手表…', think:'正在验证链路…', kind:'normal', showThink:true, collapsed:true }
|
||
]
|
||
}
|
||
},
|
||
|
||
onReady() {
|
||
this._unsub = bridge.subscribe({
|
||
status: (s) => {
|
||
this.connText = s || '未连接'
|
||
},
|
||
message: (msg) => {
|
||
this.handleBridgeMessage(msg)
|
||
}
|
||
})
|
||
bridge.refreshConn()
|
||
},
|
||
|
||
onDestroy() {
|
||
if (this._unsub) this._unsub()
|
||
},
|
||
|
||
onSwipeChange(e) {
|
||
if (e && typeof e.index == 'number') {
|
||
this.currentIndex = e.index
|
||
// 离开对话页(index 1)自动收起键盘/选择器
|
||
if (this.currentIndex !== 1) {
|
||
this.showKeyboard = false
|
||
this.showOptionPicker = false
|
||
this.showFontPicker = false
|
||
}
|
||
}
|
||
},
|
||
|
||
toggleSelect() {
|
||
this.showSelect = !this.showSelect
|
||
},
|
||
|
||
selectConversation(convIndex, convName) {
|
||
this.currentConv = convName || this.currentConv
|
||
this.showSelect = false
|
||
},
|
||
|
||
selectConv(name) {
|
||
this.currentConv = name
|
||
this.currentIndex = 1
|
||
this.showSelect = false
|
||
},
|
||
|
||
newSession() {
|
||
// 弹出选择工作区小窗(预留:后续可从电脑目录/插件串流新建工作区)
|
||
this.pickerTitle = '选择工作区'
|
||
this.pickerTarget = 'ws'
|
||
this.pickerOptions = this.workTree.map((w) => w.name)
|
||
this.pickerIndex = 0
|
||
this.showOptionPicker = true
|
||
},
|
||
|
||
setActive(n) {
|
||
for (let i = 0; i <= 7; i++) this['active' + i] = (i === n)
|
||
},
|
||
|
||
goToPage(index) {
|
||
this.currentIndex = index
|
||
try {
|
||
const el = this.$element('mainSwiper')
|
||
if (el && el.swipeTo) el.swipeTo({ index: index })
|
||
} catch (e) {}
|
||
},
|
||
|
||
pickConv0() { this.currentConv = '小米手环10 Pro远程控制'; this.messages = this.convMsgs['小米手环10 Pro远程控制']; this.setActive(0); this.goToPage(1) },
|
||
pickConv1() { this.currentConv = 'DSHWatch 验收'; this.messages = this.convMsgs['DSHWatch 验收']; this.setActive(1); this.goToPage(1) },
|
||
pickConv2() { this.currentConv = '排查Git文件乱码问题'; this.messages = this.convMsgs['排查Git文件乱码问题']; this.setActive(2); this.goToPage(1) },
|
||
pickConv3() { this.currentConv = '修改API配置为固定密钥'; this.messages = this.convMsgs['修改API配置为固定密钥']; this.setActive(3); this.goToPage(1) },
|
||
pickConv4() { this.currentConv = '检测DeepSeek API可用性'; this.messages = this.convMsgs['检测DeepSeek API可用性']; this.setActive(4); this.goToPage(1) },
|
||
pickConv5() { this.currentConv = '评估一下项目状态'; this.messages = this.convMsgs['评估一下项目状态']; this.setActive(5); this.goToPage(1) },
|
||
pickConv6() { this.currentConv = '旧站交接与维护指南'; this.messages = this.convMsgs['旧站交接与维护指南']; this.setActive(6); this.goToPage(1) },
|
||
pickConv7() { this.currentConv = 'O-COM 云端模式开发交接'; this.messages = this.convMsgs['O-COM 云端模式开发交接']; this.setActive(7); this.goToPage(1) },
|
||
|
||
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.showKeyboard = true
|
||
},
|
||
|
||
kbKey(ch) {
|
||
if (this.pinyinMode) {
|
||
this.pinyinBuf = (this.pinyinBuf + ch).slice(0, 12)
|
||
this.computeCandidates()
|
||
} else {
|
||
this.inputText = (this.inputText + ch).slice(0, 80)
|
||
}
|
||
},
|
||
|
||
kbToggleZh() {
|
||
this.kbMode = this.kbMode == 'en' ? 'zh' : 'en'
|
||
},
|
||
|
||
kbTogglePy() {
|
||
this.pinyinMode = !this.pinyinMode
|
||
this.pinyinBuf = ''
|
||
this.candidates = []
|
||
},
|
||
|
||
computeCandidates() {
|
||
const buf = this.pinyinBuf
|
||
if (!buf) { this.candidates = []; return }
|
||
const out = []
|
||
if (this.pinDict[buf]) out.push(...this.pinDict[buf])
|
||
// 双音节组合词(如 ni+hao -> 你好)
|
||
for (let i = 1; i < buf.length; i++) {
|
||
const a = this.pinDict[buf.slice(0, i)]
|
||
const b = this.pinDict[buf.slice(i)]
|
||
if (a && b) out.push(a[0] + b[0])
|
||
}
|
||
this.candidates = out.slice(0, 4)
|
||
},
|
||
|
||
pickCand0() { this.candPick(0) },
|
||
pickCand1() { this.candPick(1) },
|
||
pickCand2() { this.candPick(2) },
|
||
pickCand3() { this.candPick(3) },
|
||
candPick(i) {
|
||
const c = this.candidates[i]
|
||
if (!c) return
|
||
this.inputText = (this.inputText + c).slice(0, 80)
|
||
this.pinyinBuf = ''
|
||
this.candidates = []
|
||
},
|
||
|
||
kbSpace() {
|
||
this.inputText = (this.inputText + ' ').slice(0, 80)
|
||
},
|
||
|
||
kbBack() {
|
||
this.inputText = this.inputText.slice(0, -1)
|
||
},
|
||
|
||
kbClose() {
|
||
this.showKeyboard = false
|
||
},
|
||
|
||
kbSend() {
|
||
if (this.inputText.trim().length > 0) {
|
||
bridge.sendText(this.inputText.trim())
|
||
}
|
||
this.inputText = ''
|
||
this.showKeyboard = false
|
||
},
|
||
|
||
onInputChange(e) {
|
||
this.inputText = e && e.value || ''
|
||
},
|
||
|
||
toggleModel() { this.openPicker('model') },
|
||
toggleProvider() { this.openPicker('provider') },
|
||
toggleReason() { this.openPicker('reason') },
|
||
togglePerm() { this.openPicker('perm') },
|
||
toggleDialog() { this.openPicker('dialog') },
|
||
toggleWatchConn() { this.openPicker('conn') },
|
||
toggleServer() { this.openPicker('server') },
|
||
|
||
openPicker(target) {
|
||
const titles = { model:'模型', provider:'提供方', reason:'推理强度', perm:'权限模式', dialog:'对话模式', conn:'手环连接', server:'服务器' }
|
||
const values = { model:'modelValue', provider:'providerValue', reason:'reasonValue', perm:'permValue', dialog:'dialogModeValue', conn:'watchConnValue', server:'serverValue' }
|
||
this.pickerTitle = titles[target] || '选择'
|
||
this.pickerTarget = target
|
||
this.pickerOptions = this[target + 'Options']
|
||
this.pickerIndex = this[target + 'Idx'] || 0
|
||
this.showOptionPicker = true
|
||
},
|
||
|
||
pick0() { this.pickerIndex = 0 },
|
||
pick1() { this.pickerIndex = 1 },
|
||
pick2() { this.pickerIndex = 2 },
|
||
pick3() { this.pickerIndex = 3 },
|
||
|
||
applyPicker() {
|
||
const t = this.pickerTarget
|
||
if (!t) return this.showOptionPicker = false
|
||
if (t == 'ws') {
|
||
// 预留:新建工作区(后续让插件串流电脑目录,这里先占位)
|
||
this.currentConv = '新会话'
|
||
this.showOptionPicker = false
|
||
return
|
||
}
|
||
const values = { model:'modelValue', provider:'providerValue', reason:'reasonValue', perm:'permValue', dialog:'dialogModeValue', conn:'watchConnValue', server:'serverValue' }
|
||
this[t + 'Idx'] = this.pickerIndex
|
||
this[values[t]] = this.pickerOptions[this.pickerIndex]
|
||
this.showOptionPicker = false
|
||
},
|
||
|
||
openFontPicker() {
|
||
this.showFontPicker = true
|
||
this.fontValue = 18
|
||
},
|
||
|
||
onFontChange(e) {
|
||
let v = e && typeof e.progress == 'number' ? e.progress : (e && typeof e.value == 'number' ? e.value : 18)
|
||
// 兼容 progress 是 0-100 百分比的情况
|
||
if (v >= 10 && v <= 24) {
|
||
this.fontValue = v
|
||
} else {
|
||
this.fontValue = Math.round(14 + (v / 100) * (22 - 14))
|
||
}
|
||
const lv = Math.round((this.fontValue - 14) / 2)
|
||
this.fontLevel = Math.max(0, Math.min(4, lv))
|
||
this.fontLabel = ['最小', '小', '中', '大', '特大'][this.fontLevel]
|
||
},
|
||
|
||
confirmFont() {
|
||
this.showFontPicker = false
|
||
const row = this.watchSettings.find((r) => r.label == '字体大小')
|
||
if (row) row.value = this.fontLabel
|
||
},
|
||
|
||
refreshPeak() {
|
||
// 用北京时间计算峰谷;周六/周日全天谷时
|
||
const now = Date.now()
|
||
const d = new Date(now + 8 * 3600 * 1000)
|
||
const day = d.getUTCDay() // 0=Sun
|
||
const hour = d.getUTCHours()
|
||
const isWeekend = day == 0 || day == 6
|
||
const inPeak = !isWeekend && ((hour >= 9 && hour < 12) || (hour >= 14 && hour < 18))
|
||
this.isPeak = inPeak
|
||
this.peakLabel = inPeak ? '峰时 · 全价' : '谷时 · 半价'
|
||
},
|
||
|
||
onTap() {
|
||
// 冷却:动画/气泡显示期间不重复触发
|
||
if (this.animLock) return
|
||
this.animLock = true
|
||
|
||
// Q 弹:底部不动,从上往下收缩,再回弹(scaleY 多帧平滑)
|
||
const self = this
|
||
this.animStep = 0
|
||
setTimeout(() => { self.animStep = 1 }, 0) // scaleY 0.92
|
||
setTimeout(() => { self.animStep = 2 }, 40) // scaleY 0.84
|
||
setTimeout(() => { self.animStep = 3 }, 85) // scaleY 0.95
|
||
setTimeout(() => { self.animStep = 4 }, 135) // scaleY 1.05
|
||
setTimeout(() => { self.animStep = 0 }, 200) // scaleY 1.00
|
||
// 回弹时播鸭叫
|
||
setTimeout(() => { playDuckSound() }, 150)
|
||
|
||
// 轮播趣味/上轮消耗信息
|
||
const idx = this.bubbleIndex % this.bubblePresets.length
|
||
this.bubbleIndex += 1
|
||
this.bubbleText = this.bubblePresets[idx]
|
||
this.bubbleVisible = true
|
||
|
||
// 气泡显示约 1 秒后自动消失
|
||
setTimeout(() => { self.bubbleVisible = false }, 1000)
|
||
// 消失后再等 0.5 秒才允许再次点击
|
||
setTimeout(() => { self.animLock = false }, 1500)
|
||
},
|
||
|
||
selectSession(index) {
|
||
this.currentIndex = 1
|
||
},
|
||
|
||
onServerChange(e) { this.settings.serverUrl = e && e.value || '' },
|
||
onUserChange(e) { this.settings.userId = e && e.value || '' },
|
||
onTokenChange(e) { this.settings.token = e && e.value || '' },
|
||
|
||
handleBridgeMessage(msg) {
|
||
if (!msg) return
|
||
if (msg.type == 'status' && msg.payload) {
|
||
const pl = msg.payload
|
||
if (typeof pl.balance !== 'undefined') this.balance = pl.balance
|
||
if (typeof pl.todayCost !== 'undefined') this.todayCost = pl.todayCost
|
||
if (pl.model) this.statusText = '在线 · ' + pl.model
|
||
if (typeof pl.running !== 'undefined') this.statusText = pl.running ? '执行中' : '空闲'
|
||
this.connText = pl.model ? ('在线 · ' + pl.model) : '已连接'
|
||
} else if (msg.type == 'event' && msg.payload) {
|
||
// 后续可更新会话/事件列表
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style>
|
||
.page {
|
||
width: 100%;
|
||
height: 100%;
|
||
flex-direction: column;
|
||
background-color: #0b1220;
|
||
}
|
||
|
||
.swiper {
|
||
flex: 1;
|
||
width: 100%;
|
||
indicator-size: 0px;
|
||
}
|
||
|
||
.page-item {
|
||
width: 100%;
|
||
height: 100%;
|
||
padding: 12px;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.left-page,
|
||
.right-page {
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
padding-top: 22px;
|
||
}
|
||
|
||
.center-page {
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
align-items: center;
|
||
}
|
||
|
||
.bubble-zone {
|
||
width: 100%;
|
||
height: 54px;
|
||
justify-content: center;
|
||
align-items: center;
|
||
}
|
||
|
||
.page-title {
|
||
color: #e6f0ff;
|
||
font-size: 24px;
|
||
font-weight: bold;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.page-hint {
|
||
color: #6b7a99;
|
||
font-size: 16px;
|
||
margin-top: 20px;
|
||
}
|
||
|
||
.session-list {
|
||
width: 100%;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.session-row {
|
||
width: 100%;
|
||
height: 44px;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 8px;
|
||
padding: 0 10px;
|
||
border-radius: 10px;
|
||
background-color: #152238;
|
||
}
|
||
|
||
.session-title {
|
||
color: #dbe8ff;
|
||
font-size: 17px;
|
||
}
|
||
|
||
.session-dot {
|
||
color: #33b4ff;
|
||
font-size: 16px;
|
||
}
|
||
|
||
/* 吉祥物:放大 + 底部锚定的 scaleY+translateY 补偿 Q 弹 */
|
||
.mascot {
|
||
width: 456px;
|
||
height: 403px;
|
||
transition: transform 0.12s cubic-bezier(0.33, 1.12, 0.68, 1);
|
||
}
|
||
|
||
.step0 { transform: translateY(0px) scaleY(1); }
|
||
.step1 { transform: translateY(16px) scaleY(0.92); }
|
||
.step2 { transform: translateY(36px) scaleY(0.82); }
|
||
.step3 { transform: translateY(10px) scaleY(0.95); }
|
||
.step4 { transform: translateY(-10px) scaleY(1.05); }
|
||
|
||
.peak {
|
||
width: 100%;
|
||
font-size: 32px;
|
||
font-weight: bold;
|
||
line-height: 40px;
|
||
text-align: center;
|
||
margin-top: 6px;
|
||
}
|
||
|
||
.peak-green {
|
||
color: #3ddc84;
|
||
}
|
||
|
||
.peak-red {
|
||
color: #ff5b5b;
|
||
}
|
||
|
||
.stats {
|
||
width: 100%;
|
||
color: #c4d2ea;
|
||
font-size: 22px;
|
||
line-height: 28px;
|
||
text-align: center;
|
||
margin-top: 2px;
|
||
}
|
||
|
||
/* 气泡:头像正上方居中,稍微放大 */
|
||
.bubble {
|
||
width: 324px;
|
||
flex-direction: column;
|
||
padding: 18px 20px;
|
||
border-radius: 22px;
|
||
background-color: rgba(20, 32, 52, 0.86);
|
||
border: 1px solid rgba(255, 255, 255, 0.14);
|
||
}
|
||
|
||
.bubble-text {
|
||
color: #e6f0ff;
|
||
font-size: 17px;
|
||
line-height: 24px;
|
||
text-align: center;
|
||
}
|
||
|
||
.setting-label {
|
||
color: #8fa4c9;
|
||
font-size: 14px;
|
||
margin-top: 8px;
|
||
}
|
||
|
||
.setting-input {
|
||
width: 100%;
|
||
height: 38px;
|
||
border-radius: 8px;
|
||
background-color: #152238;
|
||
color: #e6f0ff;
|
||
font-size: 15px;
|
||
padding: 0 8px;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.conn-text {
|
||
color: #6b7a99;
|
||
font-size: 16px;
|
||
margin-top: 8px;
|
||
}
|
||
|
||
.dots {
|
||
flex-direction: row;
|
||
justify-content: center;
|
||
align-items: center;
|
||
height: 14px;
|
||
margin-top: 4px;
|
||
gap: 8px;
|
||
}
|
||
|
||
.dot {
|
||
width: 7px;
|
||
height: 7px;
|
||
border-radius: 4px;
|
||
background-color: rgba(255, 255, 255, 0.28);
|
||
}
|
||
|
||
.dot-active {
|
||
width: 22px;
|
||
border-radius: 4px;
|
||
background-color: #33b4ff;
|
||
}
|
||
|
||
/* ==== 左页:对话 ==== */
|
||
.chat-scroll {
|
||
flex: 1;
|
||
width: 100%;
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
}
|
||
|
||
.chat-img {
|
||
width: 100%;
|
||
height: 150px;
|
||
border-radius: 8px;
|
||
margin-top: 4px;
|
||
}
|
||
|
||
.msg-row {
|
||
width: 100%;
|
||
flex-direction: row;
|
||
margin-bottom: 6px;
|
||
}
|
||
|
||
.row-user {
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
.row-assistant {
|
||
justify-content: flex-start;
|
||
}
|
||
|
||
.chat-msg {
|
||
width: auto;
|
||
max-width: 84%;
|
||
flex-direction: column;
|
||
padding: 7px 10px;
|
||
border-radius: 12px;
|
||
}
|
||
|
||
.msg-user {
|
||
margin-right: 12px;
|
||
background-color: rgba(51, 180, 255, 0.20);
|
||
border: 1px solid rgba(120, 200, 255, 0.30);
|
||
}
|
||
|
||
.msg-assistant {
|
||
margin-left: 12px;
|
||
margin-right: 12px;
|
||
background-color: rgba(58, 62, 70, 0.55);
|
||
border: 1px solid rgba(255, 255, 255, 0.10);
|
||
}
|
||
|
||
.chat-text {
|
||
color: #f0f6ff;
|
||
font-size: 17px;
|
||
line-height: 25px;
|
||
}
|
||
|
||
.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: 14px;
|
||
}
|
||
|
||
.think-text {
|
||
color: #9db3d8;
|
||
font-size: 14px;
|
||
line-height: 20px;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.expand-chip {
|
||
margin-top: 4px;
|
||
color: #33b4ff;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.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;
|
||
min-height: 38px;
|
||
height: auto;
|
||
border-radius: 8px;
|
||
background-color: #152238;
|
||
color: #e6f0ff;
|
||
font-size: 15px;
|
||
padding: 6px 8px;
|
||
}
|
||
|
||
.input-send {
|
||
width: 72px;
|
||
min-height: 38px;
|
||
height: auto;
|
||
border-radius: 19px;
|
||
background-color: #33b4ff;
|
||
color: #08131f;
|
||
font-size: 16px;
|
||
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;
|
||
}
|
||
|
||
/* ==== 右页:设置 ==== */
|
||
.settings-scroll {
|
||
flex: 1;
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
.setting-row {
|
||
width: 100%;
|
||
min-height: 44px;
|
||
height: auto;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 6px 10px;
|
||
border-radius: 10px;
|
||
background-color: #152238;
|
||
margin-bottom: 6px;
|
||
}
|
||
|
||
.setting-label {
|
||
color: #9db3d8;
|
||
font-size: 17px;
|
||
}
|
||
|
||
.setting-value {
|
||
color: #e6f0ff;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.section-title {
|
||
color: #33b4ff;
|
||
font-size: 18px;
|
||
font-weight: bold;
|
||
margin-top: 10px;
|
||
margin-bottom: 6px;
|
||
}
|
||
|
||
/* ==== 字体等级(随字号滑块变化) ==== */
|
||
.fl0 { font-size: 15px; line-height: 21px; }
|
||
.fl1 { font-size: 17px; line-height: 25px; }
|
||
.fl2 { font-size: 19px; line-height: 28px; }
|
||
.fl3 { font-size: 21px; line-height: 31px; }
|
||
.fl4 { font-size: 23px; line-height: 34px; }
|
||
|
||
/* ==== 字体大小弹窗 ==== */
|
||
.popup {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
justify-content: center;
|
||
align-items: center;
|
||
background-color: rgba(0, 0, 0, 0.5);
|
||
}
|
||
|
||
.popup-box {
|
||
width: 300px;
|
||
min-height: 180px;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
padding: 20px;
|
||
border-radius: 18px;
|
||
background-color: #0f1a2c;
|
||
border: 1px solid rgba(255, 255, 255, 0.14);
|
||
}
|
||
|
||
.popup-title {
|
||
color: #e6f0ff;
|
||
font-size: 20px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.font-slider {
|
||
width: 240px;
|
||
margin-top: 14px;
|
||
}
|
||
|
||
.popup-value {
|
||
color: #33b4ff;
|
||
font-size: 18px;
|
||
margin-top: 10px;
|
||
}
|
||
|
||
.popup-actions {
|
||
margin-top: 14px;
|
||
}
|
||
|
||
.popup-btn {
|
||
width: 120px;
|
||
height: 42px;
|
||
border-radius: 21px;
|
||
background-color: #33b4ff;
|
||
color: #08131f;
|
||
font-size: 17px;
|
||
text-align: center;
|
||
}
|
||
|
||
.picker-option {
|
||
width: 260px;
|
||
min-height: 40px;
|
||
justify-content: center;
|
||
align-items: center;
|
||
margin-bottom: 6px;
|
||
padding: 6px 10px;
|
||
border-radius: 10px;
|
||
background-color: #152238;
|
||
border: 1px solid rgba(255,255,255,0.10);
|
||
}
|
||
|
||
.picker-option text {
|
||
color: #dbe8ff;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.picker-active {
|
||
background-color: rgba(51,180,255,0.22);
|
||
border-color: #33b4ff;
|
||
}
|
||
|
||
/* ==== 软键盘 ==== */
|
||
.keyboard {
|
||
position: absolute;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
flex-direction: column;
|
||
background-color: rgba(11, 18, 32, 0.97);
|
||
border-top: 1px solid rgba(255,255,255,0.14);
|
||
padding: 10px;
|
||
}
|
||
|
||
.kb-input {
|
||
width: 100%;
|
||
min-height: 48px;
|
||
justify-content: center;
|
||
align-items: center;
|
||
border-radius: 10px;
|
||
background-color: #152238;
|
||
margin-bottom: 10px;
|
||
padding: 6px;
|
||
}
|
||
|
||
.kb-text {
|
||
color: #e6f0ff;
|
||
font-size: 17px;
|
||
}
|
||
|
||
.kb-backdrop {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background-color: rgba(0, 0, 0, 0.35);
|
||
}
|
||
|
||
.kb-keys {
|
||
width: 100%;
|
||
flex-direction: column;
|
||
}
|
||
|
||
|
||
.kb-row {
|
||
width: 100%;
|
||
flex-direction: row;
|
||
justify-content: center;
|
||
margin-bottom: 6px;
|
||
}
|
||
|
||
.kb-key {
|
||
width: 34px;
|
||
height: 40px;
|
||
margin: 0 2px;
|
||
justify-content: center;
|
||
align-items: center;
|
||
border-radius: 8px;
|
||
background-color: #1c2c47;
|
||
color: #e6f0ff;
|
||
font-size: 16px;
|
||
text-align: center;
|
||
}
|
||
|
||
.kb-fn {
|
||
width: 64px;
|
||
}
|
||
|
||
.kb-char {
|
||
width: 30px;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.kb-phrase {
|
||
width: auto;
|
||
min-width: 56px;
|
||
padding: 0 6px;
|
||
}
|
||
|
||
.kb-send {
|
||
background-color: #33b4ff;
|
||
color: #08131f;
|
||
}
|
||
|
||
.new-session {
|
||
width: 100%;
|
||
height: 42px;
|
||
margin-top: 6px;
|
||
margin-bottom: 8px;
|
||
border-radius: 21px;
|
||
background-color: rgba(51,180,255,0.16);
|
||
border: 1px solid rgba(120,200,255,0.28);
|
||
color: #e6f0ff;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.page-title.small {
|
||
font-size: 16px;
|
||
color: #9db3d8;
|
||
margin-top: 6px;
|
||
margin-bottom: 2px;
|
||
}
|
||
|
||
.ws-group {
|
||
width: 100%;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.ws-name {
|
||
color: #9db3d8;
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
margin-top: 8px;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.sel-conv {
|
||
width: 100%;
|
||
min-height: 42px;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 6px 10px;
|
||
border-radius: 10px;
|
||
background-color: #152238;
|
||
margin-bottom: 6px;
|
||
}
|
||
|
||
.sel-title {
|
||
color: #e6f0ff;
|
||
font-size: 16px;
|
||
flex: 1;
|
||
}
|
||
|
||
.sel-time {
|
||
color: #8895ad;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.sel-active {
|
||
background-color: rgba(51,180,255,0.22);
|
||
border: 1px solid #33b4ff;
|
||
}
|
||
|
||
.sel-btn {
|
||
width: 100%;
|
||
height: 42px;
|
||
margin-bottom: 6px;
|
||
border-radius: 10px;
|
||
background-color: #152238;
|
||
color: #e6f0ff;
|
||
font-size: 15px;
|
||
text-align: left;
|
||
padding: 0 10px;
|
||
}
|
||
|
||
.sel-btn-active {
|
||
background-color: rgba(51,180,255,0.22);
|
||
border: 1px solid #33b4ff;
|
||
}
|
||
|
||
.kb-pinyin {
|
||
width: 100%;
|
||
justify-content: center;
|
||
align-items: center;
|
||
color: #7fd0ff;
|
||
font-size: 18px;
|
||
margin-bottom: 6px;
|
||
min-height: 24px;
|
||
}
|
||
|
||
.kb-cands {
|
||
width: 100%;
|
||
flex-direction: row;
|
||
justify-content: center;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.kb-cand {
|
||
width: 56px;
|
||
height: 38px;
|
||
margin: 0 3px;
|
||
justify-content: center;
|
||
align-items: center;
|
||
border-radius: 9px;
|
||
background-color: rgba(51,180,255,0.16);
|
||
border: 1px solid rgba(120,200,255,0.28);
|
||
color: #e6f0ff;
|
||
font-size: 18px;
|
||
text-align: center;
|
||
}
|
||
</style>
|