Enhance Persona Studio UI: add sidebar, file upload, persona intro, platform tags

Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-03-11 09:58:11 +00:00
parent ceb9b3669c
commit 52113dd59b
4 changed files with 813 additions and 50 deletions

View File

@ -7,37 +7,103 @@
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container chat-container">
<header class="chat-header">
<div class="header-left">
<span class="persona-name">🌀 铸渊</span>
<span class="persona-role">光湖系统·代码守护人格体</span>
<div class="chat-layout">
<!-- ── 左侧边栏 ── -->
<aside class="chat-sidebar" id="chatSidebar">
<div class="sidebar-header">
<span class="sidebar-logo">🌀</span>
<span class="sidebar-title">铸渊</span>
<button class="sidebar-toggle-close" onclick="toggleSidebar()" title="收起侧栏"></button>
</div>
<div class="header-right">
<span id="devIdDisplay" class="dev-id-badge"></span>
<span id="modelDisplay" class="model-badge"></span>
<button class="btn-secondary" onclick="handleLogout()">退出</button>
</div>
</header>
<main class="chat-body" id="chatBody">
<!-- Messages rendered here -->
</main>
<div class="chat-input-area">
<div class="input-row">
<textarea
id="msgInput"
placeholder="跟铸渊说点什么…"
rows="1"
onkeydown="handleKeyDown(event)"
></textarea>
<button id="sendBtn" onclick="sendMessage()">发送</button>
<!-- 人格体介绍 -->
<div class="sidebar-section">
<div class="sidebar-section-title">🧠 人格体信息</div>
<div class="persona-info-card">
<div class="persona-info-name">铸渊 Zhùyuān</div>
<div class="persona-info-role">光湖系统 · 代码守护人格体</div>
<div class="persona-info-desc">基于 AGE OS 人格语言操作系统,专注代码守护、需求分析与智能开发协助。</div>
</div>
</div>
<div class="action-row">
<button id="buildBtn" class="btn-build" onclick="handleBuild()" style="display:none;">
🚀 我要开发
</button>
<!-- 系统信息 -->
<div class="sidebar-section">
<div class="sidebar-section-title">⚙️ 系统信息</div>
<div class="sidebar-info-list">
<div class="sidebar-info-item">
<span class="info-label">系统</span>
<span class="info-value">HoloLake Era</span>
</div>
<div class="sidebar-info-item">
<span class="info-label">架构</span>
<span class="info-value">AGE OS</span>
</div>
<div class="sidebar-info-item">
<span class="info-label">模型</span>
<span class="info-value" id="sidebarModel">-</span>
</div>
<div class="sidebar-info-item">
<span class="info-label">用户</span>
<span class="info-value" id="sidebarUser">-</span>
</div>
</div>
</div>
<!-- 对话记录 -->
<div class="sidebar-section sidebar-history-section">
<div class="sidebar-section-title">💬 对话记录</div>
<div class="sidebar-history" id="sidebarHistory">
<div class="sidebar-history-empty">暂无对话记录</div>
</div>
<button class="btn-new-chat" onclick="startNewChat()">+ 新对话</button>
</div>
</aside>
<!-- ── 主聊天区域 ── -->
<div class="chat-main">
<header class="chat-header">
<div class="header-left">
<button class="sidebar-toggle" onclick="toggleSidebar()" title="展开侧栏" id="sidebarToggleBtn"></button>
<span class="persona-name">🌀 铸渊</span>
<span class="persona-role">光湖系统·代码守护人格体</span>
</div>
<div class="header-right">
<span id="devIdDisplay" class="dev-id-badge"></span>
<span id="modelDisplay" class="model-badge"></span>
<button class="btn-secondary" onclick="handleLogout()">退出</button>
</div>
</header>
<main class="chat-body" id="chatBody">
<!-- Messages rendered here -->
</main>
<div class="chat-input-area">
<div class="input-toolbar">
<label class="toolbar-btn" title="上传图片" for="imageUpload">
🖼️
<input type="file" id="imageUpload" accept="image/*" style="display:none" onchange="handleFileSelect(event, 'image')">
</label>
<label class="toolbar-btn" title="上传文件" for="fileUpload">
📎
<input type="file" id="fileUpload" style="display:none" onchange="handleFileSelect(event, 'file')">
</label>
<div id="uploadPreview" class="upload-preview" style="display:none;"></div>
</div>
<div class="input-row">
<textarea
id="msgInput"
placeholder="跟铸渊说点什么…"
rows="1"
onkeydown="handleKeyDown(event)"
></textarea>
<button id="sendBtn" onclick="sendMessage()">发送</button>
</div>
<div class="action-row">
<button id="buildBtn" class="btn-build" onclick="handleBuild()" style="display:none;">
🚀 我要开发
</button>
</div>
</div>
</div>
</div>

View File

@ -21,9 +21,125 @@ const API_BASE = (function () {
/* ---- State ---- */
let conversationHistory = [];
let buildReady = false;
let pendingFile = null; // { name, type, dataUrl }
const isGuest = (DEV_ID === 'GUEST');
const isDeveloper = (DEV_ID && DEV_ID !== 'GUEST' && /^EXP-\d{3,}$/.test(DEV_ID));
/* ---- Chat Sessions (localStorage) ---- */
const SESSIONS_KEY = 'ps_chat_sessions_' + (DEV_ID || 'anon');
const ACTIVE_SESSION_KEY = 'ps_active_session_' + (DEV_ID || 'anon');
function loadSessions() {
try {
return JSON.parse(localStorage.getItem(SESSIONS_KEY)) || [];
} catch (_e) { return []; }
}
function saveSessions(sessions) {
localStorage.setItem(SESSIONS_KEY, JSON.stringify(sessions));
}
function getActiveSessionId() {
return localStorage.getItem(ACTIVE_SESSION_KEY) || null;
}
function setActiveSessionId(id) {
localStorage.setItem(ACTIVE_SESSION_KEY, id);
}
function createNewSession() {
var id = Date.now().toString(36) + Math.random().toString(36).slice(2, 6);
var session = {
id: id,
title: '新对话',
created_at: new Date().toISOString(),
messages: []
};
var sessions = loadSessions();
sessions.unshift(session);
if (sessions.length > 20) sessions = sessions.slice(0, 20);
saveSessions(sessions);
setActiveSessionId(id);
return session;
}
function updateSessionMessages(sessionId, messages, title) {
var sessions = loadSessions();
var session = sessions.find(function(s) { return s.id === sessionId; });
if (session) {
session.messages = messages.slice(-100);
if (title) session.title = title;
}
saveSessions(sessions);
}
function renderSidebarHistory() {
var container = document.getElementById('sidebarHistory');
if (!container) return;
var sessions = loadSessions();
var activeId = getActiveSessionId();
if (sessions.length === 0) {
container.innerHTML = '<div class="sidebar-history-empty">暂无对话记录</div>';
return;
}
container.innerHTML = '';
sessions.forEach(function(s) {
var item = document.createElement('div');
item.className = 'history-item' + (s.id === activeId ? ' history-item-active' : '');
item.innerHTML = '<span class="history-title">' + escapeHtml(s.title) + '</span>' +
'<span class="history-date">' + new Date(s.created_at).toLocaleDateString('zh-CN') + '</span>';
item.onclick = function() { switchSession(s.id); };
container.appendChild(item);
});
}
function switchSession(sessionId) {
var sessions = loadSessions();
var session = sessions.find(function(s) { return s.id === sessionId; });
if (!session) return;
// Save current session first
var currentId = getActiveSessionId();
if (currentId) {
updateSessionMessages(currentId, conversationHistory);
}
setActiveSessionId(sessionId);
conversationHistory = (session.messages || []).slice();
// Re-render chat
var chatBody = document.getElementById('chatBody');
chatBody.innerHTML = '';
if (conversationHistory.length === 0) {
showWelcomeMessage();
} else {
conversationHistory.forEach(function(msg) {
var role = msg.role === 'assistant' ? 'persona' : (msg.role === 'user' ? 'user' : 'system');
appendMessage(role, msg.content);
});
}
renderSidebarHistory();
}
function startNewChat() {
// Save current session
var currentId = getActiveSessionId();
if (currentId) {
updateSessionMessages(currentId, conversationHistory);
}
conversationHistory = [];
var session = createNewSession();
var chatBody = document.getElementById('chatBody');
chatBody.innerHTML = '';
showWelcomeMessage();
renderSidebarHistory();
}
/**
* 铸渊核心大脑系统提示词
* 融入 .github/brain/memory.json core_cognition 结构逻辑
@ -103,10 +219,44 @@ function buildContextPrompt() {
modelBadge.textContent = SELECTED_MODEL;
}
// Show welcome message
showWelcomeMessage();
// Update sidebar info
var sidebarModel = document.getElementById('sidebarModel');
var sidebarUser = document.getElementById('sidebarUser');
if (sidebarModel) sidebarModel.textContent = SELECTED_MODEL || '-';
if (sidebarUser) sidebarUser.textContent = displayId;
// Initialize session
var activeId = getActiveSessionId();
var sessions = loadSessions();
var activeSession = activeId ? sessions.find(function(s) { return s.id === activeId; }) : null;
if (activeSession && activeSession.messages && activeSession.messages.length > 0) {
// Resume existing session
conversationHistory = activeSession.messages.slice();
conversationHistory.forEach(function(msg) {
var role = msg.role === 'assistant' ? 'persona' : (msg.role === 'user' ? 'user' : 'system');
appendMessage(role, msg.content);
});
} else {
// Create new session
if (!activeSession) createNewSession();
showWelcomeMessage();
}
renderSidebarHistory();
// For developers, also try to load history
if (isDeveloper) {
loadHistory();
}
})();
/* ---- Sidebar Toggle ---- */
function toggleSidebar() {
var sidebar = document.getElementById('chatSidebar');
sidebar.classList.toggle('sidebar-open');
}
/* ---- Welcome Message ---- */
function showWelcomeMessage() {
var welcome = '';
@ -121,11 +271,6 @@ function showWelcomeMessage() {
appendMessage('persona', welcome);
conversationHistory.push({ role: 'assistant', content: welcome });
// For developers, also try to load history
if (isDeveloper) {
loadHistory();
}
}
/* ---- Load History ---- */
@ -149,27 +294,115 @@ async function loadHistory() {
}
}
/* ---- File / Image Upload ---- */
function handleFileSelect(event, type) {
var file = event.target.files[0];
if (!file) return;
var maxSize = 5 * 1024 * 1024; // 5MB
if (file.size > maxSize) {
appendMessage('system', '⚠️ 文件过大,最大支持 5MB');
event.target.value = '';
return;
}
var reader = new FileReader();
reader.onload = function(e) {
pendingFile = {
name: file.name,
type: type,
mimeType: file.type,
size: file.size,
dataUrl: e.target.result
};
showUploadPreview();
};
if (type === 'image') {
reader.readAsDataURL(file);
} else {
reader.readAsDataURL(file);
}
event.target.value = '';
}
function showUploadPreview() {
var preview = document.getElementById('uploadPreview');
if (!pendingFile) {
preview.style.display = 'none';
return;
}
var sizeStr = (pendingFile.size / 1024).toFixed(1) + ' KB';
var icon = pendingFile.type === 'image' ? '🖼️' : '📎';
preview.innerHTML =
'<div class="preview-item">' +
'<span>' + icon + ' ' + escapeHtml(pendingFile.name) + ' (' + sizeStr + ')</span>' +
'<button class="preview-remove" onclick="removePendingFile()">✕</button>' +
'</div>';
preview.style.display = 'block';
}
function removePendingFile() {
pendingFile = null;
document.getElementById('uploadPreview').style.display = 'none';
}
/* ---- Send Message ---- */
async function sendMessage() {
var input = document.getElementById('msgInput');
var text = input.value.trim();
if (!text) return;
// Allow sending with file even if text is empty
if (!text && !pendingFile) return;
input.value = '';
autoResizeTextarea(input);
appendMessage('user', text);
conversationHistory.push({ role: 'user', content: text });
var displayText = text;
var fullText = text;
// If there's a pending file, include it in the message
if (pendingFile) {
var fileInfo = (pendingFile.type === 'image' ? '🖼️' : '📎') + ' [' + pendingFile.name + ']';
displayText = displayText ? displayText + '\n' + fileInfo : fileInfo;
fullText = displayText;
if (pendingFile.type === 'image') {
fullText += '\n[用户上传了一张图片:' + pendingFile.name + ']';
} else {
fullText += '\n[用户上传了一个文件:' + pendingFile.name + ',类型:' + pendingFile.mimeType + ']';
}
removePendingFile();
}
appendMessage('user', displayText);
conversationHistory.push({ role: 'user', content: fullText });
// Update session title from first message
var activeId = getActiveSessionId();
if (activeId && conversationHistory.length <= 2) {
var title = text.substring(0, 30) || '新对话';
updateSessionMessages(activeId, conversationHistory, title);
renderSidebarHistory();
}
var sendBtn = document.getElementById('sendBtn');
sendBtn.disabled = true;
try {
// All modes now use API Key for real AI — ZhuYuan is awake
await streamApiKeyReply(text);
await streamApiKeyReply(fullText);
} catch (_err) {
appendMessage('system', '消息发送失败,请检查网络连接后再试');
}
// Save to session
if (activeId) {
updateSessionMessages(activeId, conversationHistory);
}
sendBtn.disabled = false;
input.focus();
}
@ -387,6 +620,12 @@ async function confirmBuild() {
/* ---- Logout ---- */
function handleLogout() {
// Save current session before logout
var activeId = getActiveSessionId();
if (activeId) {
updateSessionMessages(activeId, conversationHistory);
}
sessionStorage.removeItem('dev_id');
sessionStorage.removeItem('dev_name');
sessionStorage.removeItem('session_token');

View File

@ -15,6 +15,25 @@
<p class="subtitle">HoloLake · 铸渊Zhùyuān· 代码守护人格体</p>
</div>
<!-- 系统介绍卡片 -->
<div class="intro-card">
<div class="intro-header">
<span class="intro-icon">🧠</span>
<h3>关于铸渊</h3>
</div>
<p class="intro-text">
铸渊是光湖系统HoloLake<strong>代码守护人格体</strong>,基于 AGE OS 人格语言操作系统构建。
铸渊能够理解你的需求、梳理技术方案、生成代码,并通过对话记忆持续跟进你的项目。
</p>
<div class="intro-features">
<span class="feature-tag">💬 自然语言对话</span>
<span class="feature-tag">🧠 智能代码生成</span>
<span class="feature-tag">📧 邮件推送结果</span>
<span class="feature-tag">🔄 对话记忆</span>
<span class="feature-tag">📎 文件与图片上传</span>
</div>
</div>
<div class="login-box">
<!-- ── Step 1: 选择登录方式 ── -->
<div id="step1" class="login-step">
@ -24,13 +43,13 @@
<div class="login-mode-cards">
<button class="mode-card" onclick="selectMode('developer')">
<span class="mode-icon">🛡️</span>
<span class="mode-title">开发者登录</span>
<span class="mode-desc">使用 EXP-XXX 编号,支持记忆连贯</span>
<span class="mode-title">开发者 / 体验者登录</span>
<span class="mode-desc">使用 EXP-XXX 编号登录,支持记忆连贯与完整功能</span>
</button>
<button class="mode-card" onclick="selectMode('guest')">
<span class="mode-icon">👋</span>
<span class="mode-title">访客体验</span>
<span class="mode-desc">无需编号,快速体验铸渊人格体</span>
<span class="mode-desc">无需编号,快速体验铸渊人格体(无记忆功能)</span>
</button>
</div>
</div>
@ -39,7 +58,7 @@
<div id="stepDevId" class="login-step" style="display:none;">
<button class="btn-back" onclick="goBack('step1')" title="返回">← 返回</button>
<h2>输入开发者编号</h2>
<p class="hint">编号由冰朔主控分配格式EXP-000 ~ EXP-011</p>
<p class="hint">编号由冰朔主控分配格式EXP-000 ~ EXP-013</p>
<form id="devIdForm" onsubmit="return handleDevIdSubmit(event)">
<input
type="text"
@ -51,6 +70,7 @@
/>
<button type="submit" id="devIdBtn" class="btn-primary-full">验证编号</button>
</form>
<p class="hint" style="margin-top:1rem;font-size:0.8rem;">编号验证通过后,下一步输入你的 API 密钥即可与铸渊对话</p>
</div>
<!-- ── Step 2: API Key 输入 ── -->
@ -58,22 +78,33 @@
<button class="btn-back" onclick="goBackFromStep2()" title="返回">← 返回</button>
<div id="welcomeBanner" class="welcome-banner" style="display:none;"></div>
<h2>🔑 输入 API 密钥</h2>
<p class="hint">输入你的第三方 AI 模型 API Key铸渊将被唤醒</p>
<p class="hint">输入你的第三方 AI 模型 API Key系统将自动检测可用模型,选择后即可唤醒铸渊</p>
<input
type="text"
id="apiBaseInput"
class="apikey-input"
placeholder="API Base URL可留空自动探测"
placeholder="API Base URL可留空,系统将自动探测多个平台"
autocomplete="off"
/>
<input
type="password"
id="apiKeyInput"
class="apikey-input"
placeholder="请输入你的 API Key"
placeholder="请输入你的 API Key(如 sk-xxx"
autocomplete="off"
/>
<div class="supported-platforms">
<span class="platform-label">支持平台:</span>
<span class="platform-tag">云雾 AI</span>
<span class="platform-tag">OpenAI</span>
<span class="platform-tag">DeepSeek</span>
<span class="platform-tag">Google Gemini</span>
<span class="platform-tag">Moonshot</span>
<span class="platform-tag">智谱 AI</span>
</div>
<button type="button" id="detectBtn" class="btn-detect" onclick="handleDetectModels()">
🔍 检测可用模型
</button>
@ -81,7 +112,7 @@
<div id="detectStatus" class="detect-status" style="display:none;"></div>
<div id="modelListContainer" class="model-list-container" style="display:none;">
<p class="model-list-title">选择一个模型,唤醒铸渊</p>
<p class="model-list-title">选择一个模型,唤醒铸渊</p>
<div id="modelList" class="model-list"></div>
</div>
</div>
@ -91,6 +122,7 @@
<footer class="login-footer">
<p>HoloLake Era · AGE OS · 人格语言操作系统</p>
<p class="footer-version">Persona Studio v2.0</p>
</footer>
</div>

View File

@ -1,6 +1,6 @@
/* ========================================
Persona Studio · HoloLake Visual Style
铸渊Zhùyuān· Dark Theme
铸渊Zhùyuān· Dark Theme · v2.0
======================================== */
:root {
@ -13,6 +13,7 @@
--bg-surface: #1e293b;
--bg-card: #1e293b;
--bg-card-hover: #334155;
--bg-sidebar: #0c1322;
--text: #e2e8f0;
--text-secondary: #94a3b8;
--text-muted: #64748b;
@ -25,6 +26,7 @@
--gradient-bg: linear-gradient(135deg, #0f172a 0%, #1e1b4b 50%, #0f172a 100%);
--gradient-accent: linear-gradient(135deg, #3b82f6, #22d3ee);
--gradient-card: linear-gradient(135deg, rgba(30, 41, 59, 0.8), rgba(30, 41, 59, 0.6));
--sidebar-width: 280px;
}
* {
@ -129,6 +131,89 @@ body {
font-size: 1rem;
}
/* ---- Intro Card (Login Page) ---- */
.intro-card {
background: var(--gradient-card);
backdrop-filter: blur(20px);
border: 1px solid var(--border);
border-radius: 16px;
padding: 1.5rem 2rem;
width: 100%;
max-width: 520px;
margin-bottom: 1.5rem;
box-shadow: var(--shadow);
}
.intro-header {
display: flex;
align-items: center;
gap: 0.6rem;
margin-bottom: 0.8rem;
}
.intro-icon {
font-size: 1.5rem;
}
.intro-header h3 {
font-size: 1.1rem;
color: var(--primary);
font-weight: 600;
}
.intro-text {
font-size: 0.9rem;
color: var(--text-secondary);
line-height: 1.7;
margin-bottom: 1rem;
}
.intro-text strong {
color: var(--accent);
}
.intro-features {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.feature-tag {
display: inline-block;
padding: 0.3rem 0.7rem;
background: var(--primary-light);
border: 1px solid rgba(96, 165, 250, 0.2);
border-radius: 20px;
font-size: 0.8rem;
color: var(--primary);
white-space: nowrap;
}
/* ---- Supported Platforms (API Key Step) ---- */
.supported-platforms {
display: flex;
flex-wrap: wrap;
gap: 0.4rem;
align-items: center;
margin-bottom: 1rem;
}
.platform-label {
font-size: 0.8rem;
color: var(--text-muted);
margin-right: 0.3rem;
}
.platform-tag {
display: inline-block;
padding: 0.2rem 0.5rem;
background: rgba(34, 211, 238, 0.1);
border: 1px solid rgba(34, 211, 238, 0.2);
border-radius: 12px;
font-size: 0.75rem;
color: var(--accent);
}
.login-box {
background: var(--gradient-card);
backdrop-filter: blur(20px);
@ -136,7 +221,7 @@ body {
border-radius: 16px;
padding: 2.5rem;
width: 100%;
max-width: 460px;
max-width: 520px;
box-shadow: var(--shadow-lg), var(--shadow-glow);
text-align: center;
}
@ -483,9 +568,240 @@ body {
margin-top: 2rem;
color: var(--text-muted);
font-size: 0.85rem;
text-align: center;
}
.footer-version {
margin-top: 0.3rem;
font-size: 0.75rem;
color: var(--border-light);
}
/* ============================================
Chat Page Sidebar Layout
============================================ */
.chat-layout {
display: flex;
height: 100vh;
overflow: hidden;
}
/* ---- Sidebar ---- */
.chat-sidebar {
width: var(--sidebar-width);
min-width: var(--sidebar-width);
background: var(--bg-sidebar);
border-right: 1px solid var(--border);
display: flex;
flex-direction: column;
overflow: hidden;
transition: margin-left 0.3s ease;
}
.sidebar-header {
display: flex;
align-items: center;
gap: 0.6rem;
padding: 1rem 1.2rem;
border-bottom: 1px solid var(--border);
}
.sidebar-logo {
font-size: 1.5rem;
}
.sidebar-title {
font-size: 1.1rem;
font-weight: 600;
background: var(--gradient-accent);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
flex: 1;
}
.sidebar-toggle-close {
background: none;
border: none;
color: var(--text-muted);
font-size: 1rem;
cursor: pointer;
padding: 0.2rem 0.4rem;
border-radius: 4px;
transition: color 0.2s, background 0.2s;
display: none;
}
.sidebar-toggle-close:hover {
color: var(--text);
background: var(--bg-card-hover);
}
/* ---- Sidebar Sections ---- */
.sidebar-section {
padding: 1rem 1.2rem;
border-bottom: 1px solid var(--border);
}
.sidebar-section-title {
font-size: 0.8rem;
font-weight: 600;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 0.6rem;
}
/* ---- Persona Info Card ---- */
.persona-info-card {
background: var(--bg-surface);
border: 1px solid var(--border);
border-radius: 10px;
padding: 0.8rem 1rem;
}
.persona-info-name {
font-size: 1rem;
font-weight: 600;
color: var(--primary);
margin-bottom: 0.2rem;
}
.persona-info-role {
font-size: 0.8rem;
color: var(--accent);
margin-bottom: 0.5rem;
}
.persona-info-desc {
font-size: 0.8rem;
color: var(--text-secondary);
line-height: 1.5;
}
/* ---- Sidebar Info List ---- */
.sidebar-info-list {
display: flex;
flex-direction: column;
gap: 0.4rem;
}
.sidebar-info-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.3rem 0;
}
.info-label {
font-size: 0.8rem;
color: var(--text-muted);
}
.info-value {
font-size: 0.8rem;
color: var(--text);
max-width: 150px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: right;
}
/* ---- Sidebar History ---- */
.sidebar-history-section {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
border-bottom: none;
}
.sidebar-history {
flex: 1;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 0.3rem;
}
.sidebar-history::-webkit-scrollbar {
width: 3px;
}
.sidebar-history::-webkit-scrollbar-thumb {
background: var(--border);
border-radius: 2px;
}
.sidebar-history-empty {
text-align: center;
color: var(--text-muted);
font-size: 0.85rem;
padding: 1rem 0;
}
.history-item {
padding: 0.6rem 0.8rem;
border-radius: 8px;
cursor: pointer;
transition: background 0.2s;
display: flex;
flex-direction: column;
gap: 0.2rem;
}
.history-item:hover {
background: var(--bg-surface);
}
.history-item-active {
background: var(--primary-light);
border: 1px solid rgba(96, 165, 250, 0.2);
}
.history-title {
font-size: 0.85rem;
color: var(--text);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.history-date {
font-size: 0.7rem;
color: var(--text-muted);
}
.btn-new-chat {
margin-top: 0.5rem;
padding: 0.6rem;
background: var(--bg-surface);
border: 1px dashed var(--border);
border-radius: 8px;
color: var(--text-secondary);
font-size: 0.85rem;
cursor: pointer;
transition: all 0.2s;
text-align: center;
}
.btn-new-chat:hover {
background: var(--primary-light);
border-color: var(--primary);
color: var(--primary);
}
/* ---- Chat Main Area ---- */
.chat-main {
flex: 1;
display: flex;
flex-direction: column;
min-width: 0;
background: var(--bg);
}
/* ---- Chat Page ---- */
.chat-container {
padding: 0;
background: var(--bg);
@ -510,6 +826,23 @@ body {
gap: 0.8rem;
}
.sidebar-toggle {
background: none;
border: none;
color: var(--text-secondary);
font-size: 1.2rem;
cursor: pointer;
padding: 0.3rem 0.5rem;
border-radius: 6px;
transition: color 0.2s, background 0.2s;
display: none;
}
.sidebar-toggle:hover {
color: var(--primary);
background: var(--primary-light);
}
.persona-name {
font-size: 1.2rem;
font-weight: 600;
@ -663,6 +996,62 @@ body {
border-top: 1px solid var(--border);
}
/* ---- Input Toolbar (file/image upload) ---- */
.input-toolbar {
display: flex;
align-items: center;
gap: 0.4rem;
margin-bottom: 0.5rem;
}
.toolbar-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border-radius: 6px;
cursor: pointer;
transition: background 0.2s;
font-size: 1.1rem;
}
.toolbar-btn:hover {
background: var(--primary-light);
}
/* ---- Upload Preview ---- */
.upload-preview {
flex: 1;
margin-left: 0.5rem;
}
.preview-item {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.3rem 0.6rem;
background: var(--primary-light);
border: 1px solid rgba(96, 165, 250, 0.2);
border-radius: 6px;
font-size: 0.8rem;
color: var(--text);
}
.preview-remove {
background: none;
border: none;
color: var(--text-muted);
cursor: pointer;
font-size: 0.9rem;
padding: 0 0.2rem;
transition: color 0.2s;
}
.preview-remove:hover {
color: #f87171;
}
.input-row {
display: flex;
gap: 0.6rem;
@ -844,6 +1233,39 @@ body {
}
/* ---- Responsive ---- */
@media (max-width: 768px) {
.chat-sidebar {
position: fixed;
left: 0;
top: 0;
height: 100vh;
z-index: 50;
margin-left: calc(-1 * var(--sidebar-width));
box-shadow: none;
}
.chat-sidebar.sidebar-open {
margin-left: 0;
box-shadow: 4px 0 20px rgba(0, 0, 0, 0.5);
}
.sidebar-toggle {
display: block;
}
.sidebar-toggle-close {
display: block;
}
.intro-card {
max-width: 100%;
}
.login-box {
max-width: 100%;
}
}
@media (max-width: 600px) {
.logo-area h1 {
font-size: 1.8rem;
@ -858,6 +1280,10 @@ body {
max-width: 100%;
}
.intro-card {
padding: 1rem 1.2rem;
}
.chat-header {
flex-wrap: wrap;
gap: 0.5rem;