4 changed files with 2075 additions and 715 deletions
File diff suppressed because it is too large
File diff suppressed because it is too large
@ -0,0 +1,81 @@ |
|||||
|
#!/usr/bin/env python3 |
||||
|
import ssl, time, hashlib, json, websocket |
||||
|
|
||||
|
DEVICE_NO = '199839C0AAAE99653D2E' |
||||
|
DEVICE_ID = '2032013791868043266' |
||||
|
PRODUCT_ID = '2031271967792472065' |
||||
|
PRODUCT_KEY = 'hotepZZwvouWQjHz' |
||||
|
DEVICE_SECRET = 'ijdthZGQELhkmigf' |
||||
|
|
||||
|
ts = str(int(time.time() * 1000)) |
||||
|
sign = hashlib.md5((DEVICE_SECRET + ts).encode()).hexdigest() |
||||
|
url = (f'wss://ivs.chinamobiledevice.com:11443/app-ws/v2/asr' |
||||
|
f'?deviceNo={DEVICE_NO}&deviceId={DEVICE_ID}' |
||||
|
f'&productId={PRODUCT_ID}&productKey={PRODUCT_KEY}' |
||||
|
f'&sign={sign}&sn=py-test&ts={ts}') |
||||
|
|
||||
|
print(f'connecting...\n{url}') |
||||
|
ws = websocket.WebSocket(sslopt={'cert_reqs': ssl.CERT_NONE}) |
||||
|
ws.settimeout(15) |
||||
|
ws.connect(url) |
||||
|
print('connected! sending start frame immediately...') |
||||
|
|
||||
|
import uuid |
||||
|
start_frame = json.dumps({ |
||||
|
'type': 'start', |
||||
|
'data': { |
||||
|
'cuid': DEVICE_ID, |
||||
|
'format': 'pcm', |
||||
|
'sample': 16000, |
||||
|
'support_dcs': 2, |
||||
|
'chunk_size': 10240, |
||||
|
'support_tts': True, |
||||
|
'support_text2dcs': True, |
||||
|
'user_agent': 'voitrans/1.0', |
||||
|
'dialog_request_id': str(uuid.uuid4()), |
||||
|
'access_rc': True, |
||||
|
'support_part_tts': True, |
||||
|
'need_stoplisten': True, |
||||
|
'need_dialogue_finish': True, |
||||
|
'result_trans2directive': False, |
||||
|
'rc_version': '5', |
||||
|
'messages': [], |
||||
|
'client_context': [], |
||||
|
} |
||||
|
}) |
||||
|
ws.send(start_frame) |
||||
|
print('start frame sent, waiting for ready...') |
||||
|
|
||||
|
# 等待 ready |
||||
|
while True: |
||||
|
msg = ws.recv() |
||||
|
print('received:', msg) |
||||
|
data = json.loads(msg) |
||||
|
if data.get('type') == 'ready': |
||||
|
print('--- ready received, sending text "你好" ---') |
||||
|
break |
||||
|
|
||||
|
# 发送文本 |
||||
|
text_frame = json.dumps({'type': 'text', 'data': {'text': '你好'}}) |
||||
|
ws.send(text_frame) |
||||
|
print('text frame sent') |
||||
|
|
||||
|
# 发送 finish |
||||
|
finish_frame = json.dumps({'type': 'finish'}) |
||||
|
ws.send(finish_frame) |
||||
|
print('finish frame sent, waiting for AI response...') |
||||
|
|
||||
|
# 接收所有回包直到 end=1 |
||||
|
while True: |
||||
|
msg = ws.recv() |
||||
|
print('received:', msg) |
||||
|
try: |
||||
|
data = json.loads(msg) |
||||
|
if data.get('end') == 1: |
||||
|
print('--- session end (end=1), closing ---') |
||||
|
break |
||||
|
except Exception: |
||||
|
pass |
||||
|
|
||||
|
ws.close() |
||||
|
print('done.') |
||||
@ -0,0 +1,563 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<html lang="zh"> |
||||
|
<head> |
||||
|
<meta charset="UTF-8"> |
||||
|
<meta name="viewport" content="width=375, initial-scale=1"> |
||||
|
<title>移动精灵 UI 预览</title> |
||||
|
<style> |
||||
|
* { margin: 0; padding: 0; box-sizing: border-box; } |
||||
|
body { |
||||
|
background: #e8edf2; |
||||
|
font-family: -apple-system, 'PingFang SC', 'Helvetica Neue', sans-serif; |
||||
|
display: flex; gap: 20px; padding: 24px; |
||||
|
flex-wrap: wrap; justify-content: center; align-items: flex-start; |
||||
|
} |
||||
|
|
||||
|
/* ── Phone shell ── */ |
||||
|
.phone { |
||||
|
width: 375px; height: 700px; |
||||
|
background: #f4f6f9; |
||||
|
border-radius: 44px; |
||||
|
border: 1px solid #d0d5dd; |
||||
|
overflow: hidden; |
||||
|
display: flex; flex-direction: column; |
||||
|
box-shadow: 0 8px 32px rgba(0,0,0,0.12), 0 2px 8px rgba(0,0,0,0.06); |
||||
|
} |
||||
|
|
||||
|
/* ── AppBar ── */ |
||||
|
.appbar { |
||||
|
height: 56px; flex-shrink: 0; |
||||
|
background: #fff; |
||||
|
border-bottom: 1px solid #eaecf0; |
||||
|
display: flex; align-items: center; |
||||
|
padding: 0 16px; gap: 8px; |
||||
|
} |
||||
|
.appbar-back { color: #667085; font-size: 22px; line-height: 1; cursor: pointer; } |
||||
|
.appbar-avatar { |
||||
|
width: 32px; height: 32px; border-radius: 50%; |
||||
|
background: linear-gradient(135deg, #667eea, #764ba2); |
||||
|
display: flex; align-items: center; justify-content: center; |
||||
|
color: #fff; font-size: 14px; font-weight: 600; flex-shrink: 0; |
||||
|
} |
||||
|
.appbar-name { font-size: 16px; font-weight: 600; color: #101828; flex: 1; } |
||||
|
.status-pill { |
||||
|
font-size: 11px; font-weight: 500; |
||||
|
padding: 3px 10px; border-radius: 20px; |
||||
|
display: flex; align-items: center; gap: 4px; |
||||
|
} |
||||
|
.pill-idle { background: #f2f4f7; color: #667085; } |
||||
|
.pill-connecting { background: #fffaeb; color: #b54708; } |
||||
|
.pill-recording { background: #ecfdf3; color: #027a48; } |
||||
|
.pill-thinking { background: #eff8ff; color: #175cd3; } |
||||
|
.pill-speaking { background: #ecfdf3; color: #027a48; } |
||||
|
.pill-timer { font-weight: 400; } |
||||
|
|
||||
|
/* ── Chat area ── */ |
||||
|
.chat-area { |
||||
|
flex: 1; overflow-y: auto; |
||||
|
padding: 16px 16px 8px; |
||||
|
display: flex; flex-direction: column; gap: 14px; |
||||
|
} |
||||
|
.empty-state { |
||||
|
flex: 1; display: flex; flex-direction: column; |
||||
|
align-items: center; justify-content: center; gap: 8px; |
||||
|
} |
||||
|
.empty-avatar { |
||||
|
width: 80px; height: 80px; border-radius: 50%; |
||||
|
background: linear-gradient(135deg, #667eea, #764ba2); |
||||
|
display: flex; align-items: center; justify-content: center; |
||||
|
color: #fff; font-size: 32px; font-weight: 600; margin-bottom: 12px; |
||||
|
} |
||||
|
.empty-title { font-size: 20px; font-weight: bold; color: #101828; } |
||||
|
.empty-sub { font-size: 13px; color: #667085; } |
||||
|
|
||||
|
.msg-row { display: flex; flex-direction: column; } |
||||
|
.msg-row.user { align-items: flex-end; } |
||||
|
.msg-row.ai { align-items: flex-start; gap: 4px; } |
||||
|
.ai-label { display: flex; align-items: center; gap: 6px; } |
||||
|
.ai-dot { |
||||
|
width: 22px; height: 22px; border-radius: 50%; |
||||
|
background: linear-gradient(135deg, #667eea, #764ba2); |
||||
|
display: flex; align-items: center; justify-content: center; |
||||
|
color: #fff; font-size: 10px; |
||||
|
} |
||||
|
.ai-name { font-size: 11px; color: #98a2b3; } |
||||
|
.bubble { |
||||
|
max-width: 72%; padding: 10px 14px; |
||||
|
font-size: 15px; line-height: 1.55; |
||||
|
word-break: break-all; |
||||
|
} |
||||
|
.bubble-user { |
||||
|
background: #2e7cf6; color: #fff; |
||||
|
border-radius: 18px 18px 4px 18px; |
||||
|
} |
||||
|
.bubble-ai { |
||||
|
background: #fff; color: #1d2939; |
||||
|
border-radius: 4px 18px 18px 18px; |
||||
|
box-shadow: 0 1px 4px rgba(0,0,0,0.06); |
||||
|
} |
||||
|
.msg-time { font-size: 11px; color: #d0d5dd; margin-top: 3px; } |
||||
|
|
||||
|
/* ── Bottom bar ── */ |
||||
|
.bottom-bar { |
||||
|
flex-shrink: 0; |
||||
|
background: #fff; |
||||
|
border-top: 1px solid #eaecf0; |
||||
|
box-shadow: 0 -2px 12px rgba(0,0,0,0.05); |
||||
|
} |
||||
|
|
||||
|
/* ── 输入区域 ── */ |
||||
|
.input-row { |
||||
|
display: flex; align-items: center; gap: 8px; |
||||
|
padding: 8px 12px 18px; |
||||
|
} |
||||
|
.mode-toggle { |
||||
|
width: 38px; height: 38px; border-radius: 50%; |
||||
|
background: #f2f4f7; border: none; |
||||
|
display: flex; align-items: center; justify-content: center; |
||||
|
cursor: pointer; flex-shrink: 0; color: #667085; font-size: 18px; |
||||
|
} |
||||
|
/* 文本输入框(内含发送按钮) */ |
||||
|
.text-input-wrap { |
||||
|
flex: 1; height: 42px; border-radius: 21px; |
||||
|
background: #f2f4f7; |
||||
|
display: flex; align-items: center; |
||||
|
padding: 0 4px 0 16px; |
||||
|
} |
||||
|
.text-input-wrap input { |
||||
|
flex: 1; height: 100%; border: none; background: transparent; |
||||
|
font-size: 14px; color: #101828; outline: none; |
||||
|
} |
||||
|
.text-input-wrap input::placeholder { color: #98a2b3; } |
||||
|
.send-btn-inner { |
||||
|
width: 32px; height: 32px; border-radius: 50%; |
||||
|
background: linear-gradient(135deg, #667eea, #5a67d8); |
||||
|
border: none; cursor: pointer; color: #fff; font-size: 16px; |
||||
|
display: flex; align-items: center; justify-content: center; flex-shrink: 0; |
||||
|
} |
||||
|
.call-btn { |
||||
|
width: 44px; height: 44px; border-radius: 50%; |
||||
|
background: linear-gradient(135deg, #34c759, #28a745); |
||||
|
border: none; cursor: pointer; color: #fff; font-size: 18px; |
||||
|
display: flex; align-items: center; justify-content: center; flex-shrink: 0; |
||||
|
box-shadow: 0 4px 14px rgba(52,199,89,.4); |
||||
|
} |
||||
|
|
||||
|
/* 按住说话按钮(微信风格) */ |
||||
|
.ptt-btn { |
||||
|
flex: 1; height: 42px; border-radius: 21px; |
||||
|
background: #fff; border: 1px solid #d0d5dd; |
||||
|
display: flex; align-items: center; justify-content: center; |
||||
|
cursor: pointer; font-size: 15px; color: #101828; font-weight: 600; |
||||
|
letter-spacing: 2px; |
||||
|
transition: all .15s; |
||||
|
user-select: none; |
||||
|
} |
||||
|
.ptt-btn:active { |
||||
|
background: #f2f4f7; |
||||
|
border-color: #98a2b3; |
||||
|
} |
||||
|
/* 禁用状态 */ |
||||
|
.ptt-btn.disabled { |
||||
|
opacity: 0.5; |
||||
|
pointer-events: none; |
||||
|
} |
||||
|
|
||||
|
/* PTT 录音中 - 中间按钮(橙色) */ |
||||
|
.ptt-record-center { |
||||
|
flex: 1; height: 42px; border-radius: 21px; |
||||
|
background: linear-gradient(135deg, #f79009, #dc6803); |
||||
|
display: flex; align-items: center; justify-content: center; gap: 8px; |
||||
|
box-shadow: 0 2px 8px rgba(247,144,9,.3); |
||||
|
color: #fff; font-size: 14px; font-weight: 500; |
||||
|
} |
||||
|
/* PTT 连接中 - 与按住说话空闲状态相同 */ |
||||
|
.ptt-connecting-center { |
||||
|
flex: 1; height: 42px; border-radius: 21px; |
||||
|
background: #fff; border: 1px solid #d0d5dd; |
||||
|
display: flex; align-items: center; justify-content: center; gap: 8px; |
||||
|
color: #667085; font-size: 14px; font-weight: 500; |
||||
|
} |
||||
|
/* 取消按钮(替换左侧切换按钮位置) */ |
||||
|
.cancel-toggle { |
||||
|
width: 38px; height: 38px; border-radius: 50%; |
||||
|
background: #f2f4f7; border: 1px solid #eaecf0; |
||||
|
display: flex; align-items: center; justify-content: center; |
||||
|
cursor: pointer; flex-shrink: 0; color: #ef4444; font-size: 16px; |
||||
|
} |
||||
|
|
||||
|
/* PTT 实时识别文字(渐变背景) */ |
||||
|
.ptt-realtime { |
||||
|
padding: 12px 20px; margin: 0 12px 4px; |
||||
|
background: linear-gradient(135deg, #f0f4ff, #e8f5e9); |
||||
|
border: 0.5px solid #e2e8f0; |
||||
|
border-radius: 16px; |
||||
|
font-size: 15px; color: #101828; |
||||
|
text-align: center; |
||||
|
line-height: 1.4; |
||||
|
} |
||||
|
|
||||
|
/* ── 状态提示条 ── */ |
||||
|
.status-strip { |
||||
|
padding: 8px 16px; |
||||
|
display: flex; align-items: center; gap: 8px; |
||||
|
font-size: 13px; font-weight: 500; |
||||
|
} |
||||
|
.strip-speaking { background: #ecfdf3; color: #16a34a; } |
||||
|
.strip-thinking { background: #eff8ff; color: #175cd3; } |
||||
|
.strip-connecting { background: #fffaeb; color: #b54708; } |
||||
|
.strip-recording { background: #ecfdf3; color: #16a34a; } |
||||
|
.interrupt-btn { |
||||
|
margin-left: auto; |
||||
|
padding: 4px 12px; border-radius: 16px; |
||||
|
border: 1px solid #ef4444; background: transparent; |
||||
|
color: #ef4444; font-size: 12px; font-weight: 500; |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
|
||||
|
/* ── 通话底栏 ── */ |
||||
|
.call-bottom { |
||||
|
padding: 12px 20px 18px; |
||||
|
display: flex; align-items: center; gap: 12px; |
||||
|
} |
||||
|
.call-status { |
||||
|
display: flex; align-items: center; gap: 6px; flex: 1; |
||||
|
} |
||||
|
.call-dot { width: 8px; height: 8px; border-radius: 50%; } |
||||
|
.call-label { font-size: 13px; color: #667085; } |
||||
|
/* 音量指示器 */ |
||||
|
.vol-indicator { |
||||
|
width: 44px; height: 44px; border-radius: 50%; |
||||
|
background: #f2f4f7; |
||||
|
display: flex; align-items: center; justify-content: center; gap: 2px; |
||||
|
flex-shrink: 0; |
||||
|
} |
||||
|
.vol-bar { |
||||
|
width: 3px; border-radius: 1.5px; background: #d0d5dd; |
||||
|
transition: background .15s; |
||||
|
} |
||||
|
.vol-bar.active { background: #34c759; } |
||||
|
.hangup-btn { |
||||
|
width: 52px; height: 52px; border-radius: 50%; |
||||
|
background: #ef4444; border: none; color: #fff; |
||||
|
display: flex; align-items: center; justify-content: center; |
||||
|
cursor: pointer; flex-shrink: 0; |
||||
|
box-shadow: 0 4px 14px rgba(239,68,68,.3); |
||||
|
} |
||||
|
|
||||
|
/* Spinner */ |
||||
|
.spinner { |
||||
|
width: 22px; height: 22px; |
||||
|
border: 2.5px solid currentColor; border-top-color: transparent; |
||||
|
border-radius: 50%; animation: spin .7s linear infinite; |
||||
|
} |
||||
|
.spinner-sm { width: 14px; height: 14px; border-width: 2px; } |
||||
|
@keyframes spin { to { transform: rotate(360deg); } } |
||||
|
|
||||
|
/* Mini wave */ |
||||
|
.mini-wave { display: flex; gap: 2px; align-items: center; } |
||||
|
.mini-wave span { |
||||
|
display: block; width: 2.5px; border-radius: 2px; |
||||
|
animation: mwave .8s ease-in-out infinite; |
||||
|
} |
||||
|
.mini-wave span:nth-child(1) { animation-delay: 0s; } |
||||
|
.mini-wave span:nth-child(2) { animation-delay: .13s; } |
||||
|
.mini-wave span:nth-child(3) { animation-delay: .26s; } |
||||
|
@keyframes mwave { |
||||
|
0%,100% { height: 3px; } |
||||
|
50% { height: 10px; } |
||||
|
} |
||||
|
|
||||
|
.label { |
||||
|
font-size: 11px; color: #98a2b3; text-align: center; |
||||
|
padding: 6px 0 0; letter-spacing: .3px; font-weight: 500; |
||||
|
} |
||||
|
</style> |
||||
|
</head> |
||||
|
<body> |
||||
|
|
||||
|
<!-- ① 文本输入模式(空闲) --> |
||||
|
<div> |
||||
|
<p class="label">① 文本输入模式</p> |
||||
|
<div class="phone"> |
||||
|
<div class="appbar"> |
||||
|
<span class="appbar-back">‹</span> |
||||
|
<div class="appbar-avatar">晓</div> |
||||
|
<span class="appbar-name">移动精灵</span> |
||||
|
<span class="status-pill pill-idle">空闲</span> |
||||
|
</div> |
||||
|
<div class="chat-area"> |
||||
|
<div class="empty-state"> |
||||
|
<div class="empty-avatar">晓</div> |
||||
|
<div class="empty-title">移动精灵</div> |
||||
|
<div class="empty-sub">点击右下角按钮,即可与晓晓对话</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="bottom-bar"> |
||||
|
<div class="input-row"> |
||||
|
<button class="mode-toggle" title="切换到语音">🎤</button> |
||||
|
<div class="text-input-wrap"> |
||||
|
<input placeholder="继续提问..." /> |
||||
|
<button class="send-btn-inner">↑</button> |
||||
|
</div> |
||||
|
<button class="call-btn">📞</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- ② 语音输入模式(空闲) --> |
||||
|
<div> |
||||
|
<p class="label">② 语音模式 - 按住说话</p> |
||||
|
<div class="phone"> |
||||
|
<div class="appbar"> |
||||
|
<span class="appbar-back">‹</span> |
||||
|
<div class="appbar-avatar">晓</div> |
||||
|
<span class="appbar-name">移动精灵</span> |
||||
|
<span class="status-pill pill-idle">空闲</span> |
||||
|
</div> |
||||
|
<div class="chat-area"> |
||||
|
<div class="msg-row ai"> |
||||
|
<div class="ai-label"><div class="ai-dot">晓</div><span class="ai-name">晓晓</span></div> |
||||
|
<div class="bubble bubble-ai">你好!我是晓晓,有什么可以帮你的吗?</div> |
||||
|
<span class="msg-time">14:23</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="bottom-bar"> |
||||
|
<div class="input-row"> |
||||
|
<button class="mode-toggle" title="切换到文本">⌨</button> |
||||
|
<div class="ptt-btn">按住 说话</div> |
||||
|
<button class="call-btn">📞</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- ③ PTT 按住中 - 连接阶段(灰色,保留两侧按钮) --> |
||||
|
<div> |
||||
|
<p class="label">③ 按住说话 - 连接中(同空闲样式)</p> |
||||
|
<div class="phone"> |
||||
|
<div class="appbar"> |
||||
|
<span class="appbar-back">‹</span> |
||||
|
<div class="appbar-avatar">晓</div> |
||||
|
<span class="appbar-name">移动精灵</span> |
||||
|
<span class="status-pill pill-connecting">连接中…</span> |
||||
|
</div> |
||||
|
<div class="chat-area"> |
||||
|
<div class="msg-row ai"> |
||||
|
<div class="ai-label"><div class="ai-dot">晓</div><span class="ai-name">晓晓</span></div> |
||||
|
<div class="bubble bubble-ai">你好!我是晓晓,有什么可以帮你的吗?</div> |
||||
|
<span class="msg-time">14:23</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="bottom-bar"> |
||||
|
<div class="input-row"> |
||||
|
<button class="mode-toggle" style="opacity:0.4;pointer-events:none;">⌨</button> |
||||
|
<div class="ptt-connecting-center"> |
||||
|
<div class="spinner spinner-sm" style="color:#667085;"></div> |
||||
|
<span>连接中...</span> |
||||
|
</div> |
||||
|
<button class="call-btn" style="opacity:0.4;pointer-events:none;">📞</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- ④ PTT 按住中 - 录音中 + 实时识别(绿色,渐变识别背景) --> |
||||
|
<div> |
||||
|
<p class="label">④ 按住说话 - 录音中(橙色)</p> |
||||
|
<div class="phone"> |
||||
|
<div class="appbar"> |
||||
|
<span class="appbar-back">‹</span> |
||||
|
<div class="appbar-avatar">晓</div> |
||||
|
<span class="appbar-name">移动精灵</span> |
||||
|
<span class="status-pill pill-recording">识别中</span> |
||||
|
</div> |
||||
|
<div class="chat-area"> |
||||
|
<div class="msg-row ai"> |
||||
|
<div class="ai-label"><div class="ai-dot">晓</div><span class="ai-name">晓晓</span></div> |
||||
|
<div class="bubble bubble-ai">你好!我是晓晓,有什么可以帮你的吗?</div> |
||||
|
<span class="msg-time">14:23</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="bottom-bar"> |
||||
|
<!-- 居中实时识别文字(渐变背景) --> |
||||
|
<div class="ptt-realtime">今天天气怎么样?</div> |
||||
|
<div class="input-row" style="padding-top:4px;"> |
||||
|
<button class="mode-toggle" style="opacity:0.4;pointer-events:none;">⌨</button> |
||||
|
<div class="ptt-record-center"> |
||||
|
<div class="mini-wave"><span style="background:#fff;height:6px;"></span><span style="background:#fff;height:10px;"></span><span style="background:#fff;height:4px;"></span></div> |
||||
|
<span>松开发送 · 2s</span> |
||||
|
</div> |
||||
|
<button class="call-btn" style="opacity:0.4;pointer-events:none;">📞</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- ⑤ AI 思考中(输入栏保留,半透明禁用) --> |
||||
|
<div> |
||||
|
<p class="label">⑤ AI 思考中(输入栏禁用)</p> |
||||
|
<div class="phone"> |
||||
|
<div class="appbar"> |
||||
|
<span class="appbar-back">‹</span> |
||||
|
<div class="appbar-avatar">晓</div> |
||||
|
<span class="appbar-name">移动精灵</span> |
||||
|
<span class="status-pill pill-thinking">处理中</span> |
||||
|
</div> |
||||
|
<div class="chat-area"> |
||||
|
<div class="msg-row ai"> |
||||
|
<div class="ai-label"><div class="ai-dot">晓</div><span class="ai-name">晓晓</span></div> |
||||
|
<div class="bubble bubble-ai">你好!我是晓晓,有什么可以帮你的吗?</div> |
||||
|
<span class="msg-time">14:23</span> |
||||
|
</div> |
||||
|
<div class="msg-row user"> |
||||
|
<div class="bubble bubble-user">今天天气怎么样?</div> |
||||
|
<span class="msg-time">14:24</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="bottom-bar"> |
||||
|
<div class="status-strip strip-thinking"> |
||||
|
<div class="spinner spinner-sm" style="color:#175cd3;"></div> |
||||
|
<span>AI 思考中...</span> |
||||
|
</div> |
||||
|
<div class="input-row" style="padding-top:4px; opacity:0.5; pointer-events:none;"> |
||||
|
<button class="mode-toggle">⌨</button> |
||||
|
<div class="ptt-btn">按住 说话</div> |
||||
|
<button class="call-btn">📞</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- ⑥ AI 播报中(输入栏保留,可打断) --> |
||||
|
<div> |
||||
|
<p class="label">⑥ AI 播报中(可打断)</p> |
||||
|
<div class="phone"> |
||||
|
<div class="appbar"> |
||||
|
<span class="appbar-back">‹</span> |
||||
|
<div class="appbar-avatar">晓</div> |
||||
|
<span class="appbar-name">移动精灵</span> |
||||
|
<span class="status-pill pill-speaking">朗读中</span> |
||||
|
</div> |
||||
|
<div class="chat-area"> |
||||
|
<div class="msg-row user"> |
||||
|
<div class="bubble bubble-user">今天天气怎么样?</div> |
||||
|
<span class="msg-time">14:24</span> |
||||
|
</div> |
||||
|
<div class="msg-row ai"> |
||||
|
<div class="ai-label"><div class="ai-dot">晓</div><span class="ai-name">晓晓</span></div> |
||||
|
<div class="bubble bubble-ai">今天晴天,气温 18°C,微风,空气质量优,很适合出行哦</div> |
||||
|
<span class="msg-time">14:24</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="bottom-bar"> |
||||
|
<div class="status-strip strip-speaking"> |
||||
|
<div class="mini-wave"><span style="background:#16a34a;"></span><span style="background:#16a34a;"></span><span style="background:#16a34a;"></span></div> |
||||
|
<span>晓晓正在朗读...</span> |
||||
|
<button class="interrupt-btn">✕ 打断</button> |
||||
|
</div> |
||||
|
<div class="input-row" style="padding-top:4px; opacity:0.5; pointer-events:none;"> |
||||
|
<button class="mode-toggle">⌨</button> |
||||
|
<div class="ptt-btn">按住 说话</div> |
||||
|
<button class="call-btn">📞</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- ⑦ 通话模式 - 等待说话(音量指示器 + 挂断) --> |
||||
|
<div> |
||||
|
<p class="label">⑦ 通话模式 - 等待说话</p> |
||||
|
<div class="phone"> |
||||
|
<div class="appbar"> |
||||
|
<span class="appbar-back">‹</span> |
||||
|
<div class="appbar-avatar">晓</div> |
||||
|
<span class="appbar-name">移动精灵</span> |
||||
|
<span class="status-pill pill-recording">通话中 <span class="pill-timer">01:23</span></span> |
||||
|
</div> |
||||
|
<div class="chat-area"> |
||||
|
<div class="msg-row user"> |
||||
|
<div class="bubble bubble-user">今天天气怎么样?</div> |
||||
|
<span class="msg-time">14:24</span> |
||||
|
</div> |
||||
|
<div class="msg-row ai"> |
||||
|
<div class="ai-label"><div class="ai-dot">晓</div><span class="ai-name">晓晓</span></div> |
||||
|
<div class="bubble bubble-ai">今天晴天,气温 18°C,微风,空气质量优</div> |
||||
|
<span class="msg-time">14:24</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="bottom-bar"> |
||||
|
<div class="call-bottom"> |
||||
|
<div class="call-status"> |
||||
|
<div class="call-dot" style="background:#34c759;"></div> |
||||
|
<span class="call-label">通话中</span> |
||||
|
<span class="call-label">01:23</span> |
||||
|
</div> |
||||
|
<!-- 音量指示器 --> |
||||
|
<div class="vol-indicator"> |
||||
|
<div class="vol-bar active" style="height:8px;"></div> |
||||
|
<div class="vol-bar active" style="height:12px;"></div> |
||||
|
<div class="vol-bar active" style="height:16px;"></div> |
||||
|
<div class="vol-bar" style="height:20px;"></div> |
||||
|
<div class="vol-bar" style="height:24px;"></div> |
||||
|
</div> |
||||
|
<!-- 挂断按钮 --> |
||||
|
<button class="hangup-btn"> |
||||
|
<svg width="26" height="26" viewBox="0 0 24 24" fill="currentColor"><path d="M12 9c-1.6 0-3.15.25-4.6.72v3.1c0 .39-.23.74-.56.9-.98.49-1.87 1.12-2.66 1.85-.18.18-.43.28-.7.28-.28 0-.53-.11-.71-.29L.29 13.08a.956.956 0 0 1 0-1.36C3.36 8.85 7.47 7 12 7s8.64 1.85 11.71 4.72c.18.18.29.43.29.71 0 .28-.11.53-.29.71l-2.48 2.48c-.18.18-.43.29-.71.29-.27 0-.52-.1-.7-.28-.79-.73-1.68-1.36-2.66-1.85-.33-.16-.56-.5-.56-.9v-3.1C14.15 9.25 12.6 9 12 9z"/></svg> |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- ⑧ 通话模式 - AI 播报中 --> |
||||
|
<div> |
||||
|
<p class="label">⑧ 通话模式 - AI 播报</p> |
||||
|
<div class="phone"> |
||||
|
<div class="appbar"> |
||||
|
<span class="appbar-back">‹</span> |
||||
|
<div class="appbar-avatar">晓</div> |
||||
|
<span class="appbar-name">移动精灵</span> |
||||
|
<span class="status-pill pill-speaking">朗读中 <span class="pill-timer">01:48</span></span> |
||||
|
</div> |
||||
|
<div class="chat-area"> |
||||
|
<div class="msg-row user"> |
||||
|
<div class="bubble bubble-user">Flutter 有什么优势?</div> |
||||
|
<span class="msg-time">14:41</span> |
||||
|
</div> |
||||
|
<div class="msg-row ai"> |
||||
|
<div class="ai-label"><div class="ai-dot">晓</div><span class="ai-name">晓晓</span></div> |
||||
|
<div class="bubble bubble-ai">Flutter 的核心优势在于跨平台能力强、渲染性能高,以及 Dart 语言的高效开发体验...</div> |
||||
|
<span class="msg-time">14:41</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="bottom-bar"> |
||||
|
<div class="status-strip strip-speaking"> |
||||
|
<div class="mini-wave"><span style="background:#16a34a;"></span><span style="background:#16a34a;"></span><span style="background:#16a34a;"></span></div> |
||||
|
<span>晓晓正在朗读...</span> |
||||
|
<button class="interrupt-btn">✕ 打断</button> |
||||
|
</div> |
||||
|
<div class="call-bottom" style="padding-top:6px;"> |
||||
|
<div class="call-status"> |
||||
|
<div class="call-dot" style="background:#34c759;"></div> |
||||
|
<span class="call-label">通话中</span> |
||||
|
<span class="call-label">01:48</span> |
||||
|
</div> |
||||
|
<div class="vol-indicator"> |
||||
|
<div class="vol-bar" style="height:8px;"></div> |
||||
|
<div class="vol-bar" style="height:12px;"></div> |
||||
|
<div class="vol-bar" style="height:16px;"></div> |
||||
|
<div class="vol-bar" style="height:20px;"></div> |
||||
|
<div class="vol-bar" style="height:24px;"></div> |
||||
|
</div> |
||||
|
<button class="hangup-btn"> |
||||
|
<svg width="26" height="26" viewBox="0 0 24 24" fill="currentColor"><path d="M12 9c-1.6 0-3.15.25-4.6.72v3.1c0 .39-.23.74-.56.9-.98.49-1.87 1.12-2.66 1.85-.18.18-.43.28-.7.28-.28 0-.53-.11-.71-.29L.29 13.08a.956.956 0 0 1 0-1.36C3.36 8.85 7.47 7 12 7s8.64 1.85 11.71 4.72c.18.18.29.43.29.71 0 .28-.11.53-.29.71l-2.48 2.48c-.18.18-.43.29-.71.29-.27 0-.52-.1-.7-.28-.79-.73-1.68-1.36-2.66-1.85-.33-.16-.56-.5-.56-.9v-3.1C14.15 9.25 12.6 9 12 9z"/></svg> |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
</body> |
||||
|
</html> |
||||
Loading…
Reference in new issue