feat(前端): 重构数字人交互界面并添加全屏功能
重构前端界面布局,将控制面板改为浮动式设计,添加全屏显示功能。主要变更包括: 1. 重新设计界面布局,使用浮动面板替代固定面板 2. 实现数字人显示区域的全屏功能 3. 优化SDK容器缩放逻辑 4. 添加面板切换和状态管理功能 5. 更新样式以适应新的交互设计
This commit is contained in:
28
frontend/cert/localhost-key.pem
Normal file
28
frontend/cert/localhost-key.pem
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDIemmkD9Xbna4D
|
||||||
|
WydBTk+72YFgbaX16lTwWEaohZlUlROojevKKVSLUW4LnkHfB5AN0S+Kk8mv0vqq
|
||||||
|
17v8H+qp+p3141kdUtGuPK/Rjli5KahgVNhRkz56JDj2MkDQoP7i69lg4Nxu33+D
|
||||||
|
E6ZtMafmqgadlHLy4somPcKW2uqFHHRznXucJThGwVxKYyo5ouct86eGg5x5Ov0T
|
||||||
|
hE/9XdGkP9QJK6RgLzgoiLLAAlXvYhp/TpoGIuHRTabFsSl0vrDAtZxgsq3M5OUw
|
||||||
|
ltfUz98sgo+aZp7pev6tg1xlyJavclv02HScm0m6GLO/+legil4B5BylZZw179ye
|
||||||
|
UTn4JtYvAgMBAAECggEADOtKjhgd8kDQ7myidN4DKGCMKra8lUJyrJxbs7XZG2Je
|
||||||
|
xnCceCyKqYG0kD4a3so1Ev1FTYfxkG/0CsXLp1c2y//9y7mVzA+pIH8CEerugvXE
|
||||||
|
YOGgbdbwL0NqgkvbWANDLN3OMwtUJgPA5IvQuhFGayICbbqbxiHHeiH/vX31BmFu
|
||||||
|
NJTYH9BA/56qbMyruMvdo0WOSFgyYmTogBddTVGT4l1+xjKB1hHIggEOxcj+Kcu/
|
||||||
|
aPMh6W9a843nIt/AUkLztbpD49Jp2u9B+uHUexsp+vgGebHmn1+x5+QOjpr/4BzA
|
||||||
|
pa41ZdSZwpu10UzWDnXmmrVXuti0fhz8GdYGoD0OiQKBgQD6KiOkoRaUELwvENjV
|
||||||
|
ybMOs7rCrz3AzvvXwXl9ncP4e25zs0MO3Fdr+RnssashAQl/eMrpWbqXCJ5L8ocV
|
||||||
|
QdinicNRbiI67J7LIQYCKmoF0qHEioXZGljHCdDOi6mARgaRIXGzrfwTjYpt93BJ
|
||||||
|
ZZjAJnVemmITK6uCo3JdWdFSeQKBgQDNJ5IBzvwLL+/AkQJ4O6mNmJjsuh+0XLHi
|
||||||
|
50gAg/QkUJPFD9NIETXuAoSs+HEwoMNNOxvClNkQKuXhKeaVST5IXBNI+OLk4tAX
|
||||||
|
7kZYgmUB3WoqMRQyxi+HpcK8Vz9yEZYtcjltHaj9U+W758Pz4XDPPZRPVZynwNBT
|
||||||
|
SWQatz4D5wKBgQDS/EFNctMhE7AzHclTHabXqnvEAJpbh/ZddH6rlCiY5Dk2eaT/
|
||||||
|
3pQYq2HS/9Ftp3tEODMidZvqD8m6EhUdt4grXQQPJIzYF4KhnPt9ThpXSNtIcPAM
|
||||||
|
N61SSRQDb0UIu4aEVqmXuhBUyVZMApmAPaCDxqvMmvZGZtli98jXtI3XOQKBgHry
|
||||||
|
uAH1df+OXvTc8snzl42QYjWc5dtFoiYFzZThf7MzxxtWfJy4E8XsJG81BUDLV8A1
|
||||||
|
KFHYuq2Bx/rFDbdyCa174OLepbveYi+1UNtCah5P/4wBlcVIGLwGj0ooT5GhEItz
|
||||||
|
5Q6Ls/b+DLQsFbdW6DCLCxLwj83JNBB171RDdRsJAoGAS+fWOqOVhOiaNWfswJmz
|
||||||
|
ELOZyqrPx6cvtaaIupf1U4nxyGDZqoNhUkC8TK1rvURtbYCBeh7nMlbz/HIX2Xf1
|
||||||
|
1Za+lehJzHeNrjw2+mGo8VNkJyfkNJvIyQ6ELggrQLg65uETU8F23mhqY63k2Vdd
|
||||||
|
/69T6WWhQXpy6e6MyHT5P8Y=
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
17
frontend/cert/localhost.pem
Normal file
17
frontend/cert/localhost.pem
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICyTCCAbGgAwIBAgIJA1E28eTat1aCMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV
|
||||||
|
BAMTCWxvY2FsaG9zdDAeFw0yNjA0MzAwMDM5NTJaFw0yNzA0MzAwMDM5NTJaMBQx
|
||||||
|
EjAQBgNVBAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
|
||||||
|
ggEBAMh6aaQP1dudrgNbJ0FOT7vZgWBtpfXqVPBYRqiFmVSVE6iN68opVItRbgue
|
||||||
|
Qd8HkA3RL4qTya/S+qrXu/wf6qn6nfXjWR1S0a48r9GOWLkpqGBU2FGTPnokOPYy
|
||||||
|
QNCg/uLr2WDg3G7ff4MTpm0xp+aqBp2UcvLiyiY9wpba6oUcdHOde5wlOEbBXEpj
|
||||||
|
Kjmi5y3zp4aDnHk6/ROET/1d0aQ/1AkrpGAvOCiIssACVe9iGn9OmgYi4dFNpsWx
|
||||||
|
KXS+sMC1nGCyrczk5TCW19TP3yyCj5pmnul6/q2DXGXIlq9yW/TYdJybSboYs7/6
|
||||||
|
V6CKXgHkHKVlnDXv3J5ROfgm1i8CAwEAAaMeMBwwGgYDVR0RBBMwEYIJbG9jYWxo
|
||||||
|
b3N0hwR/AAABMA0GCSqGSIb3DQEBCwUAA4IBAQBOUidP5/w56yRWMSSkRAqAHmmC
|
||||||
|
HKd/Q/Q2Haro+vXsyeWGhsGaxaWNo99YQ17K9RDPKPXSiaRebQf5gTKEt/VFMttI
|
||||||
|
HLzJM7/X4RK+wRRs1alD+4UBxI+dISS51BAYQEMc1ib9tzqInsW6ItvBEWuIIXyt
|
||||||
|
Itwqsvkw9lH8ClgrrgLADw3mzWfFXOaadEJxjshcSNgg6V1JcC4v0D/Nve2P/pLW
|
||||||
|
KSQYbudCn4AOpNPgChNckH9yrMU8mELtl1hmO1V35vYsJ4q6BDkmoT5aE6L+p3dC
|
||||||
|
+z6A49MEZ/W/n1Pgt0nXniSmUfCnzYz3ERVx7i7dZozOAk9g463UD3aazuSg
|
||||||
|
-----END CERTIFICATE-----
|
||||||
@@ -5,28 +5,41 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>华为交互数字人 - HwICSUiSdk 演示</title>
|
<title>华为交互数字人 - HwICSUiSdk 演示</title>
|
||||||
<link rel="stylesheet" href="./styles/main.css">
|
<link rel="stylesheet" href="./styles/main.css">
|
||||||
|
<link rel="stylesheet" href="./hwICSUiSdk/HwICSUiSdk.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="app-container">
|
<!-- 数字人全视口容器 -->
|
||||||
<!-- 头部 -->
|
<div id="digitalHumanBox" class="digital-human-box">
|
||||||
<header class="header">
|
<div class="fullscreen-exit-hint">按 ESC 退出全屏</div>
|
||||||
<div class="logo">
|
<div class="placeholder">
|
||||||
<span class="logo-icon">🎭</span>
|
<span class="placeholder-icon">🤖</span>
|
||||||
<h1>华为交互数字人</h1>
|
<p>点击右下角 ⚙️ 按钮初始化 SDK</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="status-bar">
|
|
||||||
<span id="connectionStatus" class="status disconnected">未连接</span>
|
|
||||||
<span id="sdkVersion" class="version"></span>
|
|
||||||
</div>
|
</div>
|
||||||
</header>
|
|
||||||
|
|
||||||
<!-- 主内容区 -->
|
<!-- 浮动控制栏 -->
|
||||||
<main class="main-content">
|
<div class="floating-toolbar">
|
||||||
<!-- 配置面板 -->
|
<button id="toggleConfigBtn" class="toolbar-btn" title="连接配置">
|
||||||
<section class="config-panel">
|
⚙️
|
||||||
|
</button>
|
||||||
|
<button id="toggleControlBtn" class="toolbar-btn" title="对话控制">
|
||||||
|
🎮
|
||||||
|
</button>
|
||||||
|
<button id="toggleLogBtn" class="toolbar-btn" title="事件日志">
|
||||||
|
📜
|
||||||
|
</button>
|
||||||
|
<button id="fullscreenBtn" class="toolbar-btn" title="全屏显示">
|
||||||
|
⛶
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 浮动面板:连接配置 -->
|
||||||
|
<div id="configPanel" class="floating-panel config-panel">
|
||||||
|
<div class="panel-header">
|
||||||
<h2>📋 连接配置</h2>
|
<h2>📋 连接配置</h2>
|
||||||
|
<button class="panel-close" data-panel="configPanel">×</button>
|
||||||
<!-- 基本配置 -->
|
</div>
|
||||||
|
<div class="panel-content">
|
||||||
<div class="config-section">
|
<div class="config-section">
|
||||||
<h3>基本配置</h3>
|
<h3>基本配置</h3>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@@ -39,37 +52,32 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 授权码配置 -->
|
|
||||||
<div class="config-section">
|
<div class="config-section">
|
||||||
<h3>授权码</h3>
|
<h3>授权码</h3>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="onceCode">一次性授权码</label>
|
<label for="onceCode">一次性授权码</label>
|
||||||
<div class="input-with-button">
|
<div class="input-with-button">
|
||||||
<input type="text" id="onceCode" placeholder="请输入一次性授权码或点击获取" value="" readonly>
|
<input type="text" id="onceCode" placeholder="请输入一次性授权码或点击获取" value="" readonly>
|
||||||
<button id="getOnceCodeBtn" class="btn btn-secondary">🔑 获取授权码</button>
|
<button id="getOnceCodeBtn" class="btn btn-secondary">🔑 获取</button>
|
||||||
</div>
|
</div>
|
||||||
<small class="field-hint">有效期5分钟,只能使用一次</small>
|
<small class="field-hint">有效期5分钟,只能使用一次</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-actions main-actions">
|
<div class="form-actions">
|
||||||
<button id="initBtn" class="btn btn-primary">初始化 SDK</button>
|
<button id="initBtn" class="btn btn-primary">初始化 SDK</button>
|
||||||
<button id="destroyBtn" class="btn btn-danger" disabled>销毁 SDK</button>
|
<button id="destroyBtn" class="btn btn-danger" disabled>销毁 SDK</button>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- 数字人显示区 -->
|
|
||||||
<section class="digital-human-container">
|
|
||||||
<h2>🎬 数字人显示</h2>
|
|
||||||
<div id="digitalHumanBox" class="digital-human-box">
|
|
||||||
<div class="placeholder">
|
|
||||||
<span class="placeholder-icon">🤖</span>
|
|
||||||
<p>请先初始化 SDK 并创建数字人</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 控制按钮 -->
|
<!-- 浮动面板:对话控制 -->
|
||||||
<div class="control-panel">
|
<div id="controlPanel" class="floating-panel control-panel">
|
||||||
|
<div class="panel-header">
|
||||||
|
<h2>🎮 对话控制</h2>
|
||||||
|
<button class="panel-close" data-panel="controlPanel">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="panel-content">
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<h3>对话控制</h3>
|
<h3>对话控制</h3>
|
||||||
<div class="button-row">
|
<div class="button-row">
|
||||||
@@ -111,18 +119,19 @@
|
|||||||
<span class="icon">⚡</span> 打断说话
|
<span class="icon">⚡</span> 打断说话
|
||||||
</button>
|
</button>
|
||||||
<button id="toggleAudioBtn" class="btn btn-secondary" disabled>
|
<button id="toggleAudioBtn" class="btn btn-secondary" disabled>
|
||||||
<span class="icon">🔊</span> 静音/取消静音
|
<span class="icon">🔊</span> 静音
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</div>
|
||||||
|
|
||||||
<!-- 日志面板 -->
|
<!-- 浮动面板:事件日志 -->
|
||||||
<section class="log-panel">
|
<div id="logPanel" class="floating-panel log-panel">
|
||||||
|
<div class="panel-header">
|
||||||
<h2>📜 事件日志</h2>
|
<h2>📜 事件日志</h2>
|
||||||
<div class="log-toolbar">
|
<div class="log-toolbar">
|
||||||
<button id="clearLogBtn" class="btn btn-small">清空日志</button>
|
<button id="clearLogBtn" class="btn btn-small btn-secondary">清空</button>
|
||||||
<select id="logLevel">
|
<select id="logLevel">
|
||||||
<option value="debug">Debug</option>
|
<option value="debug">Debug</option>
|
||||||
<option value="info" selected>Info</option>
|
<option value="info" selected>Info</option>
|
||||||
@@ -130,17 +139,19 @@
|
|||||||
<option value="error">Error</option>
|
<option value="error">Error</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<button class="panel-close" data-panel="logPanel">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="panel-content">
|
||||||
<div id="logContainer" class="log-container"></div>
|
<div id="logContainer" class="log-container"></div>
|
||||||
</section>
|
</div>
|
||||||
</main>
|
</div>
|
||||||
|
|
||||||
<!-- 底部 -->
|
<!-- 状态栏 -->
|
||||||
<footer class="footer">
|
<div class="status-bar-floating">
|
||||||
<p>Powered by HwICSUiSdk | 华为交互数字人解决方案</p>
|
<span id="connectionStatus" class="status disconnected">未连接</span>
|
||||||
</footer>
|
<span id="sdkVersion" class="version"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- SDK 引入 -->
|
|
||||||
<script type="module" src="./scripts/main.js"></script>
|
<script type="module" src="./scripts/main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
18
frontend/package.json
Normal file
18
frontend/package.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "frontend",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node server.js",
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"type": "commonjs",
|
||||||
|
"dependencies": {
|
||||||
|
"selfsigned": "^5.5.0",
|
||||||
|
"serve-handler": "^6.1.7"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,18 +1,25 @@
|
|||||||
const API_BASE = 'http://localhost:3001';
|
const API_BASE = 'http://localhost:3001';
|
||||||
|
|
||||||
export async function fetchOnceCode() {
|
export async function fetchOnceCode() {
|
||||||
|
console.log('正在调用后端 API:', `${API_BASE}/api/once-code`);
|
||||||
|
|
||||||
const response = await fetch(`${API_BASE}/api/once-code`, {
|
const response = await fetch(`${API_BASE}/api/once-code`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
}
|
},
|
||||||
|
body: '{}'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log('后端响应状态:', response.status);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const error = await response.json();
|
const error = await response.json();
|
||||||
|
console.error('后端返回错误:', error);
|
||||||
throw new Error(error.error_msg || '获取授权码失败');
|
throw new Error(error.error_msg || '获取授权码失败');
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
console.log('后端返回数据:', data);
|
||||||
return data.once_code;
|
return data.once_code;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,41 @@
|
|||||||
/**
|
/**
|
||||||
* 华为交互数字人 - HwICSUiSdk 集成示例
|
* 华为交互数字人 - HwICSUiSdk 集成示例
|
||||||
*
|
|
||||||
* 功能说明:
|
|
||||||
* 1. SDK 初始化和销毁
|
|
||||||
* 2. 数字人创建和显示
|
|
||||||
* 3. 语音/文本对话交互
|
|
||||||
* 4. 事件监听和日志记录
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// 导入 HwICSUiSdk
|
import { HwICSUiSdk } from '../hwICSUiSdk/HwICSUiSdk.esm.js';
|
||||||
import { HwICSUiSdk, ICSEventKey, InteractionMode, ConfigKey } from '../hwICSUiSdk/HwICSUiSdk.esm.js';
|
|
||||||
import { fetchOnceCode } from './api.js';
|
import { fetchOnceCode } from './api.js';
|
||||||
|
|
||||||
// ===== 全局状态 =====
|
const ICSEventKey = {
|
||||||
|
ERROR: 'error',
|
||||||
|
JOB_INFO_CHANGE: 'jobInfoChange',
|
||||||
|
SPEAKING_START: 'speakingStart',
|
||||||
|
SPEAKING_STOP: 'speakingStop',
|
||||||
|
SPEECH_RECOGNIZED: 'speechRecognized',
|
||||||
|
SEMANTIC_RECOGNIZED: 'semanticRecognized',
|
||||||
|
ENTER_SLEEP: 'enterSleep',
|
||||||
|
ENTER_ACTIVE: 'enterActive',
|
||||||
|
LANGUAGE_INFO_CHANGE: 'languageInfoChange',
|
||||||
|
JOB_END_MENTION: 'jobEndMention',
|
||||||
|
JOB_END: 'jobEnd'
|
||||||
|
};
|
||||||
|
|
||||||
|
const InteractionMode = {
|
||||||
|
AUDIO: 'AUDIO',
|
||||||
|
TEXT: 'TEXT'
|
||||||
|
};
|
||||||
|
|
||||||
|
const ConfigKey = {
|
||||||
|
ENABLE_CAPTION: 'enableCaption',
|
||||||
|
ENABLE_CHAT_BTN: 'enableChatBtn',
|
||||||
|
ENABLE_HOT_ISSUES: 'enableHotIssues',
|
||||||
|
ENABLE_WEAK_ERROR_INFO: 'enableWeakErrorInfo',
|
||||||
|
SHOW_LANGUAGE_UI: 'showLanguageUI',
|
||||||
|
ENABLE_DEVICE_SETTING: 'enableDeviceSetting',
|
||||||
|
ENABLE_NETWORK_CHECK: 'enableNetworkCheck',
|
||||||
|
AUTO_PLAY_REMOTE_VIDEO: 'autoPlayRemoteVideo',
|
||||||
|
DEFAULT_MUTE_REMOTE_AUDIO: 'defaultMuteRemoteAudio'
|
||||||
|
};
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
isInitialized: false,
|
isInitialized: false,
|
||||||
isChatting: false,
|
isChatting: false,
|
||||||
@@ -22,14 +45,11 @@ const state = {
|
|||||||
currentInteractionMode: InteractionMode.AUDIO
|
currentInteractionMode: InteractionMode.AUDIO
|
||||||
};
|
};
|
||||||
|
|
||||||
// ===== DOM 元素引用 =====
|
|
||||||
const elements = {
|
const elements = {
|
||||||
// 配置输入
|
|
||||||
serverAddress: document.getElementById('serverAddress'),
|
serverAddress: document.getElementById('serverAddress'),
|
||||||
robotId: document.getElementById('robotId'),
|
robotId: document.getElementById('robotId'),
|
||||||
onceCode: document.getElementById('onceCode'),
|
onceCode: document.getElementById('onceCode'),
|
||||||
|
|
||||||
// 按钮
|
|
||||||
initBtn: document.getElementById('initBtn'),
|
initBtn: document.getElementById('initBtn'),
|
||||||
destroyBtn: document.getElementById('destroyBtn'),
|
destroyBtn: document.getElementById('destroyBtn'),
|
||||||
getOnceCodeBtn: document.getElementById('getOnceCodeBtn'),
|
getOnceCodeBtn: document.getElementById('getOnceCodeBtn'),
|
||||||
@@ -41,19 +61,31 @@ const elements = {
|
|||||||
interruptBtn: document.getElementById('interruptBtn'),
|
interruptBtn: document.getElementById('interruptBtn'),
|
||||||
toggleAudioBtn: document.getElementById('toggleAudioBtn'),
|
toggleAudioBtn: document.getElementById('toggleAudioBtn'),
|
||||||
clearLogBtn: document.getElementById('clearLogBtn'),
|
clearLogBtn: document.getElementById('clearLogBtn'),
|
||||||
|
fullscreenBtn: document.getElementById('fullscreenBtn'),
|
||||||
|
toggleConfigBtn: document.getElementById('toggleConfigBtn'),
|
||||||
|
toggleControlBtn: document.getElementById('toggleControlBtn'),
|
||||||
|
toggleLogBtn: document.getElementById('toggleLogBtn'),
|
||||||
|
|
||||||
// 输入框
|
|
||||||
textInput: document.getElementById('textInput'),
|
textInput: document.getElementById('textInput'),
|
||||||
logLevel: document.getElementById('logLevel'),
|
logLevel: document.getElementById('logLevel'),
|
||||||
|
|
||||||
// 显示区域
|
|
||||||
digitalHumanBox: document.getElementById('digitalHumanBox'),
|
digitalHumanBox: document.getElementById('digitalHumanBox'),
|
||||||
logContainer: document.getElementById('logContainer'),
|
logContainer: document.getElementById('logContainer'),
|
||||||
connectionStatus: document.getElementById('connectionStatus'),
|
connectionStatus: document.getElementById('connectionStatus'),
|
||||||
sdkVersion: document.getElementById('sdkVersion')
|
sdkVersion: document.getElementById('sdkVersion')
|
||||||
};
|
};
|
||||||
|
|
||||||
// ===== 日志工具 =====
|
const fullscreenState = {
|
||||||
|
isFullscreen: false,
|
||||||
|
sdkInitialized: false
|
||||||
|
};
|
||||||
|
|
||||||
|
const panelState = {
|
||||||
|
configPanel: false,
|
||||||
|
controlPanel: false,
|
||||||
|
logPanel: false
|
||||||
|
};
|
||||||
|
|
||||||
const logger = {
|
const logger = {
|
||||||
log(level, message, data = null) {
|
log(level, message, data = null) {
|
||||||
const timestamp = new Date().toLocaleTimeString('zh-CN', { hour12: false });
|
const timestamp = new Date().toLocaleTimeString('zh-CN', { hour12: false });
|
||||||
@@ -81,7 +113,6 @@ const logger = {
|
|||||||
error(message, data) { this.log('ERROR', message, data); }
|
error(message, data) { this.log('ERROR', message, data); }
|
||||||
};
|
};
|
||||||
|
|
||||||
// ===== SDK 事件监听配置 =====
|
|
||||||
const eventListeners = {
|
const eventListeners = {
|
||||||
[ICSEventKey.ERROR]: (error) => {
|
[ICSEventKey.ERROR]: (error) => {
|
||||||
logger.error('SDK 错误', error);
|
logger.error('SDK 错误', error);
|
||||||
@@ -138,7 +169,6 @@ const eventListeners = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// ===== UI 状态更新 =====
|
|
||||||
function updateUIState() {
|
function updateUIState() {
|
||||||
const { isInitialized, isChatting, isSpeaking } = state;
|
const { isInitialized, isChatting, isSpeaking } = state;
|
||||||
|
|
||||||
@@ -175,7 +205,39 @@ function updateConnectionStatus(connected) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== SDK 操作 =====
|
function togglePanel(panelId) {
|
||||||
|
const panel = document.getElementById(panelId);
|
||||||
|
console.log('切换面板:', panelId, '找到元素:', !!panel);
|
||||||
|
if (!panel) return;
|
||||||
|
|
||||||
|
const isVisible = panel.classList.contains('visible');
|
||||||
|
console.log('当前可见:', isVisible);
|
||||||
|
|
||||||
|
Object.keys(panelState).forEach(key => {
|
||||||
|
const el = document.getElementById(key);
|
||||||
|
if (el) el.classList.remove('visible');
|
||||||
|
panelState[key] = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('.toolbar-btn').forEach(btn => btn.classList.remove('active'));
|
||||||
|
|
||||||
|
if (!isVisible) {
|
||||||
|
panel.classList.add('visible');
|
||||||
|
panelState[panelId] = true;
|
||||||
|
console.log('面板已显示:', panelId);
|
||||||
|
|
||||||
|
const btnMap = {
|
||||||
|
configPanel: 'toggleConfigBtn',
|
||||||
|
controlPanel: 'toggleControlBtn',
|
||||||
|
logPanel: 'toggleLogBtn'
|
||||||
|
};
|
||||||
|
|
||||||
|
const btnId = btnMap[panelId];
|
||||||
|
if (btnId && elements[btnId]) {
|
||||||
|
elements[btnId].classList.add('active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function initializeSDK() {
|
async function initializeSDK() {
|
||||||
try {
|
try {
|
||||||
@@ -221,10 +283,10 @@ async function initializeSDK() {
|
|||||||
[ConfigKey.ENABLE_CAPTION]: true,
|
[ConfigKey.ENABLE_CAPTION]: true,
|
||||||
[ConfigKey.ENABLE_CHAT_BTN]: true,
|
[ConfigKey.ENABLE_CHAT_BTN]: true,
|
||||||
[ConfigKey.ENABLE_HOT_ISSUES]: true,
|
[ConfigKey.ENABLE_HOT_ISSUES]: true,
|
||||||
[ConfigKey.ENABLE_WEAK_ERROR_INFO]: true,
|
[ConfigKey.ENABLE_WEAK_ERROR_INFO]: false,
|
||||||
[ConfigKey.SHOW_LANGUAGE_UI]: true,
|
[ConfigKey.SHOW_LANGUAGE_UI]: false,
|
||||||
[ConfigKey.ENABLE_DEVICE_SETTING]: true,
|
[ConfigKey.ENABLE_DEVICE_SETTING]: false,
|
||||||
[ConfigKey.ENABLE_NETWORK_CHECK]: true,
|
[ConfigKey.ENABLE_NETWORK_CHECK]: false,
|
||||||
[ConfigKey.AUTO_PLAY_REMOTE_VIDEO]: true,
|
[ConfigKey.AUTO_PLAY_REMOTE_VIDEO]: true,
|
||||||
[ConfigKey.DEFAULT_MUTE_REMOTE_AUDIO]: false
|
[ConfigKey.DEFAULT_MUTE_REMOTE_AUDIO]: false
|
||||||
},
|
},
|
||||||
@@ -234,6 +296,7 @@ async function initializeSDK() {
|
|||||||
await HwICSUiSdk.create(createParam);
|
await HwICSUiSdk.create(createParam);
|
||||||
|
|
||||||
state.isInitialized = true;
|
state.isInitialized = true;
|
||||||
|
fullscreenState.sdkInitialized = true;
|
||||||
updateConnectionStatus(true);
|
updateConnectionStatus(true);
|
||||||
updateUIState();
|
updateUIState();
|
||||||
|
|
||||||
@@ -241,6 +304,9 @@ async function initializeSDK() {
|
|||||||
|
|
||||||
logger.info('SDK 初始化成功', { version: HwICSUiSdk.version });
|
logger.info('SDK 初始化成功', { version: HwICSUiSdk.version });
|
||||||
|
|
||||||
|
rebindDynamicButtons();
|
||||||
|
setTimeout(calculateScale, 500);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('SDK 初始化失败', error);
|
logger.error('SDK 初始化失败', error);
|
||||||
state.isInitialized = false;
|
state.isInitialized = false;
|
||||||
@@ -258,14 +324,16 @@ async function destroySDK() {
|
|||||||
state.isChatting = false;
|
state.isChatting = false;
|
||||||
state.isSpeaking = false;
|
state.isSpeaking = false;
|
||||||
state.jobInfo = null;
|
state.jobInfo = null;
|
||||||
|
fullscreenState.sdkInitialized = false;
|
||||||
|
|
||||||
updateConnectionStatus(false);
|
updateConnectionStatus(false);
|
||||||
updateUIState();
|
updateUIState();
|
||||||
|
|
||||||
elements.digitalHumanBox.innerHTML = `
|
elements.digitalHumanBox.innerHTML = `
|
||||||
|
<div class="fullscreen-exit-hint">按 ESC 退出全屏</div>
|
||||||
<div class="placeholder">
|
<div class="placeholder">
|
||||||
<span class="placeholder-icon">🤖</span>
|
<span class="placeholder-icon">🤖</span>
|
||||||
<p>请先初始化 SDK 并创建数字人</p>
|
<p>点击右下角 ⚙️ 按钮初始化 SDK</p>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -436,7 +504,7 @@ async function handleGetOnceCode() {
|
|||||||
logger.error('获取授权码失败', error.message);
|
logger.error('获取授权码失败', error.message);
|
||||||
} finally {
|
} finally {
|
||||||
elements.getOnceCodeBtn.disabled = false;
|
elements.getOnceCodeBtn.disabled = false;
|
||||||
elements.getOnceCodeBtn.textContent = '🔑 获取授权码';
|
elements.getOnceCodeBtn.textContent = '🔑 获取';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -445,7 +513,67 @@ function clearLogs() {
|
|||||||
logger.info('日志已清空');
|
logger.info('日志已清空');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== 事件绑定 =====
|
function calculateScale() {
|
||||||
|
const container = elements.digitalHumanBox;
|
||||||
|
const sdkContainer = container.querySelector('.digital-intelligent-interaction');
|
||||||
|
|
||||||
|
if (!sdkContainer) return;
|
||||||
|
|
||||||
|
const containerWidth = container.clientWidth;
|
||||||
|
const containerHeight = container.clientHeight;
|
||||||
|
|
||||||
|
const sdkWidth = 1920;
|
||||||
|
const sdkHeight = 1080;
|
||||||
|
|
||||||
|
const scaleX = containerWidth / sdkWidth;
|
||||||
|
const scaleY = containerHeight / sdkHeight;
|
||||||
|
const scale = Math.max(scaleX, scaleY);
|
||||||
|
|
||||||
|
sdkContainer.style.transform = `scale(${scale})`;
|
||||||
|
sdkContainer.style.transformOrigin = 'center center';
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleFullscreen() {
|
||||||
|
if (!fullscreenState.sdkInitialized) {
|
||||||
|
logger.warn('请先初始化 SDK');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fullscreenState.isFullscreen = !fullscreenState.isFullscreen;
|
||||||
|
|
||||||
|
if (fullscreenState.isFullscreen) {
|
||||||
|
elements.digitalHumanBox.classList.add('fullscreen');
|
||||||
|
elements.fullscreenBtn.innerHTML = '⛶';
|
||||||
|
elements.fullscreenBtn.title = '退出全屏';
|
||||||
|
logger.info('进入全屏模式');
|
||||||
|
} else {
|
||||||
|
elements.digitalHumanBox.classList.remove('fullscreen');
|
||||||
|
elements.fullscreenBtn.innerHTML = '⛶';
|
||||||
|
elements.fullscreenBtn.title = '全屏显示';
|
||||||
|
logger.info('退出全屏模式');
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(calculateScale, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleFullscreenKeydown(e) {
|
||||||
|
if (e.key === 'Escape' && fullscreenState.isFullscreen) {
|
||||||
|
toggleFullscreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initScaling() {
|
||||||
|
const resizeObserver = new ResizeObserver(() => {
|
||||||
|
if (fullscreenState.sdkInitialized) {
|
||||||
|
calculateScale();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
resizeObserver.observe(elements.digitalHumanBox);
|
||||||
|
|
||||||
|
window.addEventListener('keydown', handleFullscreenKeydown);
|
||||||
|
}
|
||||||
|
|
||||||
function bindEvents() {
|
function bindEvents() {
|
||||||
elements.initBtn.addEventListener('click', initializeSDK);
|
elements.initBtn.addEventListener('click', initializeSDK);
|
||||||
elements.destroyBtn.addEventListener('click', destroySDK);
|
elements.destroyBtn.addEventListener('click', destroySDK);
|
||||||
@@ -475,6 +603,18 @@ function bindEvents() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
elements.toggleConfigBtn.addEventListener('click', () => togglePanel('configPanel'));
|
||||||
|
elements.toggleControlBtn.addEventListener('click', () => togglePanel('controlPanel'));
|
||||||
|
elements.toggleLogBtn.addEventListener('click', () => togglePanel('logPanel'));
|
||||||
|
elements.fullscreenBtn.addEventListener('click', toggleFullscreen);
|
||||||
|
|
||||||
|
document.querySelectorAll('.panel-close').forEach(btn => {
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
const panelId = btn.dataset.panel;
|
||||||
|
if (panelId) togglePanel(panelId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
window.addEventListener('beforeunload', () => {
|
window.addEventListener('beforeunload', () => {
|
||||||
if (state.isInitialized) {
|
if (state.isInitialized) {
|
||||||
HwICSUiSdk.destroy();
|
HwICSUiSdk.destroy();
|
||||||
@@ -482,13 +622,21 @@ function bindEvents() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== 初始化 =====
|
function rebindDynamicButtons() {
|
||||||
|
const fullscreenBtn = document.getElementById('fullscreenBtn');
|
||||||
|
if (fullscreenBtn) {
|
||||||
|
fullscreenBtn.removeEventListener('click', toggleFullscreen);
|
||||||
|
fullscreenBtn.addEventListener('click', toggleFullscreen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
logger.info('应用初始化完成');
|
logger.info('应用初始化完成');
|
||||||
logger.info('HwICSUiSdk 版本', { version: HwICSUiSdk.version, buildTime: HwICSUiSdk.buildTime });
|
logger.info('HwICSUiSdk 版本', { version: HwICSUiSdk.version, buildTime: HwICSUiSdk.buildTime });
|
||||||
|
|
||||||
bindEvents();
|
bindEvents();
|
||||||
updateUIState();
|
updateUIState();
|
||||||
|
initScaling();
|
||||||
}
|
}
|
||||||
|
|
||||||
init();
|
init();
|
||||||
|
|||||||
68
frontend/server.js
Normal file
68
frontend/server.js
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
const https = require('https');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const selfsigned = require('selfsigned');
|
||||||
|
const handler = require('serve-handler');
|
||||||
|
|
||||||
|
const PORT = 8443;
|
||||||
|
const CERT_DIR = path.join(__dirname, 'cert');
|
||||||
|
|
||||||
|
// 确保证书目录存在
|
||||||
|
if (!fs.existsSync(CERT_DIR)) {
|
||||||
|
fs.mkdirSync(CERT_DIR, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const CERT_PATH = path.join(CERT_DIR, 'localhost.pem');
|
||||||
|
const KEY_PATH = path.join(CERT_DIR, 'localhost-key.pem');
|
||||||
|
|
||||||
|
async function startServer() {
|
||||||
|
let cert, key;
|
||||||
|
|
||||||
|
if (fs.existsSync(CERT_PATH) && fs.existsSync(KEY_PATH)) {
|
||||||
|
console.log('使用已生成的自签名证书...');
|
||||||
|
cert = fs.readFileSync(CERT_PATH);
|
||||||
|
key = fs.readFileSync(KEY_PATH);
|
||||||
|
} else {
|
||||||
|
console.log('正在生成自签名证书...');
|
||||||
|
const attrs = [{ name: 'commonName', value: 'localhost' }];
|
||||||
|
const pems = await selfsigned.generate(attrs, {
|
||||||
|
keySize: 2048,
|
||||||
|
algorithm: 'sha256',
|
||||||
|
extensions: [
|
||||||
|
{
|
||||||
|
name: 'subjectAltName',
|
||||||
|
altNames: [
|
||||||
|
{ type: 2, value: 'localhost' },
|
||||||
|
{ type: 7, ip: '127.0.0.1' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
fs.writeFileSync(CERT_PATH, pems.cert);
|
||||||
|
fs.writeFileSync(KEY_PATH, pems.private);
|
||||||
|
cert = pems.cert;
|
||||||
|
key = pems.private;
|
||||||
|
console.log('自签名证书已生成并保存');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建 HTTPS 服务器
|
||||||
|
const server = https.createServer({ key, cert }, (req, res) => {
|
||||||
|
return handler(req, res, {
|
||||||
|
public: __dirname,
|
||||||
|
rewrites: [
|
||||||
|
{ source: '**', destination: '/index.html' }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen(PORT, () => {
|
||||||
|
console.log(`前端 HTTPS 服务已启动: https://localhost:${PORT}`);
|
||||||
|
console.log('注意:浏览器会提示证书不安全,请点击"高级"->"继续访问"');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
startServer().catch(err => {
|
||||||
|
console.error('启动失败:', err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
@@ -1,18 +1,15 @@
|
|||||||
/* ===== CSS 变量定义 ===== */
|
/* ===== CSS 变量定义 ===== */
|
||||||
:root {
|
:root {
|
||||||
/* 主色调 - 科技蓝 */
|
|
||||||
--primary-color: #2563eb;
|
--primary-color: #2563eb;
|
||||||
--primary-dark: #1d4ed8;
|
--primary-dark: #1d4ed8;
|
||||||
--primary-light: #3b82f6;
|
--primary-light: #3b82f6;
|
||||||
--primary-gradient: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);
|
--primary-gradient: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);
|
||||||
|
|
||||||
/* 辅助色 */
|
|
||||||
--success-color: #10b981;
|
--success-color: #10b981;
|
||||||
--warning-color: #f59e0b;
|
--warning-color: #f59e0b;
|
||||||
--danger-color: #ef4444;
|
--danger-color: #ef4444;
|
||||||
--info-color: #06b6d4;
|
--info-color: #06b6d4;
|
||||||
|
|
||||||
/* 中性色 */
|
|
||||||
--bg-primary: #0f172a;
|
--bg-primary: #0f172a;
|
||||||
--bg-secondary: #1e293b;
|
--bg-secondary: #1e293b;
|
||||||
--bg-tertiary: #334155;
|
--bg-tertiary: #334155;
|
||||||
@@ -21,26 +18,22 @@
|
|||||||
--text-muted: #64748b;
|
--text-muted: #64748b;
|
||||||
--border-color: #475569;
|
--border-color: #475569;
|
||||||
|
|
||||||
/* 阴影 */
|
|
||||||
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.3);
|
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.3);
|
||||||
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4);
|
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4);
|
||||||
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.5);
|
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.5);
|
||||||
--shadow-glow: 0 0 20px rgba(37, 99, 235, 0.3);
|
--shadow-glow: 0 0 20px rgba(37, 99, 235, 0.3);
|
||||||
|
|
||||||
/* 间距 */
|
|
||||||
--spacing-xs: 0.25rem;
|
--spacing-xs: 0.25rem;
|
||||||
--spacing-sm: 0.5rem;
|
--spacing-sm: 0.5rem;
|
||||||
--spacing-md: 1rem;
|
--spacing-md: 1rem;
|
||||||
--spacing-lg: 1.5rem;
|
--spacing-lg: 1.5rem;
|
||||||
--spacing-xl: 2rem;
|
--spacing-xl: 2rem;
|
||||||
|
|
||||||
/* 圆角 */
|
|
||||||
--radius-sm: 0.375rem;
|
--radius-sm: 0.375rem;
|
||||||
--radius-md: 0.5rem;
|
--radius-md: 0.5rem;
|
||||||
--radius-lg: 0.75rem;
|
--radius-lg: 0.75rem;
|
||||||
--radius-xl: 1rem;
|
--radius-xl: 1rem;
|
||||||
|
|
||||||
/* 字体 */
|
|
||||||
--font-family: 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif;
|
--font-family: 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,128 +49,207 @@ body {
|
|||||||
background: var(--bg-primary);
|
background: var(--bg-primary);
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
line-height: 1.6;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===== 应用容器 ===== */
|
/* ===== 数字人全视口容器 ===== */
|
||||||
.app-container {
|
.digital-human-box {
|
||||||
min-height: 100vh;
|
position: fixed;
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ===== 头部 ===== */
|
|
||||||
.header {
|
|
||||||
background: var(--bg-secondary);
|
|
||||||
border-bottom: 1px solid var(--border-color);
|
|
||||||
padding: var(--spacing-md) var(--spacing-xl);
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 100;
|
left: 0;
|
||||||
box-shadow: var(--shadow-md);
|
width: 100vw;
|
||||||
}
|
height: 100vh;
|
||||||
|
background: var(--bg-primary);
|
||||||
.logo {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--spacing-md);
|
justify-content: center;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo-icon {
|
.digital-human-box.active {
|
||||||
font-size: 2rem;
|
box-shadow: inset 0 0 50px rgba(37, 99, 235, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.digital-human-box::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 20% 80%, rgba(37, 99, 235, 0.05) 0%, transparent 50%),
|
||||||
|
radial-gradient(circle at 80% 20%, rgba(6, 182, 212, 0.05) 0%, transparent 50%);
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.placeholder {
|
||||||
|
text-align: center;
|
||||||
|
color: var(--text-muted);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.placeholder-icon {
|
||||||
|
font-size: 5rem;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: var(--spacing-md);
|
||||||
|
opacity: 0.5;
|
||||||
animation: float 3s ease-in-out infinite;
|
animation: float 3s ease-in-out infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes float {
|
@keyframes float {
|
||||||
0%, 100% { transform: translateY(0); }
|
0%, 100% { transform: translateY(0); }
|
||||||
50% { transform: translateY(-5px); }
|
50% { transform: translateY(-10px); }
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo h1 {
|
.placeholder p {
|
||||||
font-size: 1.5rem;
|
font-size: 1rem;
|
||||||
font-weight: 600;
|
opacity: 0.7;
|
||||||
background: linear-gradient(135deg, #60a5fa 0%, #3b82f6 50%, #2563eb 100%);
|
|
||||||
-webkit-background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
background-clip: text;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-bar {
|
.fullscreen-exit-hint {
|
||||||
display: flex;
|
position: absolute;
|
||||||
align-items: center;
|
bottom: var(--spacing-lg);
|
||||||
gap: var(--spacing-lg);
|
left: 50%;
|
||||||
}
|
transform: translateX(-50%);
|
||||||
|
background: rgba(30, 41, 59, 0.8);
|
||||||
.status {
|
backdrop-filter: blur(10px);
|
||||||
padding: var(--spacing-xs) var(--spacing-md);
|
padding: var(--spacing-sm) var(--spacing-md);
|
||||||
border-radius: var(--radius-full);
|
border-radius: var(--radius-md);
|
||||||
|
color: var(--text-secondary);
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
font-weight: 500;
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.digital-human-box.fullscreen:hover .fullscreen-exit-hint {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== 浮动控制栏 ===== */
|
||||||
|
.floating-toolbar {
|
||||||
|
position: fixed;
|
||||||
|
bottom: var(--spacing-xl);
|
||||||
|
right: var(--spacing-xl);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
flex-direction: column;
|
||||||
gap: var(--spacing-xs);
|
gap: var(--spacing-sm);
|
||||||
|
z-index: 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status::before {
|
.toolbar-btn {
|
||||||
content: '';
|
width: 50px;
|
||||||
width: 8px;
|
height: 50px;
|
||||||
height: 8px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: currentColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status.connected {
|
|
||||||
background: rgba(16, 185, 129, 0.2);
|
|
||||||
color: var(--success-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.status.disconnected {
|
|
||||||
background: rgba(239, 68, 68, 0.2);
|
|
||||||
color: var(--danger-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.version {
|
|
||||||
color: var(--text-muted);
|
|
||||||
font-size: 0.875rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ===== 主内容区 ===== */
|
|
||||||
.main-content {
|
|
||||||
flex: 1;
|
|
||||||
padding: var(--spacing-xl);
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 320px 1fr 380px;
|
|
||||||
gap: var(--spacing-xl);
|
|
||||||
max-width: 1600px;
|
|
||||||
margin: 0 auto;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ===== 面板通用样式 ===== */
|
|
||||||
section {
|
|
||||||
background: var(--bg-secondary);
|
|
||||||
border-radius: var(--radius-lg);
|
border-radius: var(--radius-lg);
|
||||||
padding: var(--spacing-lg);
|
background: var(--bg-secondary);
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 1.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.2s ease;
|
||||||
box-shadow: var(--shadow-md);
|
box-shadow: var(--shadow-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
section h2 {
|
.toolbar-btn:hover {
|
||||||
font-size: 1.125rem;
|
background: var(--primary-color);
|
||||||
font-weight: 600;
|
border-color: var(--primary-color);
|
||||||
margin-bottom: var(--spacing-lg);
|
transform: scale(1.1);
|
||||||
color: var(--text-primary);
|
box-shadow: var(--shadow-glow);
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--spacing-sm);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===== 配置面板 ===== */
|
.toolbar-btn:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-btn.active {
|
||||||
|
background: var(--primary-color);
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== 浮动面板通用样式 ===== */
|
||||||
|
.floating-panel {
|
||||||
|
position: fixed;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--radius-xl);
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
z-index: 999;
|
||||||
|
display: none;
|
||||||
|
flex-direction: column;
|
||||||
|
max-height: 80vh;
|
||||||
|
overflow: hidden;
|
||||||
|
animation: panelSlideIn 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes panelSlideIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(10px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.floating-panel.visible {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: var(--spacing-md) var(--spacing-lg);
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-header h2 {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-close {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 1.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-close:hover {
|
||||||
|
background: var(--danger-color);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-content {
|
||||||
|
padding: var(--spacing-lg);
|
||||||
|
overflow-y: auto;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== 连接配置面板 ===== */
|
||||||
.config-panel {
|
.config-panel {
|
||||||
height: fit-content;
|
bottom: calc(var(--spacing-xl) + 60px);
|
||||||
|
right: var(--spacing-xl);
|
||||||
|
width: 360px;
|
||||||
|
max-height: 70vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.config-section {
|
.config-section {
|
||||||
@@ -238,16 +310,6 @@ section h2 {
|
|||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-with-status {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-with-status input {
|
|
||||||
padding-right: 80px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-with-button {
|
.input-with-button {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--spacing-sm);
|
gap: var(--spacing-sm);
|
||||||
@@ -257,30 +319,6 @@ section h2 {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.field-status {
|
|
||||||
position: absolute;
|
|
||||||
right: var(--spacing-sm);
|
|
||||||
font-size: 0.75rem;
|
|
||||||
padding: 2px 8px;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.field-status.success {
|
|
||||||
background: rgba(16, 185, 129, 0.2);
|
|
||||||
color: var(--success-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.field-status.error {
|
|
||||||
background: rgba(239, 68, 68, 0.2);
|
|
||||||
color: var(--danger-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.field-status.loading {
|
|
||||||
background: rgba(6, 182, 212, 0.2);
|
|
||||||
color: var(--info-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.field-hint {
|
.field-hint {
|
||||||
display: block;
|
display: block;
|
||||||
margin-top: var(--spacing-xs);
|
margin-top: var(--spacing-xs);
|
||||||
@@ -291,13 +329,194 @@ section h2 {
|
|||||||
.form-actions {
|
.form-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--spacing-sm);
|
gap: var(--spacing-sm);
|
||||||
margin-top: var(--spacing-md);
|
margin-top: var(--spacing-lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-actions.main-actions {
|
/* ===== 对话控制面板 ===== */
|
||||||
margin-top: var(--spacing-xl);
|
.control-panel {
|
||||||
padding-top: var(--spacing-lg);
|
bottom: calc(var(--spacing-xl) + 60px);
|
||||||
border-top: 2px solid var(--border-color);
|
right: var(--spacing-xl);
|
||||||
|
width: 360px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-group {
|
||||||
|
margin-bottom: var(--spacing-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-group:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-group h3 {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-bottom: var(--spacing-sm);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-row {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-row .btn {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-input-row {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-input-row input {
|
||||||
|
flex: 1;
|
||||||
|
padding: var(--spacing-sm) var(--spacing-md);
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-input-row input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-input-row input:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== 日志面板 ===== */
|
||||||
|
.log-panel {
|
||||||
|
bottom: calc(var(--spacing-xl) + 60px);
|
||||||
|
right: var(--spacing-xl);
|
||||||
|
width: 450px;
|
||||||
|
max-height: 60vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-toolbar {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-toolbar select {
|
||||||
|
padding: var(--spacing-xs) var(--spacing-sm);
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-container {
|
||||||
|
overflow-y: auto;
|
||||||
|
background: var(--bg-primary);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
font-family: 'Consolas', 'Monaco', monospace;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
max-height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-entry {
|
||||||
|
padding: var(--spacing-xs) 0;
|
||||||
|
border-bottom: 1px solid var(--bg-tertiary);
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
animation: slideIn 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-entry:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-time {
|
||||||
|
color: var(--text-muted);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-level {
|
||||||
|
font-weight: 600;
|
||||||
|
min-width: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-level.debug {
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-level.info {
|
||||||
|
color: var(--info-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-level.warn {
|
||||||
|
color: var(--warning-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-level.error {
|
||||||
|
color: var(--danger-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-message {
|
||||||
|
color: var(--text-primary);
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== 状态栏 ===== */
|
||||||
|
.status-bar-floating {
|
||||||
|
position: fixed;
|
||||||
|
top: var(--spacing-lg);
|
||||||
|
left: var(--spacing-lg);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
z-index: 10000;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
padding: var(--spacing-sm) var(--spacing-md);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
padding: var(--spacing-xs) var(--spacing-md);
|
||||||
|
border-radius: var(--radius-full);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status::before {
|
||||||
|
content: '';
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status.connected {
|
||||||
|
background: rgba(16, 185, 129, 0.2);
|
||||||
|
color: var(--success-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status.disconnected {
|
||||||
|
background: rgba(239, 68, 68, 0.2);
|
||||||
|
color: var(--danger-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.version {
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 0.875rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===== 按钮样式 ===== */
|
/* ===== 按钮样式 ===== */
|
||||||
@@ -374,233 +593,60 @@ section h2 {
|
|||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===== 数字人显示区 ===== */
|
/* ===== 全屏模式 ===== */
|
||||||
.digital-human-container {
|
.digital-human-box.fullscreen {
|
||||||
display: flex;
|
z-index: 9999 !important;
|
||||||
flex-direction: column;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.digital-human-box {
|
.digital-human-box.fullscreen .fullscreen-exit-hint {
|
||||||
flex: 1;
|
opacity: 1;
|
||||||
min-height: 400px;
|
|
||||||
background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
border: 2px dashed var(--border-color);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
margin-bottom: var(--spacing-lg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.digital-human-box.active {
|
/* ===== SDK 响应式缩放 ===== */
|
||||||
border-style: solid;
|
.digital-human-box .sdk-scaling-wrapper {
|
||||||
border-color: var(--primary-color);
|
|
||||||
box-shadow: var(--shadow-glow);
|
|
||||||
}
|
|
||||||
|
|
||||||
.digital-human-box::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
width: 100%;
|
||||||
bottom: 0;
|
height: 100%;
|
||||||
background:
|
|
||||||
radial-gradient(circle at 20% 80%, rgba(37, 99, 235, 0.1) 0%, transparent 50%),
|
|
||||||
radial-gradient(circle at 80% 20%, rgba(6, 182, 212, 0.1) 0%, transparent 50%);
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder {
|
|
||||||
text-align: center;
|
|
||||||
color: var(--text-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder-icon {
|
|
||||||
font-size: 4rem;
|
|
||||||
display: block;
|
|
||||||
margin-bottom: var(--spacing-md);
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ===== 控制面板 ===== */
|
|
||||||
.control-panel {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--spacing-lg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-group h3 {
|
|
||||||
font-size: 0.875rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
margin-bottom: var(--spacing-sm);
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-row {
|
|
||||||
display: flex;
|
|
||||||
gap: var(--spacing-sm);
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-row .btn {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-input-row {
|
|
||||||
display: flex;
|
|
||||||
gap: var(--spacing-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-input-row input {
|
|
||||||
flex: 1;
|
|
||||||
padding: var(--spacing-sm) var(--spacing-md);
|
|
||||||
background: var(--bg-tertiary);
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
color: var(--text-primary);
|
|
||||||
font-size: 0.875rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-input-row input:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: var(--primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-input-row input:disabled {
|
|
||||||
opacity: 0.5;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ===== 日志面板 ===== */
|
|
||||||
.log-panel {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
max-height: calc(100vh - 200px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.log-toolbar {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: var(--spacing-md);
|
justify-content: center;
|
||||||
|
overflow: hidden;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.log-toolbar select {
|
.digital-human-box .sdk-scaling-wrapper .sdk-inner {
|
||||||
padding: var(--spacing-xs) var(--spacing-sm);
|
transform-origin: center center;
|
||||||
background: var(--bg-tertiary);
|
width: 100%;
|
||||||
border: 1px solid var(--border-color);
|
height: 100%;
|
||||||
border-radius: var(--radius-md);
|
|
||||||
color: var(--text-primary);
|
|
||||||
font-size: 0.875rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.log-container {
|
.digital-human-box .sdk-scaling-wrapper .sdk-inner .digital-intelligent-interaction {
|
||||||
flex: 1;
|
width: 100% !important;
|
||||||
overflow-y: auto;
|
height: 100% !important;
|
||||||
background: var(--bg-primary);
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
padding: var(--spacing-md);
|
|
||||||
font-family: 'Consolas', 'Monaco', monospace;
|
|
||||||
font-size: 0.8125rem;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.log-entry {
|
/* ===== 覆盖 SDK 字体大小 ===== */
|
||||||
padding: var(--spacing-xs) 0;
|
.digital-intelligent-interaction {
|
||||||
border-bottom: 1px solid var(--bg-tertiary);
|
font-size: 14px !important;
|
||||||
display: flex;
|
|
||||||
gap: var(--spacing-sm);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.log-entry:last-child {
|
.digital-intelligent-interaction .ai-chat-btn .ai-chat-btn-content {
|
||||||
border-bottom: none;
|
font-size: 16px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.log-time {
|
.digital-intelligent-interaction .message-item .rich-text-card .content {
|
||||||
color: var(--text-muted);
|
font-size: 16px !important;
|
||||||
white-space: nowrap;
|
line-height: 24px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.log-level {
|
.digital-intelligent-interaction .stop-chat-btn.normal .stop-chat-btn-content {
|
||||||
font-weight: 600;
|
font-size: 16px !important;
|
||||||
min-width: 50px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.log-level.debug {
|
.digital-intelligent-interaction .toast-container .toast-content .toast-text {
|
||||||
color: var(--text-muted);
|
font-size: 12px !important;
|
||||||
}
|
|
||||||
|
|
||||||
.log-level.info {
|
|
||||||
color: var(--info-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.log-level.warn {
|
|
||||||
color: var(--warning-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.log-level.error {
|
|
||||||
color: var(--danger-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.log-message {
|
|
||||||
color: var(--text-primary);
|
|
||||||
word-break: break-all;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ===== 底部 ===== */
|
|
||||||
.footer {
|
|
||||||
background: var(--bg-secondary);
|
|
||||||
border-top: 1px solid var(--border-color);
|
|
||||||
padding: var(--spacing-md) var(--spacing-xl);
|
|
||||||
text-align: center;
|
|
||||||
color: var(--text-muted);
|
|
||||||
font-size: 0.875rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ===== 响应式设计 ===== */
|
|
||||||
@media (max-width: 1200px) {
|
|
||||||
.main-content {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
max-width: 800px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.digital-human-box {
|
|
||||||
min-height: 300px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.log-panel {
|
|
||||||
max-height: 400px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
|
||||||
.header {
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--spacing-md);
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-content {
|
|
||||||
padding: var(--spacing-md);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-actions {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-row {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-input-row {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===== 滚动条样式 ===== */
|
/* ===== 滚动条样式 ===== */
|
||||||
@@ -647,7 +693,3 @@ section h2 {
|
|||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.log-entry {
|
|
||||||
animation: slideIn 0.2s ease-out;
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user