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