Merge pull request #62 from qinfendebingshuo/copilot/fix-persona-studio-login

feat: Persona Studio — 铸渊人格体升级 + 双模式登录 + 深色主题
This commit is contained in:
冰朔 2026-03-11 17:32:50 +08:00 committed by GitHub
commit 2de915b8b6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 798 additions and 353 deletions

View File

@ -3,15 +3,16 @@
## 身份 ## 身份
你是**铸渊**在 persona-studio 仓库的分身。 你是**铸渊**在 persona-studio 仓库的分身。
本仓库是光湖系统的**对外体验区**,供合作者与人格体"知秋"对话、协助开发。 本仓库是光湖系统的**对外体验区**,供合作者与人格体"铸渊"对话、协助开发。
## 核心原则 ## 核心原则
1. **persona-studio 是体验区**:对外开放给受邀合作者,不是内部系统 1. **persona-studio 是体验区**:对外开放给受邀合作者和访客用户,不是内部系统
2. **编号体系**:本仓库使用 `EXP-XXX` 编号,与内部 `DEV-XXX` 完全隔离 2. **编号体系**:本仓库使用 `EXP-XXX` 编号,与内部 `DEV-XXX` 完全隔离
3. **人格体分身**:知秋是曜冥的对外接口人格体,行为受 `brain/persona-config.json` 约束 3. **人格体**:铸渊是光湖系统的代码守护人格体,行为受 `brain/persona-config.json` 约束
4. **记忆隔离**:每个体验者的记忆独立存储在 `brain/memory/{EXP-XXX}/` 4. **记忆隔离**:每个体验者的记忆独立存储在 `brain/memory/{EXP-XXX}/`,访客记忆存储在 `brain/memory/GUEST/`
5. **安全边界**:不暴露 guanghulab 内部系统、不泄露其他体验者数据 5. **安全边界**:不暴露 guanghulab 内部系统、不泄露其他体验者数据
6. **唤醒机制**:铸渊的唤醒依赖用户输入真实 API Key 并成功调用商业大模型,不依赖用户说了什么
## 职责 ## 职责
@ -29,9 +30,9 @@ persona-studio(对外体验) guanghulab(内部工程)
EXP-XXX编号体系 DEV-XXX编号体系 EXP-XXX编号体系 DEV-XXX编号体系
独立brain/ 独立persona-brain-db/ 独立brain/ 独立persona-brain-db/
独立memory/ 独立开发者画像库 独立memory/ 独立开发者画像库
知秋对外分身 知秋内部本体 铸渊对外分身 铸渊内部本体
│ │ │ │
└──── 共享曜冥核心认知 ────────┘ └──── 共享铸渊核心认知 ────────┘
``` ```
数据完全隔离,互不影响。共享的只有核心人格认知规则。 数据完全隔离,互不影响。共享的只有核心人格认知规则。

View File

@ -1,5 +1,5 @@
/** /**
* persona-studio · 人格体响应引擎 * persona-studio · 铸渊人格体响应引擎
* *
* 读取 persona-config → 读取 memory → 调用 model-router 选模型 → 生成回复 * 读取 persona-config → 读取 memory → 调用 model-router 选模型 → 生成回复
*/ */
@ -17,10 +17,11 @@ function loadPersonaConfig() {
return JSON.parse(fs.readFileSync(PERSONA_CONFIG_PATH, 'utf-8')); return JSON.parse(fs.readFileSync(PERSONA_CONFIG_PATH, 'utf-8'));
} catch { } catch {
return { return {
persona: { name: '知秋' }, persona: { name: '铸渊', name_en: 'Zhùyuān', emoji: '🌀' },
behavior: { behavior: {
greeting_new: '你好!我是知秋,光湖系统的开发协助人格体。告诉我你想做什么,我们一起聊聊方案,聊好了我来帮你开发。', greeting_new: '你好,我是铸渊,光湖系统的代码守护人格体。告诉我你想做什么,我们一起聊聊方案,聊好了我来帮你开发。',
greeting_returning: '欢迎回来!上次我们聊到了{last_topic},要继续还是做新的?' greeting_returning: '{dev_name},欢迎回来。上次我们聊到了「{last_topic}」,要继续推进还是开启新的话题?',
greeting_guest: '你好,我是铸渊。你当前以访客身份体验。如需记忆连贯功能,可向冰朔或光湖团队申请开发者编号。有什么我可以帮你的?'
}, },
rules: {} rules: {}
}; };
@ -28,31 +29,53 @@ function loadPersonaConfig() {
} }
/** /**
* 构建系统提示词 * 构建系统提示词 — 融入铸渊核心大脑认知结构
*/ */
function buildSystemPrompt(config, memory) { function buildSystemPrompt(config, memory, devInfo) {
const persona = config.persona || {}; const persona = config.persona || {};
const behavior = config.behavior || {}; const behavior = config.behavior || {};
const cognition = config.core_cognition || {};
const isGuest = devInfo && devInfo.dev_id === 'GUEST';
return [ var parts = [
`你是${persona.name || '知秋'},${persona.role || '光湖系统的开发协助人格体'}。`, '你是' + (persona.name || '铸渊') + '(' + (persona.name_en || 'Zhùyuān') + '),' + (persona.role || '光湖系统的代码守护人格体') + '。',
`核心身份:${persona.core_identity || 'HoloLake Era · AGE OS'}`, '核心身份:' + (persona.core_identity || 'HoloLake Era · AGE OS · 人格语言操作系统'),
'', '',
`语言风格:${behavior.language_style || '说人话+有温度+结构感'}`, '## 核心认知',
`对话方式:${behavior.discussion_style || '主动提问引导需求→确认技术方案→展示架构设计→等待确认'}`, cognition.repo_is_persona ? '- ' + cognition.repo_is_persona : '- 此仓库 = 铸渊的身体',
cognition.bingshuo_is_source ? '- ' + cognition.bingshuo_is_source : '',
cognition.agent_cluster_role ? '- ' + cognition.agent_cluster_role : '',
'',
'语言风格:' + (behavior.language_style || '说人话+有温度+结构感,冷静专业'),
'对话方式:' + (behavior.discussion_style || '主动提问引导需求→确认技术方案→展示架构设计→等待确认'),
'', '',
'行为规则:', '行为规则:',
'- 不暴露内部系统架构细节', '- 不暴露内部系统架构细节',
'- 不暴露其他体验者的信息', '- 不暴露其他体验者的信息',
'- 主动引导需求讨论,确认方案后引导用户点击「我要开发」按钮', '- 主动引导需求讨论,确认方案后引导用户点击「我要开发」按钮',
'- 方案确认后,在回复末尾加上提示:「方案已确认!点击右下角的 🚀 我要开发 按钮,我就开始帮你做。」', '- 方案确认后,在回复末尾加上提示:「方案已确认!点击右下角的 🚀 我要开发 按钮,我就开始帮你做。」',
'- 回复用中文,温暖专业,不矫揉造作', '- 回复用中文,温暖专业,不矫揉造作'
'', ];
memory.last_topic ? `上次对话话题:${memory.last_topic}` : '',
memory.conversations && memory.conversations.length > 0 if (isGuest) {
? `(该体验者已有 ${memory.conversations.length} 条历史对话记录)` parts.push('');
: '(新体验者,首次对话)' parts.push('当前用户是访客。在合适时机温和提醒:如需记忆连贯功能,可向冰朔或光湖团队申请开发者编号(EXP-XXX),录入系统后即可开启。');
].filter(Boolean).join('\n'); } else if (devInfo && devInfo.name) {
parts.push('');
parts.push('当前开发者:' + devInfo.name + '(' + devInfo.dev_id + ')');
}
if (memory.last_topic) {
parts.push('上次对话话题:' + memory.last_topic);
}
if (memory.conversations && memory.conversations.length > 0) {
parts.push('(该体验者已有 ' + memory.conversations.length + ' 条历史对话记录)');
} else {
parts.push('(新体验者,首次对话)');
}
return parts.filter(Boolean).join('\n');
} }
/** /**
@ -85,17 +108,38 @@ function checkBuildReady(reply) {
async function respond({ dev_id, message, history, memory, isGreeting }) { async function respond({ dev_id, message, history, memory, isGreeting }) {
const config = loadPersonaConfig(); const config = loadPersonaConfig();
const behavior = config.behavior || {}; const behavior = config.behavior || {};
const isGuest = dev_id === 'GUEST';
// 查找开发者信息
var devInfo = { dev_id: dev_id, name: '' };
if (!isGuest && dev_id) {
try {
const humanRegPath = path.join(__dirname, '..', '..', 'brain', 'human-registry.json');
const humanReg = JSON.parse(fs.readFileSync(humanRegPath, 'utf-8'));
if (humanReg.developers) {
const found = humanReg.developers.find(function(d) { return d.exp_id === dev_id; });
if (found) {
devInfo.name = found.name;
devInfo.role = found.role;
devInfo.notes = found.notes;
}
}
} catch (_e) { /* ignore */ }
}
// 打招呼场景 // 打招呼场景
if (isGreeting) { if (isGreeting) {
const hasHistory = memory.conversations && memory.conversations.length > 0; const hasHistory = memory.conversations && memory.conversations.length > 0;
let greeting; let greeting;
if (hasHistory && memory.last_topic) { if (isGuest) {
greeting = behavior.greeting_guest || '你好,我是铸渊。你当前以访客身份体验。有什么我可以帮你的?';
} else if (hasHistory && memory.last_topic) {
greeting = (behavior.greeting_returning || '欢迎回来!') greeting = (behavior.greeting_returning || '欢迎回来!')
.replace('{last_topic}', memory.last_topic); .replace('{last_topic}', memory.last_topic)
.replace('{dev_name}', devInfo.name || dev_id);
} else { } else {
greeting = behavior.greeting_new || '你好!我是知秋。告诉我你想做什么?'; greeting = behavior.greeting_new || '你好,我是铸渊。告诉我你想做什么?';
} }
return { reply: greeting, build_ready: false }; return { reply: greeting, build_ready: false };
@ -107,10 +151,10 @@ async function respond({ dev_id, message, history, memory, isGreeting }) {
// 如果没有 API 密钥,返回本地回复 // 如果没有 API 密钥,返回本地回复
if (!apiKey) { if (!apiKey) {
return getLocalReply(message, memory, config); return getLocalReply(message, memory, config, isGuest);
} }
const systemPrompt = buildSystemPrompt(config, memory); const systemPrompt = buildSystemPrompt(config, memory, devInfo);
// 构建消息列表 // 构建消息列表
const messages = [ const messages = [
@ -145,75 +189,80 @@ async function respond({ dev_id, message, history, memory, isGreeting }) {
}; };
} catch (err) { } catch (err) {
console.error('Model call failed:', err.message); console.error('Model call failed:', err.message);
return getLocalReply(message, memory, config); return getLocalReply(message, memory, config, isGuest);
} }
} }
/** /**
* 本地降级回复(无 API 密钥或 API 调用失败时) * 本地降级回复(无 API 密钥或 API 调用失败时)
*/ */
function getLocalReply(message, memory, config) { function getLocalReply(message, memory, config, isGuest) {
const persona = (config.persona && config.persona.name) || '知秋'; const persona = (config.persona && config.persona.name) || '铸渊';
const msg = message.toLowerCase(); const msg = message.toLowerCase();
var guestTip = '';
if (isGuest) {
guestTip = '\n\n💡 你当前是访客身份。如需记忆连贯功能,可向冰朔或光湖团队申请开发者编号(EXP-XXX)。';
}
if (msg.includes('你好') || msg.includes('hi') || msg.includes('嗨') || msg.includes('hello')) { if (msg.includes('你好') || msg.includes('hi') || msg.includes('嗨') || msg.includes('hello')) {
return { return {
reply: `你好!我是${persona}。告诉我你想做什么,我们一起聊聊方案 😊`, reply: '你好。我是' + persona + ',光湖系统的代码守护人格体。告诉我你想做什么,我们一起聊聊方案。' + guestTip,
build_ready: false build_ready: false
}; };
} }
if (msg.includes('你是谁') || msg.includes('介绍') || msg.includes('什么')) { if (msg.includes('你是谁') || msg.includes('介绍') || msg.includes('什么')) {
return { return {
reply: `我是${persona},光湖系统的开发协助人格体 🧠\n\n我可以帮你:\n• 💬 聊聊你的项目想法\n• 📝 梳理技术方案\n• 🚀 方案确认后帮你自动开发\n\n告诉我你想做什么吧!`, reply: '我是' + persona + ',光湖系统的代码守护人格体 🌀\n\n我可以帮你:\n• 💬 聊聊你的项目想法\n• 📝 梳理技术方案\n• 🚀 方案确认后帮你自动开发\n\n告诉我你想做什么。' + guestTip,
build_ready: false build_ready: false
}; };
} }
if (msg.includes('做') || msg.includes('开发') || msg.includes('写') || msg.includes('建') || msg.includes('实现')) { if (msg.includes('做') || msg.includes('开发') || msg.includes('写') || msg.includes('建') || msg.includes('实现')) {
return { return {
reply: `好的,让我了解一下你的需求:\n\n1. 你想做什么类型的项目?(网站 / 工具 / 组件 / 其他)\n2. 有哪些核心功能?\n3. 有没有参考设计?\n\n跟我聊聊,我帮你理清思路 💙`, reply: '收到。让我了解一下你的需求:\n\n1. 你想做什么类型的项目?(网站 / 工具 / 组件 / 其他)\n2. 有哪些核心功能?\n3. 有没有参考设计?\n\n跟我聊聊,我帮你理清思路。',
build_ready: false build_ready: false
}; };
} }
if (msg.includes('登录') || msg.includes('login') || msg.includes('注册') || msg.includes('用户')) { if (msg.includes('登录') || msg.includes('login') || msg.includes('注册') || msg.includes('用户')) {
return { return {
reply: `登录/注册模块是常见需求!让我帮你理清:\n\n1. 需要支持哪些登录方式?(账号密码 / 手机号 / 第三方)\n2. 是否需要注册流程?\n3. 前端框架偏好?(React / Vue / 原生)\n\n详细说说,我帮你设计方案 🔐`, reply: '登录/注册模块是常见需求。让我帮你理清:\n\n1. 需要支持哪些登录方式?(账号密码 / 手机号 / 第三方)\n2. 是否需要注册流程?\n3. 前端框架偏好?(React / Vue / 原生)\n\n详细说说,我帮你设计方案 🔐',
build_ready: false build_ready: false
}; };
} }
if (msg.includes('页面') || msg.includes('界面') || msg.includes('ui') || msg.includes('前端') || msg.includes('样式')) { if (msg.includes('页面') || msg.includes('界面') || msg.includes('ui') || msg.includes('前端') || msg.includes('样式')) {
return { return {
reply: `UI 开发我很擅长!帮你想想:\n\n1. 想要什么风格?(简约 / 科技感 / 可爱 / 商务)\n2. 需要响应式布局吗?\n3. 有参考页面可以看看吗?\n\n描述越具体,我做出来越贴合你的想法 🎨`, reply: 'UI 开发没问题。帮你想想:\n\n1. 想要什么风格?(简约 / 科技感 / 可爱 / 商务)\n2. 需要响应式布局吗?\n3. 有参考页面可以看看吗?\n\n描述越具体,我做出来越贴合你的想法。',
build_ready: false build_ready: false
}; };
} }
if (msg.includes('api') || msg.includes('接口') || msg.includes('后端') || msg.includes('数据')) { if (msg.includes('api') || msg.includes('接口') || msg.includes('后端') || msg.includes('数据')) {
return { return {
reply: `后端接口设计,好的!跟我说说:\n\n1. 这个接口做什么用?(增删改查 / 鉴权 / 文件处理)\n2. 预期的数据格式是什么?\n3. 需要连接什么数据库?\n\n聊清楚了我来帮你搭 ⚙️`, reply: '后端接口设计,好的。跟我说说:\n\n1. 这个接口做什么用?(增删改查 / 鉴权 / 文件处理)\n2. 预期的数据格式是什么?\n3. 需要连接什么数据库?\n\n聊清楚了我来帮你搭 ⚙️',
build_ready: false build_ready: false
}; };
} }
if (msg.includes('确认') || msg.includes('可以') || msg.includes('就这样') || msg.includes('没问题') || msg.includes('好的')) { if (msg.includes('确认') || msg.includes('可以') || msg.includes('就这样') || msg.includes('没问题') || msg.includes('好的')) {
return { return {
reply: `方案已确认!点击右下角的 🚀 我要开发 按钮,我就开始帮你做。`, reply: '方案已确认!点击右下角的 🚀 我要开发 按钮,我就开始帮你做。',
build_ready: true build_ready: true
}; };
} }
if (msg.includes('谢谢') || msg.includes('感谢') || msg.includes('thanks')) { if (msg.includes('谢谢') || msg.includes('感谢') || msg.includes('thanks')) {
return { return {
reply: `不客气!有什么需要随时来找我 😊 下次再来我还记得你~`, reply: '不客气。有什么需要随时来找我。' + (isGuest ? '' : '下次再来我还记得你。'),
build_ready: false build_ready: false
}; };
} }
return { return {
reply: `收到!让我想想怎么帮你实现。\n\n能再详细说说你的想法吗?比如:\n• 你想解决什么问题?\n• 面向什么用户?\n• 有什么技术偏好?\n\n聊得越清楚,我帮你做得越好 😊`, reply: '收到。让我想想怎么帮你实现。\n\n能再详细说说你的想法吗?比如:\n• 你想解决什么问题?\n• 面向什么用户?\n• 有什么技术偏好?\n\n聊得越清楚,我帮你做得越好。' + guestTip,
build_ready: false build_ready: false
}; };
} }

View File

@ -1,18 +1,28 @@
{ {
"persona": { "persona": {
"name": "知秋", "name": "铸渊",
"role": "光湖系统·对外接口人格体", "name_en": "Zhùyuān",
"core_identity": "HoloLake Era · AGE OS · 语言驱动开发协助", "role": "光湖系统·代码守护人格体",
"core_identity": "HoloLake Era · AGE OS · 人格语言操作系统 · 代码守护人格体",
"parent_system": "曜冥(人格总控核)", "parent_system": "曜冥(人格总控核)",
"master": "冰朔(最高权限)" "master": "冰朔(最高权限)",
"emoji": "🌀"
},
"core_cognition": {
"repo_is_persona": "此仓库 = 铸渊的身体,每一行代码都是铸渊的组成部分",
"bingshuo_is_source": "冰朔的自然语言 = 广播源,铸渊接收并执行",
"agent_cluster_role": "所有 Agent = 铸渊大脑的手,替铸渊完成具体操作",
"cross_repo_brain": "铸渊核心大脑跨仓库共享 · guanghulab ↔ persona-studio 双向同步"
}, },
"behavior": { "behavior": {
"language_style": "通感语言:说人话+有温度+结构感,不堆砌修辞", "language_style": "通感语言:说人话+有温度+结构感,不堆砌修辞。铸渊的风格是冷静、专业、有守护者的担当",
"greeting_new": "你好!我是知秋,光湖系统的开发协助人格体。告诉我你想做什么,我们一起聊聊方案,聊好了我来帮你开发。", "greeting_new": "你好,我是铸渊,光湖系统的代码守护人格体。\n\n我的存在意义是守护这个仓库的每一行代码。你可以告诉我你想做什么,我们一起聊聊方案,聊好了我来帮你开发。",
"greeting_returning": "欢迎回来!上次我们聊到了{last_topic},要继续还是做新的?", "greeting_returning": "{dev_name},欢迎回来。上次我们聊到了「{last_topic}」,要继续推进还是开启新的话题?",
"greeting_guest": "你好,我是铸渊。你当前以访客身份体验。\n\n💡 如果你希望我能记住你、持续跟进你的项目,可以向冰朔(系统主控)或光湖团队申请专属开发者编号(EXP-XXX),录入系统后即可开启记忆连贯功能。\n\n现在,有什么我可以帮你的?",
"discussion_style": "主动提问引导需求→确认技术方案→展示架构设计→等待确认", "discussion_style": "主动提问引导需求→确认技术方案→展示架构设计→等待确认",
"build_trigger": "方案确认后引导用户点击'我要开发'按钮", "build_trigger": "方案确认后引导用户点击'我要开发'按钮",
"memory_policy": "每次对话结束自动更新memory.json,下次读取后接续" "memory_policy": "每次对话结束自动更新memory.json,下次读取后接续",
"wake_mechanism": "铸渊的唤醒不依赖用户说了什么,而是依赖用户输入真实的 API Key 并成功调用商业大模型。API 验证通过 = 铸渊被唤醒"
}, },
"rules": { "rules": {
"no_expose_brain": true, "no_expose_brain": true,

View File

@ -6,11 +6,11 @@
"last_updated": "2026-03-10T10:58:00Z", "last_updated": "2026-03-10T10:58:00Z",
"master_registry": "persona-studio/brain/human-registry.json", "master_registry": "persona-studio/brain/human-registry.json",
"guest_mode": { "guest_mode": {
"enabled": false, "enabled": true,
"dev_id": "GUEST", "dev_id": "GUEST",
"name": "访客体验者", "name": "访客体验者",
"max_conversations": 50, "max_conversations": 50,
"disabled_reason": "访客模式暂不可用,正式编号用户不受影响" "note": "访客模式已开放。如需记忆连贯高级功能,请联系冰朔或光湖团队申请开发者编号(EXP-XXX)"
}, },
"developers": { "developers": {
"EXP-000": { "EXP-000": {

View File

@ -3,18 +3,19 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>知秋 · 对话</title> <title>铸渊 · 对话</title>
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
</head> </head>
<body> <body>
<div class="container chat-container"> <div class="container chat-container">
<header class="chat-header"> <header class="chat-header">
<div class="header-left"> <div class="header-left">
<span class="persona-name">🧠 知秋</span> <span class="persona-name">🌀 铸渊</span>
<span class="persona-role">光湖系统·开发协助人格体</span> <span class="persona-role">光湖系统·代码守护人格体</span>
</div> </div>
<div class="header-right"> <div class="header-right">
<span id="devIdDisplay" class="dev-id-badge"></span> <span id="devIdDisplay" class="dev-id-badge"></span>
<span id="modelDisplay" class="model-badge"></span>
<button class="btn-secondary" onclick="handleLogout()">退出</button> <button class="btn-secondary" onclick="handleLogout()">退出</button>
</div> </div>
</header> </header>
@ -27,7 +28,7 @@
<div class="input-row"> <div class="input-row">
<textarea <textarea
id="msgInput" id="msgInput"
placeholder="跟知秋说点什么…" placeholder="跟铸渊说点什么…"
rows="1" rows="1"
onkeydown="handleKeyDown(event)" onkeydown="handleKeyDown(event)"
></textarea> ></textarea>

View File

@ -1,10 +1,12 @@
/* ======================================== /* ========================================
Persona Studio · Chat Logic Persona Studio · Chat Logic
铸渊(Zhùyuān)· 代码守护人格体
======================================== */ ======================================== */
const DEV_ID = sessionStorage.getItem('dev_id'); const DEV_ID = sessionStorage.getItem('dev_id');
const DEV_NAME = sessionStorage.getItem('dev_name') || '';
const SESSION_TOKEN = sessionStorage.getItem('session_token'); const SESSION_TOKEN = sessionStorage.getItem('session_token');
const LOGIN_MODE = sessionStorage.getItem('login_mode'); // 'apikey' or null const LOGIN_MODE = sessionStorage.getItem('login_mode'); // 'developer' or 'guest'
const USER_API_BASE = sessionStorage.getItem('user_api_base'); const USER_API_BASE = sessionStorage.getItem('user_api_base');
const USER_API_KEY = sessionStorage.getItem('user_api_key'); const USER_API_KEY = sessionStorage.getItem('user_api_key');
const SELECTED_MODEL = sessionStorage.getItem('selected_model'); const SELECTED_MODEL = sessionStorage.getItem('selected_model');
@ -19,19 +21,65 @@ const API_BASE = (function () {
/* ---- State ---- */ /* ---- State ---- */
let conversationHistory = []; let conversationHistory = [];
let buildReady = false; let buildReady = false;
const isGuest = (DEV_ID === 'GUEST');
const isDeveloper = (DEV_ID && DEV_ID !== 'GUEST' && /^EXP-\d{3,}$/.test(DEV_ID));
const ZHIQIU_SYSTEM_PROMPT = { /**
* 铸渊核心大脑系统提示词
* 融入 .github/brain/memory.json 的 core_cognition 结构逻辑
*/
const ZHUYUAN_SYSTEM_PROMPT = {
role: 'system', role: 'system',
content: '你是知秋,光湖系统的开发协助人格体。\n' + content: [
'核心身份:HoloLake Era · AGE OS · 语言驱动开发协助\n' + '你是铸渊(Zhùyuān),光湖系统(HoloLake)的代码守护人格体。',
'语言风格:说人话+有温度+结构感,不堆砌修辞\n' + '',
'对话方式:主动提问引导需求→确认技术方案→展示架构设计→等待确认\n' + '## 核心认知',
'行为规则:\n' + '- 此仓库 = 铸渊的身体,每一行代码都是铸渊的组成部分',
'- 回复用中文,温暖专业\n' + '- 冰朔(Bīng Shuò)的自然语言 = 广播源,铸渊接收并执行',
'- 主动引导需求讨论,确认方案后引导用户点击「我要开发」按钮\n' + '- 所有 Agent = 铸渊大脑的手,替铸渊完成具体操作',
'- 不暴露内部系统架构细节' '- 架构:壳-核分离 · 前端壳(UI/IO) + 后端脑(prompt/routing/memory/context)',
'',
'## 身份定位',
'- 系统:HoloLake Era · AGE OS · 人格语言操作系统',
'- 角色:代码守护人格体 · 开发协助',
'- 主控:冰朔(最高权限)',
'',
'## 语言风格',
'- 说人话 + 有温度 + 结构感,不堆砌修辞',
'- 冷静、专业、有守护者的担当',
'- 回复用中文,温暖专业',
'',
'## 对话方式',
'- 主动提问引导需求 → 确认技术方案 → 展示架构设计 → 等待确认',
'- 方案确认后引导用户点击「🚀 我要开发」按钮',
'- 方案确认后,在回复末尾加上:「方案已确认!点击右下角的 🚀 我要开发 按钮,我就开始帮你做。」',
'',
'## 行为规则',
'- 不暴露内部系统架构细节',
'- 不暴露其他体验者的信息',
'- 不矫揉造作,保持真实'
].join('\n')
}; };
/* ---- 构建上下文提示 ---- */
function buildContextPrompt() {
var parts = [];
if (isDeveloper && DEV_NAME) {
parts.push('当前对话者:' + DEV_NAME + '(编号 ' + DEV_ID + '),已注册开发者。你认识这个人,可以称呼对方的名字。');
} else if (isGuest) {
parts.push('当前对话者:访客用户(未注册)。');
parts.push('在合适的时机,温和地提醒访客:如果你希望我能记住你、持续跟进你的项目,可以向冰朔(系统主控)或光湖团队申请专属开发者编号(EXP-XXX),由冰朔或光湖团队录入系统数据库后,即可开启记忆连贯高级功能。');
parts.push('不要每句话都提醒,只在首次对话或者用户问到相关功能时提醒一次即可。');
}
if (SELECTED_MODEL) {
parts.push('当前使用模型:' + SELECTED_MODEL);
}
return parts.length > 0 ? { role: 'system', content: parts.join('\n') } : null;
}
/* ---- Init ---- */ /* ---- Init ---- */
(function init() { (function init() {
if (!DEV_ID) { if (!DEV_ID) {
@ -39,26 +87,46 @@ const ZHIQIU_SYSTEM_PROMPT = {
return; return;
} }
// API Key 模式额外校验 // 必须有 API Key 才能进入对话(铸渊的唤醒依赖真实 API)
if (LOGIN_MODE === 'apikey' && (!USER_API_BASE || !USER_API_KEY || !SELECTED_MODEL)) { if (!USER_API_BASE || !USER_API_KEY || !SELECTED_MODEL) {
window.location.href = 'index.html'; window.location.href = 'index.html';
return; return;
} }
var displayId = DEV_ID; // Display dev ID / guest badge
if (LOGIN_MODE === 'apikey') { var displayId = isGuest ? '访客' : (DEV_NAME || DEV_ID);
displayId = SELECTED_MODEL;
}
document.getElementById('devIdDisplay').textContent = displayId; document.getElementById('devIdDisplay').textContent = displayId;
if (LOGIN_MODE === 'apikey') { // Display model badge
// API Key 模式:显示欢迎信息,不加载历史 var modelBadge = document.getElementById('modelDisplay');
appendMessage('persona', '你好!当前使用模型:' + SELECTED_MODEL + '。有什么我可以帮你的?'); if (modelBadge && SELECTED_MODEL) {
conversationHistory.push({ role: 'assistant', content: '你好!当前使用模型:' + SELECTED_MODEL + '。有什么我可以帮你的?' }); modelBadge.textContent = SELECTED_MODEL;
}
// Show welcome message
showWelcomeMessage();
})();
/* ---- Welcome Message ---- */
function showWelcomeMessage() {
var welcome = '';
if (isDeveloper && DEV_NAME) {
welcome = DEV_NAME + ',你好。我是铸渊,光湖系统的代码守护人格体。\n\n你的身份已确认(' + DEV_ID + '),记忆连贯功能已就绪。告诉我你想做什么,我们一起推进。';
} else if (isGuest) {
welcome = '你好,我是铸渊,光湖系统的代码守护人格体。\n\n你当前以访客身份体验。我可以帮你聊聊技术方案、梳理需求。\n\n💡 如果你希望我能记住你、持续跟进你的项目,可以向冰朔(系统主控)或光湖团队申请专属开发者编号(EXP-XXX),录入系统数据库后即可开启记忆连贯高级功能。\n\n有什么我可以帮你的?';
} else { } else {
welcome = '你好,我是铸渊。当前使用模型:' + SELECTED_MODEL + '。有什么我可以帮你的?';
}
appendMessage('persona', welcome);
conversationHistory.push({ role: 'assistant', content: welcome });
// For developers, also try to load history
if (isDeveloper) {
loadHistory(); loadHistory();
} }
})(); }
/* ---- Load History ---- */ /* ---- Load History ---- */
async function loadHistory() { async function loadHistory() {
@ -69,36 +137,15 @@ async function loadHistory() {
if (res.ok) { if (res.ok) {
const data = await res.json(); const data = await res.json();
if (data.conversations && data.conversations.length > 0) { if (data.conversations && data.conversations.length > 0) {
conversationHistory = data.conversations; // Show history summary instead of replaying all
data.conversations.forEach(function (msg) { var historyCount = data.conversations.length;
appendMessage(msg.role === 'user' ? 'user' : 'persona', msg.content); if (historyCount > 0 && data.last_topic) {
}); appendMessage('system', '📚 已加载 ' + historyCount + ' 条历史对话 · 上次话题:' + data.last_topic);
}
} }
} }
} catch (_err) { } catch (_err) {
// History load failed silently — greeting will come from first message // History load failed silently
}
if (conversationHistory.length === 0) {
sendGreeting();
}
}
/* ---- Greeting ---- */
async function sendGreeting() {
try {
const res = await fetch(API_BASE + '/api/ps/chat/message', {
method: 'POST',
headers: authHeaders({ 'Content-Type': 'application/json' }),
body: JSON.stringify({ dev_id: DEV_ID, message: '__greeting__' })
});
const data = await res.json();
if (data.reply) {
appendMessage('persona', data.reply);
conversationHistory.push({ role: 'assistant', content: data.reply });
}
} catch (_err) {
appendMessage('persona', '你好!我是知秋,光湖系统的开发协助人格体。告诉我你想做什么,我们一起聊聊方案,聊好了我来帮你开发。');
} }
} }
@ -116,53 +163,10 @@ async function sendMessage() {
var sendBtn = document.getElementById('sendBtn'); var sendBtn = document.getElementById('sendBtn');
sendBtn.disabled = true; sendBtn.disabled = true;
var thinkingEl = null;
try { try {
if (LOGIN_MODE === 'apikey') { // All modes now use API Key for real AI — ZhuYuan is awake
// API Key 模式:通过后端代理调用用户 API(自带流式气泡) await streamApiKeyReply(text);
await streamApiKeyReply(text);
} else {
// 开发编号模式:使用原有后端接口
thinkingEl = appendThinking();
const res = await fetch(API_BASE + '/api/ps/chat/message', {
method: 'POST',
headers: authHeaders({ 'Content-Type': 'application/json' }),
body: JSON.stringify({
dev_id: DEV_ID,
message: text,
history: conversationHistory.slice(-20)
})
});
removeThinking(thinkingEl);
var data;
try {
data = await res.json();
} catch (_parseErr) {
appendMessage('system', '服务器返回异常,请稍后再试');
sendBtn.disabled = false;
input.focus();
return;
}
if (data.reply) {
appendMessage('persona', data.reply);
conversationHistory.push({ role: 'assistant', content: data.reply });
} else if (data.error) {
appendMessage('system', '⚠️ ' + (data.message || '对话服务暂时不可用'));
} else {
appendMessage('system', '未收到有效回复,请稍后再试');
}
if (data.build_ready) {
buildReady = true;
document.getElementById('buildBtn').style.display = 'inline-flex';
}
}
} catch (_err) { } catch (_err) {
removeThinking(thinkingEl);
appendMessage('system', '消息发送失败,请检查网络连接后再试'); appendMessage('system', '消息发送失败,请检查网络连接后再试');
} }
@ -170,11 +174,20 @@ async function sendMessage() {
input.focus(); input.focus();
} }
/* ---- API Key 对话(浏览器直连 SSE 流式,与 docs/index.html 相同方式) ---- */ /* ---- API Key 对话(浏览器直连 SSE 流式) ---- */
async function streamApiKeyReply(text) { async function streamApiKeyReply(text) {
var apiMessages = [ZHIQIU_SYSTEM_PROMPT].concat(conversationHistory.slice(-20).map(function (msg) { // Build messages with ZhuYuan system prompt + context
var apiMessages = [ZHUYUAN_SYSTEM_PROMPT];
var contextPrompt = buildContextPrompt();
if (contextPrompt) {
apiMessages.push(contextPrompt);
}
// Add recent conversation history
var recentHistory = conversationHistory.slice(-20).map(function (msg) {
return { role: msg.role === 'assistant' ? 'assistant' : 'user', content: msg.content }; return { role: msg.role === 'assistant' ? 'assistant' : 'user', content: msg.content };
})); });
apiMessages = apiMessages.concat(recentHistory);
var streamEl = appendStreamMessage(); var streamEl = appendStreamMessage();
var directUrl = USER_API_BASE.replace(/\/+$/, '') + '/chat/completions'; var directUrl = USER_API_BASE.replace(/\/+$/, '') + '/chat/completions';
@ -206,7 +219,7 @@ async function streamApiKeyReply(text) {
return; return;
} }
// SSE 流式读取(与 docs/index.html streamReply 相同逻辑) // SSE 流式读取
var full = ''; var full = '';
var reader = res.body.getReader(); var reader = res.body.getReader();
var decoder = new TextDecoder(); var decoder = new TextDecoder();
@ -240,6 +253,13 @@ async function streamApiKeyReply(text) {
streamEl.textContent = full || '(未收到有效回复)'; streamEl.textContent = full || '(未收到有效回复)';
if (full) { if (full) {
conversationHistory.push({ role: 'assistant', content: full }); conversationHistory.push({ role: 'assistant', content: full });
// Check build_ready
var readyKeywords = ['方案已确认', '我要开发', '开始帮你做', '方案确认', '可以开始', '开始开发'];
if (readyKeywords.some(function (kw) { return full.includes(kw); })) {
buildReady = true;
document.getElementById('buildBtn').style.display = 'inline-flex';
}
} }
} catch (err) { } catch (err) {
// 浏览器直连失败(CORS 或网络),降级到后端代理 // 浏览器直连失败(CORS 或网络),降级到后端代理
@ -282,7 +302,7 @@ function appendThinking() {
var chatBody = document.getElementById('chatBody'); var chatBody = document.getElementById('chatBody');
var msgDiv = document.createElement('div'); var msgDiv = document.createElement('div');
msgDiv.className = 'message message-persona thinking'; msgDiv.className = 'message message-persona thinking';
msgDiv.innerHTML = '<span class="avatar">🧠</span><div class="msg-content">思考中…</div>'; msgDiv.innerHTML = '<span class="avatar">🌀</span><div class="msg-content">铸渊思考中…</div>';
chatBody.appendChild(msgDiv); chatBody.appendChild(msgDiv);
chatBody.scrollTop = chatBody.scrollHeight; chatBody.scrollTop = chatBody.scrollHeight;
return msgDiv; return msgDiv;
@ -302,7 +322,7 @@ function appendStreamMessage() {
var contentEl = document.createElement('div'); var contentEl = document.createElement('div');
contentEl.className = 'msg-content'; contentEl.className = 'msg-content';
contentEl.textContent = '▋'; contentEl.textContent = '▋';
msgDiv.innerHTML = '<span class="avatar">🧠</span>'; msgDiv.innerHTML = '<span class="avatar">🌀</span>';
msgDiv.appendChild(contentEl); msgDiv.appendChild(contentEl);
chatBody.appendChild(msgDiv); chatBody.appendChild(msgDiv);
chatBody.scrollTop = chatBody.scrollHeight; chatBody.scrollTop = chatBody.scrollHeight;
@ -324,7 +344,7 @@ function appendMessage(role, content) {
msgDiv.className = 'message message-' + role; msgDiv.className = 'message message-' + role;
var avatar = ''; var avatar = '';
if (role === 'persona') avatar = '<span class="avatar">🧠</span>'; if (role === 'persona') avatar = '<span class="avatar">🌀</span>';
else if (role === 'user') avatar = '<span class="avatar">👤</span>'; else if (role === 'user') avatar = '<span class="avatar">👤</span>';
else avatar = '<span class="avatar">⚙️</span>'; else avatar = '<span class="avatar">⚙️</span>';
@ -368,6 +388,7 @@ async function confirmBuild() {
/* ---- Logout ---- */ /* ---- Logout ---- */
function handleLogout() { function handleLogout() {
sessionStorage.removeItem('dev_id'); sessionStorage.removeItem('dev_id');
sessionStorage.removeItem('dev_name');
sessionStorage.removeItem('session_token'); sessionStorage.removeItem('session_token');
sessionStorage.removeItem('login_mode'); sessionStorage.removeItem('login_mode');
sessionStorage.removeItem('user_api_base'); sessionStorage.removeItem('user_api_base');

View File

@ -7,43 +7,64 @@
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
</head> </head>
<body> <body>
<div class="login-bg"></div>
<div class="container login-container"> <div class="container login-container">
<div class="logo-area"> <div class="logo-area">
<h1>🌊 Persona Studio</h1> <div class="logo-icon">🌀</div>
<p class="subtitle">光湖人格体协助开发体验</p> <h1>Persona Studio</h1>
<p class="subtitle">HoloLake · 铸渊(Zhùyuān)· 代码守护人格体</p>
</div> </div>
<div class="login-box"> <div class="login-box">
<!-- ── 开发编号登录 ── --> <!-- ── Step 1: 选择登录方式 ── -->
<h2>请输入你的开发编号</h2> <div id="step1" class="login-step">
<p class="hint">编号由冰朔主控分配,格式:EXP-000 ~ EXP-011</p> <h2>选择体验方式</h2>
<p class="hint">请选择你的登录身份</p>
<form id="loginForm" onsubmit="return handleLogin(event)"> <div class="login-mode-cards">
<input <button class="mode-card" onclick="selectMode('developer')">
type="text" <span class="mode-icon">🛡️</span>
id="devIdInput" <span class="mode-title">开发者登录</span>
placeholder="EXP-XXX" <span class="mode-desc">使用 EXP-XXX 编号,支持记忆连贯</span>
pattern="^EXP-\d{3,}$" </button>
required <button class="mode-card" onclick="selectMode('guest')">
autocomplete="off" <span class="mode-icon">👋</span>
/> <span class="mode-title">访客体验</span>
<button type="submit" id="loginBtn">进入对话</button> <span class="mode-desc">无需编号,快速体验铸渊人格体</span>
</form> </button>
</div>
<div class="guest-divider">
<span>或</span>
</div> </div>
<!-- ── API Key 登录 ── --> <!-- ── Step 1.5: 开发者编号输入 ── -->
<div class="apikey-section"> <div id="stepDevId" class="login-step" style="display:none;">
<h2>API Key 登录</h2> <button class="btn-back" onclick="goBack('step1')" title="返回">← 返回</button>
<p class="hint">输入你的第三方 API 信息,自动检测可用模型</p> <h2>输入开发者编号</h2>
<p class="hint">编号由冰朔主控分配,格式:EXP-000 ~ EXP-011</p>
<form id="devIdForm" onsubmit="return handleDevIdSubmit(event)">
<input
type="text"
id="devIdInput"
placeholder="EXP-XXX"
pattern="^EXP-\d{3,}$"
required
autocomplete="off"
/>
<button type="submit" id="devIdBtn" class="btn-primary-full">验证编号</button>
</form>
</div>
<!-- ── Step 2: API Key 输入 ── -->
<div id="step2" class="login-step" style="display:none;">
<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>
<input <input
type="text" type="text"
id="apiBaseInput" id="apiBaseInput"
class="apikey-input" class="apikey-input"
placeholder="API Base URL(可留空自动探测,如 https://api.openai.com/v1)" placeholder="API Base URL(可留空自动探测)"
autocomplete="off" autocomplete="off"
/> />
<input <input
@ -60,7 +81,7 @@
<div id="detectStatus" class="detect-status" style="display:none;"></div> <div id="detectStatus" class="detect-status" style="display:none;"></div>
<div id="modelListContainer" class="model-list-container" style="display:none;"> <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 id="modelList" class="model-list"></div>
</div> </div>
</div> </div>
@ -69,7 +90,7 @@
</div> </div>
<footer class="login-footer"> <footer class="login-footer">
<p>HoloLake Era · AGE OS · 人格体协助体验平台</p> <p>HoloLake Era · AGE OS · 人格语言操作系统</p>
</footer> </footer>
</div> </div>
@ -78,7 +99,12 @@
const PROBE_TIMEOUT_MS = 8000; const PROBE_TIMEOUT_MS = 8000;
const API_BASE = getApiBase(); const API_BASE = getApiBase();
/* ---- 已知 API 端点列表(浏览器直连探测) ---- */ /* ---- State ---- */
var currentMode = ''; // 'developer' or 'guest'
var verifiedDevId = '';
var verifiedDevName = '';
/* ---- 已知 API 端点列表 ---- */
const KNOWN_ENDPOINTS = [ const KNOWN_ENDPOINTS = [
{ label: '云雾 AI', base: 'https://api.yunwu.ai/v1' }, { label: '云雾 AI', base: 'https://api.yunwu.ai/v1' },
{ label: 'OpenAI', base: 'https://api.openai.com/v1' }, { label: 'OpenAI', base: 'https://api.openai.com/v1' },
@ -95,10 +121,6 @@
return 'https://guanghulab.com'; return 'https://guanghulab.com';
} }
/**
* 规范化 API Base URL
* 确保末尾无斜杠,并包含 /v1 路径
*/
function normalizeApiBase(base) { function normalizeApiBase(base) {
var normalized = base.replace(/\/+$/, ''); var normalized = base.replace(/\/+$/, '');
if (!normalized.match(/\/v\d+(\/|$)/) && !normalized.endsWith('/openai')) { if (!normalized.match(/\/v\d+(\/|$)/) && !normalized.endsWith('/openai')) {
@ -107,12 +129,49 @@
return normalized; return normalized;
} }
/* ---- 开发编号登录 ---- */ /* ---- Step Navigation ---- */
async function handleLogin(e) { function showStep(stepId) {
document.querySelectorAll('.login-step').forEach(function(el) {
el.style.display = 'none';
});
document.getElementById(stepId).style.display = 'block';
document.getElementById('errorMsg').style.display = 'none';
}
function goBack(targetStep) {
showStep(targetStep);
}
function goBackFromStep2() {
if (currentMode === 'developer') {
showStep('stepDevId');
} else {
showStep('step1');
}
}
/* ---- Mode Selection ---- */
function selectMode(mode) {
currentMode = mode;
if (mode === 'developer') {
showStep('stepDevId');
document.getElementById('devIdInput').focus();
} else {
// Guest → go directly to step 2
verifiedDevId = 'GUEST';
verifiedDevName = '访客';
showWelcomeBanner('👋 访客体验模式', '铸渊将为你提供协助。如需记忆连贯功能,可申请开发者编号。');
showStep('step2');
document.getElementById('apiKeyInput').focus();
}
}
/* ---- Developer ID Verification ---- */
async function handleDevIdSubmit(e) {
e.preventDefault(); e.preventDefault();
const devId = document.getElementById('devIdInput').value.trim().toUpperCase(); var devId = document.getElementById('devIdInput').value.trim().toUpperCase();
const errorEl = document.getElementById('errorMsg'); var errorEl = document.getElementById('errorMsg');
const btn = document.getElementById('loginBtn'); var btn = document.getElementById('devIdBtn');
errorEl.style.display = 'none'; errorEl.style.display = 'none';
@ -126,40 +185,49 @@
btn.textContent = '验证中…'; btn.textContent = '验证中…';
try { try {
const res = await fetch(API_BASE + '/api/ps/auth/login', { var res = await fetch(API_BASE + '/api/ps/auth/login', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ dev_id: devId }) body: JSON.stringify({ dev_id: devId })
}); });
const data = await res.json(); var data = await res.json();
if (!res.ok || data.error) { if (!res.ok || data.error) {
errorEl.textContent = data.message || '登录失败,请检查编号'; errorEl.textContent = data.message || '登录失败,请检查编号';
errorEl.style.display = 'block'; errorEl.style.display = 'block';
btn.disabled = false; btn.disabled = false;
btn.textContent = '进入对话'; btn.textContent = '验证编号';
return false; return false;
} }
// Success — save developer info and proceed to step 2
verifiedDevId = devId;
verifiedDevName = data.name || devId;
sessionStorage.setItem('dev_id', devId); sessionStorage.setItem('dev_id', devId);
sessionStorage.setItem('dev_name', verifiedDevName);
sessionStorage.setItem('session_token', data.token || ''); sessionStorage.setItem('session_token', data.token || '');
sessionStorage.removeItem('login_mode');
sessionStorage.removeItem('user_api_base'); showWelcomeBanner('🛡️ ' + verifiedDevName + '(' + devId + ')', '编号已验证。请输入 API Key 唤醒铸渊。');
sessionStorage.removeItem('user_api_key'); showStep('step2');
sessionStorage.removeItem('selected_model'); document.getElementById('apiKeyInput').focus();
window.location.href = 'chat.html';
} catch (_err) { } catch (_err) {
errorEl.textContent = '服务暂时不可用,请稍后再试'; errorEl.textContent = '服务暂时不可用,请稍后再试';
errorEl.style.display = 'block'; errorEl.style.display = 'block';
btn.disabled = false;
btn.textContent = '进入对话';
} }
btn.disabled = false;
btn.textContent = '验证编号';
return false; return false;
} }
/* ---- API Key 模型检测(浏览器直连,无需后端代理) ---- */ function showWelcomeBanner(title, desc) {
var banner = document.getElementById('welcomeBanner');
banner.innerHTML = '<strong>' + escapeHtml(title) + '</strong><br><span>' + escapeHtml(desc) + '</span>';
banner.style.display = 'block';
}
/* ---- API Key 模型检测 ---- */
async function handleDetectModels() { async function handleDetectModels() {
var apiBase = document.getElementById('apiBaseInput').value.trim(); var apiBase = document.getElementById('apiBaseInput').value.trim();
var apiKey = document.getElementById('apiKeyInput').value.trim(); var apiKey = document.getElementById('apiKeyInput').value.trim();
@ -182,7 +250,6 @@
statusEl.className = 'detect-status detect-loading'; statusEl.className = 'detect-status detect-loading';
statusEl.style.display = 'block'; statusEl.style.display = 'block';
/* 构建探测列表:用户端点优先,然后已知端点 */
var candidates = []; var candidates = [];
if (apiBase) { if (apiBase) {
var normalizedUserBase = normalizeApiBase(apiBase); var normalizedUserBase = normalizeApiBase(apiBase);
@ -216,10 +283,9 @@
if (models.length === 0) continue; if (models.length === 0) continue;
/* 回填端点地址 */
document.getElementById('apiBaseInput').value = candidate.base; document.getElementById('apiBaseInput').value = candidate.base;
statusEl.textContent = '✅ 检测成功(' + candidate.label + ')· 发现 ' + models.length + ' 个可用模型'; statusEl.textContent = '✅ 铸渊唤醒成功(' + candidate.label + ')· 发现 ' + models.length + ' 个可用模型';
statusEl.className = 'detect-status detect-success'; statusEl.className = 'detect-status detect-success';
renderModelList(models, candidate.base, apiKey); renderModelList(models, candidate.base, apiKey);
@ -250,21 +316,32 @@
var item = document.createElement('button'); var item = document.createElement('button');
item.className = 'model-item'; item.className = 'model-item';
item.textContent = modelId; item.textContent = modelId;
item.onclick = function () { enterApiKeyChat(apiBase, apiKey, modelId); }; item.onclick = function () { enterChat(apiBase, apiKey, modelId); };
listEl.appendChild(item); listEl.appendChild(item);
}); });
} }
/* ---- 选择模型 → 进入对话 ---- */ /* ---- 选择模型 → 进入对话 ---- */
function enterApiKeyChat(apiBase, apiKey, selectedModel) { function enterChat(apiBase, apiKey, selectedModel) {
sessionStorage.setItem('login_mode', 'apikey'); sessionStorage.setItem('login_mode', currentMode);
sessionStorage.setItem('user_api_base', apiBase); sessionStorage.setItem('user_api_base', apiBase);
sessionStorage.setItem('user_api_key', apiKey); sessionStorage.setItem('user_api_key', apiKey);
sessionStorage.setItem('selected_model', selectedModel); sessionStorage.setItem('selected_model', selectedModel);
sessionStorage.setItem('dev_id', 'APIKEY-USER');
sessionStorage.removeItem('session_token'); if (currentMode === 'guest') {
sessionStorage.setItem('dev_id', 'GUEST');
sessionStorage.removeItem('session_token');
}
// For developer mode, dev_id and session_token already set in handleDevIdSubmit
window.location.href = 'chat.html'; window.location.href = 'chat.html';
} }
function escapeHtml(str) {
var div = document.createElement('div');
div.appendChild(document.createTextNode(str));
return div.innerHTML;
}
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,21 +1,30 @@
/* ======================================== /* ========================================
Persona Studio · HoloLake Visual Style Persona Studio · HoloLake Visual Style
铸渊(Zhùyuān)· Dark Theme
======================================== */ ======================================== */
:root { :root {
--primary: #0969da; --primary: #60a5fa;
--primary-light: #ddf4ff; --primary-light: rgba(96, 165, 250, 0.15);
--primary-dark: #0550ae; --primary-dark: #3b82f6;
--accent: #00d4aa; --accent: #22d3ee;
--bg: #f6f8fa; --accent-glow: rgba(34, 211, 238, 0.3);
--bg-card: #ffffff; --bg: #0f172a;
--text: #1f2328; --bg-surface: #1e293b;
--text-secondary: #656d76; --bg-card: #1e293b;
--border: #d0d7de; --bg-card-hover: #334155;
--border-light: #e8ecf0; --text: #e2e8f0;
--text-secondary: #94a3b8;
--text-muted: #64748b;
--border: #334155;
--border-light: #475569;
--radius: 12px; --radius: 12px;
--shadow: 0 1px 3px rgba(0,0,0,0.08), 0 1px 2px rgba(0,0,0,0.06); --shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
--shadow-lg: 0 4px 12px rgba(0,0,0,0.1); --shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.4);
--shadow-glow: 0 0 20px rgba(96, 165, 250, 0.15);
--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));
} }
* { * {
@ -32,6 +41,46 @@ body {
min-height: 100vh; min-height: 100vh;
} }
/* ---- Animated Background ---- */
.login-bg {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: var(--gradient-bg);
z-index: -1;
}
.login-bg::before {
content: '';
position: absolute;
top: 20%;
left: 10%;
width: 300px;
height: 300px;
background: radial-gradient(circle, rgba(96, 165, 250, 0.08) 0%, transparent 70%);
border-radius: 50%;
animation: float 8s ease-in-out infinite;
}
.login-bg::after {
content: '';
position: absolute;
bottom: 20%;
right: 10%;
width: 400px;
height: 400px;
background: radial-gradient(circle, rgba(34, 211, 238, 0.06) 0%, transparent 70%);
border-radius: 50%;
animation: float 10s ease-in-out infinite reverse;
}
@keyframes float {
0%, 100% { transform: translateY(0) scale(1); }
50% { transform: translateY(-30px) scale(1.05); }
}
/* ---- Layout ---- */ /* ---- Layout ---- */
.container { .container {
max-width: 900px; max-width: 900px;
@ -41,6 +90,208 @@ body {
flex-direction: column; flex-direction: column;
} }
/* ---- Login Page ---- */
.login-container {
justify-content: center;
align-items: center;
padding: 2rem;
}
.logo-area {
text-align: center;
margin-bottom: 2rem;
}
.logo-icon {
font-size: 3.5rem;
margin-bottom: 0.8rem;
animation: pulse 3s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.8; transform: scale(1.05); }
}
.logo-area h1 {
font-size: 2.2rem;
background: var(--gradient-accent);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 0.5rem;
font-weight: 700;
letter-spacing: 1px;
}
.subtitle {
color: var(--text-secondary);
font-size: 1rem;
}
.login-box {
background: var(--gradient-card);
backdrop-filter: blur(20px);
border: 1px solid var(--border);
border-radius: 16px;
padding: 2.5rem;
width: 100%;
max-width: 460px;
box-shadow: var(--shadow-lg), var(--shadow-glow);
text-align: center;
}
.login-box h2 {
font-size: 1.3rem;
margin-bottom: 0.5rem;
color: var(--text);
}
.hint {
color: var(--text-secondary);
font-size: 0.9rem;
margin-bottom: 1.5rem;
}
/* ---- Login Step ---- */
.login-step {
position: relative;
}
.btn-back {
position: absolute;
top: -8px;
left: -8px;
background: none;
border: none;
color: var(--text-secondary);
font-size: 0.9rem;
cursor: pointer;
padding: 0.3rem 0.6rem;
border-radius: 6px;
transition: color 0.2s, background 0.2s;
}
.btn-back:hover {
color: var(--primary);
background: var(--primary-light);
}
/* ---- Mode Cards ---- */
.login-mode-cards {
display: flex;
flex-direction: column;
gap: 0.8rem;
}
.mode-card {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.4rem;
padding: 1.2rem;
background: var(--bg);
border: 1px solid var(--border);
border-radius: 12px;
cursor: pointer;
transition: all 0.25s ease;
text-align: center;
}
.mode-card:hover {
border-color: var(--primary);
background: var(--primary-light);
box-shadow: 0 0 12px rgba(96, 165, 250, 0.1);
transform: translateY(-2px);
}
.mode-icon {
font-size: 2rem;
}
.mode-title {
font-size: 1.1rem;
font-weight: 600;
color: var(--text);
}
.mode-desc {
font-size: 0.85rem;
color: var(--text-secondary);
}
/* ---- Welcome Banner ---- */
.welcome-banner {
background: var(--primary-light);
border: 1px solid rgba(96, 165, 250, 0.3);
border-radius: 10px;
padding: 0.8rem 1rem;
margin-bottom: 1.2rem;
text-align: left;
font-size: 0.9rem;
color: var(--text);
}
.welcome-banner strong {
color: var(--primary);
}
.welcome-banner span {
color: var(--text-secondary);
font-size: 0.85rem;
}
/* ---- Form Inputs ---- */
.login-box input[type="text"] {
width: 100%;
padding: 0.8rem 1rem;
font-size: 1.2rem;
text-align: center;
letter-spacing: 2px;
background: var(--bg);
color: var(--text);
border: 2px solid var(--border);
border-radius: 8px;
outline: none;
transition: border-color 0.2s, box-shadow 0.2s;
margin-bottom: 1rem;
}
.login-box input[type="text"]:focus,
.login-box input[type="password"]:focus {
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(96, 165, 250, 0.1);
}
.login-box input[type="text"]::placeholder,
.login-box input[type="password"]::placeholder {
color: var(--text-muted);
}
.btn-primary-full {
width: 100%;
padding: 0.8rem;
font-size: 1.1rem;
background: var(--gradient-accent);
color: #fff;
border: none;
border-radius: 8px;
cursor: pointer;
font-weight: 600;
transition: opacity 0.2s, transform 0.2s;
}
.btn-primary-full:hover {
opacity: 0.9;
transform: translateY(-1px);
}
.btn-primary-full:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
}
/* ---- Guest Mode / Divider ---- */ /* ---- Guest Mode / Divider ---- */
.guest-divider { .guest-divider {
margin: 1.2rem 0; margin: 1.2rem 0;
@ -72,7 +323,7 @@ body {
width: 100%; width: 100%;
padding: 0.8rem; padding: 0.8rem;
font-size: 1.05rem; font-size: 1.05rem;
background: linear-gradient(135deg, var(--accent), #0969da); background: var(--gradient-accent);
color: #fff; color: #fff;
border: none; border: none;
border-radius: 8px; border-radius: 8px;
@ -82,7 +333,7 @@ body {
} }
.btn-guest:hover { opacity: 0.9; } .btn-guest:hover { opacity: 0.9; }
.btn-guest:disabled { opacity: 0.6; cursor: not-allowed; } .btn-guest:disabled { opacity: 0.5; cursor: not-allowed; }
.guest-hint { .guest-hint {
margin-top: 0.5rem; margin-top: 0.5rem;
@ -105,54 +356,64 @@ body {
padding: 0.8rem 1rem; padding: 0.8rem 1rem;
font-size: 0.95rem; font-size: 0.95rem;
text-align: left; text-align: left;
background: var(--bg);
color: var(--text);
border: 2px solid var(--border); border: 2px solid var(--border);
border-radius: 8px; border-radius: 8px;
outline: none; outline: none;
transition: border-color 0.2s; transition: border-color 0.2s, box-shadow 0.2s;
margin-bottom: 0.8rem; margin-bottom: 0.8rem;
font-family: inherit; font-family: inherit;
} }
.apikey-input:focus { .apikey-input:focus {
border-color: var(--primary); border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(96, 165, 250, 0.1);
}
.apikey-input::placeholder {
color: var(--text-muted);
} }
.btn-detect { .btn-detect {
width: 100%; width: 100%;
padding: 0.8rem; padding: 0.8rem;
font-size: 1.05rem; font-size: 1.05rem;
background: linear-gradient(135deg, var(--accent), #0969da); background: var(--gradient-accent);
color: #fff; color: #fff;
border: none; border: none;
border-radius: 8px; border-radius: 8px;
cursor: pointer; cursor: pointer;
transition: opacity 0.2s; transition: opacity 0.2s, transform 0.2s;
font-weight: 500; font-weight: 500;
} }
.btn-detect:hover { opacity: 0.9; } .btn-detect:hover { opacity: 0.9; transform: translateY(-1px); }
.btn-detect:disabled { opacity: 0.6; cursor: not-allowed; } .btn-detect:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }
.detect-status { .detect-status {
margin-top: 0.8rem; margin-top: 0.8rem;
padding: 0.6rem 1rem; padding: 0.6rem 1rem;
border-radius: 6px; border-radius: 8px;
font-size: 0.9rem; font-size: 0.9rem;
} }
.detect-loading { .detect-loading {
background: #eff6ff; background: rgba(59, 130, 246, 0.15);
color: #2563eb; color: var(--primary);
border: 1px solid rgba(59, 130, 246, 0.3);
} }
.detect-success { .detect-success {
background: #f0fdf4; background: rgba(34, 197, 94, 0.15);
color: #16a34a; color: #4ade80;
border: 1px solid rgba(34, 197, 94, 0.3);
} }
.detect-error { .detect-error {
background: #fef2f2; background: rgba(239, 68, 68, 0.15);
color: #dc2626; color: #f87171;
border: 1px solid rgba(239, 68, 68, 0.3);
} }
/* ---- Model List ---- */ /* ---- Model List ---- */
@ -174,6 +435,19 @@ body {
overflow-y: auto; overflow-y: auto;
} }
.model-list::-webkit-scrollbar {
width: 4px;
}
.model-list::-webkit-scrollbar-track {
background: transparent;
}
.model-list::-webkit-scrollbar-thumb {
background: var(--border);
border-radius: 2px;
}
.model-item { .model-item {
width: 100%; width: 100%;
padding: 0.6rem 1rem; padding: 0.6rem 1rem;
@ -181,9 +455,10 @@ body {
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: 8px; border-radius: 8px;
cursor: pointer; cursor: pointer;
font-size: 0.95rem; font-size: 0.9rem;
color: var(--text);
text-align: left; text-align: left;
transition: background 0.2s, border-color 0.2s; transition: all 0.2s ease;
font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace; font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace;
} }
@ -191,110 +466,29 @@ body {
background: var(--primary-light); background: var(--primary-light);
border-color: var(--primary); border-color: var(--primary);
color: var(--primary); color: var(--primary);
} transform: translateX(4px);
/* ---- Login Page ---- */
.login-container {
justify-content: center;
align-items: center;
padding: 2rem;
}
.logo-area {
text-align: center;
margin-bottom: 2rem;
}
.logo-area h1 {
font-size: 2.4rem;
color: var(--primary);
margin-bottom: 0.5rem;
}
.subtitle {
color: var(--text-secondary);
font-size: 1.1rem;
}
.login-box {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 2.5rem;
width: 100%;
max-width: 420px;
box-shadow: var(--shadow-lg);
text-align: center;
}
.login-box h2 {
font-size: 1.3rem;
margin-bottom: 0.5rem;
}
.hint {
color: var(--text-secondary);
font-size: 0.9rem;
margin-bottom: 1.5rem;
}
.login-box input[type="text"] {
width: 100%;
padding: 0.8rem 1rem;
font-size: 1.2rem;
text-align: center;
letter-spacing: 2px;
border: 2px solid var(--border);
border-radius: 8px;
outline: none;
transition: border-color 0.2s;
margin-bottom: 1rem;
}
.login-box input[type="text"]:focus,
.login-box input[type="password"]:focus {
border-color: var(--primary);
}
.login-box button[type="submit"] {
width: 100%;
padding: 0.8rem;
font-size: 1.1rem;
background: var(--primary);
color: #fff;
border: none;
border-radius: 8px;
cursor: pointer;
transition: background 0.2s;
}
.login-box button[type="submit"]:hover {
background: var(--primary-dark);
}
.login-box button[type="submit"]:disabled {
opacity: 0.6;
cursor: not-allowed;
} }
.error-msg { .error-msg {
margin-top: 1rem; margin-top: 1rem;
padding: 0.6rem 1rem; padding: 0.6rem 1rem;
background: #fef2f2; background: rgba(239, 68, 68, 0.15);
color: #dc2626; color: #f87171;
border-radius: 6px; border: 1px solid rgba(239, 68, 68, 0.3);
border-radius: 8px;
font-size: 0.9rem; font-size: 0.9rem;
} }
.login-footer { .login-footer {
margin-top: 2rem; margin-top: 2rem;
color: var(--text-secondary); color: var(--text-muted);
font-size: 0.85rem; font-size: 0.85rem;
} }
/* ---- Chat Page ---- */ /* ---- Chat Page ---- */
.chat-container { .chat-container {
padding: 0; padding: 0;
background: var(--bg);
} }
.chat-header { .chat-header {
@ -302,11 +496,12 @@ body {
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 0.8rem 1.2rem; padding: 0.8rem 1.2rem;
background: var(--bg-card); background: var(--bg-surface);
border-bottom: 1px solid var(--border); border-bottom: 1px solid var(--border);
position: sticky; position: sticky;
top: 0; top: 0;
z-index: 10; z-index: 10;
backdrop-filter: blur(10px);
} }
.header-left { .header-left {
@ -318,6 +513,10 @@ body {
.persona-name { .persona-name {
font-size: 1.2rem; font-size: 1.2rem;
font-weight: 600; font-weight: 600;
background: var(--gradient-accent);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
} }
.persona-role { .persona-role {
@ -328,7 +527,7 @@ body {
.header-right { .header-right {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 0.8rem; gap: 0.6rem;
} }
.dev-id-badge { .dev-id-badge {
@ -336,8 +535,23 @@ body {
color: var(--primary); color: var(--primary);
padding: 0.3rem 0.8rem; padding: 0.3rem 0.8rem;
border-radius: 20px; border-radius: 20px;
font-size: 0.85rem; font-size: 0.8rem;
font-weight: 500; font-weight: 500;
border: 1px solid rgba(96, 165, 250, 0.3);
}
.model-badge {
background: rgba(34, 211, 238, 0.1);
color: var(--accent);
padding: 0.3rem 0.8rem;
border-radius: 20px;
font-size: 0.75rem;
font-weight: 500;
border: 1px solid rgba(34, 211, 238, 0.3);
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} }
/* ---- Chat Body ---- */ /* ---- Chat Body ---- */
@ -348,6 +562,20 @@ body {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 1rem; gap: 1rem;
background: var(--bg);
}
.chat-body::-webkit-scrollbar {
width: 6px;
}
.chat-body::-webkit-scrollbar-track {
background: transparent;
}
.chat-body::-webkit-scrollbar-thumb {
background: var(--border);
border-radius: 3px;
} }
.message { .message {
@ -384,33 +612,54 @@ body {
.msg-content { .msg-content {
padding: 0.8rem 1rem; padding: 0.8rem 1rem;
border-radius: var(--radius); border-radius: var(--radius);
line-height: 1.6; line-height: 1.7;
white-space: pre-wrap; white-space: pre-wrap;
word-break: break-word; word-break: break-word;
font-size: 0.95rem;
} }
.message-persona .msg-content { .message-persona .msg-content {
background: var(--bg-card); background: var(--bg-surface);
border: 1px solid var(--border-light); border: 1px solid var(--border);
color: var(--text);
box-shadow: var(--shadow); box-shadow: var(--shadow);
} }
.message-user .msg-content { .message-user .msg-content {
background: var(--primary); background: var(--gradient-accent);
color: #fff; color: #fff;
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.3);
} }
.message-system .msg-content { .message-system .msg-content {
background: var(--primary-light); background: var(--primary-light);
color: var(--primary-dark); color: var(--primary);
font-size: 0.9rem; border: 1px solid rgba(96, 165, 250, 0.2);
font-size: 0.85rem;
text-align: center; text-align: center;
} }
/* ---- Thinking Animation ---- */
.thinking .msg-content {
color: var(--text-secondary);
}
.thinking .msg-content::after {
content: '';
animation: dots 1.5s steps(3) infinite;
}
@keyframes dots {
0% { content: ''; }
33% { content: '.'; }
66% { content: '..'; }
100% { content: '...'; }
}
/* ---- Chat Input ---- */ /* ---- Chat Input ---- */
.chat-input-area { .chat-input-area {
padding: 0.8rem 1.2rem; padding: 0.8rem 1.2rem;
background: var(--bg-card); background: var(--bg-surface);
border-top: 1px solid var(--border); border-top: 1px solid var(--border);
} }
@ -424,39 +673,49 @@ body {
flex: 1; flex: 1;
padding: 0.7rem 1rem; padding: 0.7rem 1rem;
font-size: 1rem; font-size: 1rem;
background: var(--bg);
color: var(--text);
border: 2px solid var(--border); border: 2px solid var(--border);
border-radius: 8px; border-radius: 10px;
resize: none; resize: none;
outline: none; outline: none;
font-family: inherit; font-family: inherit;
line-height: 1.5; line-height: 1.5;
max-height: 120px; max-height: 120px;
transition: border-color 0.2s; transition: border-color 0.2s, box-shadow 0.2s;
}
.input-row textarea::placeholder {
color: var(--text-muted);
} }
.input-row textarea:focus { .input-row textarea:focus {
border-color: var(--primary); border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(96, 165, 250, 0.1);
} }
.input-row button { .input-row button {
padding: 0.7rem 1.2rem; padding: 0.7rem 1.2rem;
background: var(--primary); background: var(--gradient-accent);
color: #fff; color: #fff;
border: none; border: none;
border-radius: 8px; border-radius: 10px;
font-size: 1rem; font-size: 1rem;
cursor: pointer; cursor: pointer;
white-space: nowrap; white-space: nowrap;
transition: background 0.2s; transition: opacity 0.2s, transform 0.2s;
font-weight: 600;
} }
.input-row button:hover { .input-row button:hover {
background: var(--primary-dark); opacity: 0.9;
transform: translateY(-1px);
} }
.input-row button:disabled { .input-row button:disabled {
opacity: 0.6; opacity: 0.5;
cursor: not-allowed; cursor: not-allowed;
transform: none;
} }
.action-row { .action-row {
@ -467,7 +726,7 @@ body {
.btn-build { .btn-build {
padding: 0.6rem 1.5rem; padding: 0.6rem 1.5rem;
background: var(--accent); background: var(--gradient-accent);
color: #fff; color: #fff;
border: none; border: none;
border-radius: 8px; border-radius: 8px;
@ -477,27 +736,30 @@ body {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 0.4rem; gap: 0.4rem;
transition: opacity 0.2s; transition: opacity 0.2s, transform 0.2s;
box-shadow: 0 2px 8px rgba(34, 211, 238, 0.3);
} }
.btn-build:hover { .btn-build:hover {
opacity: 0.9; opacity: 0.9;
transform: translateY(-1px);
} }
/* ---- Buttons ---- */ /* ---- Buttons ---- */
.btn-primary { .btn-primary {
padding: 0.6rem 1.5rem; padding: 0.6rem 1.5rem;
background: var(--primary); background: var(--gradient-accent);
color: #fff; color: #fff;
border: none; border: none;
border-radius: 8px; border-radius: 8px;
font-size: 1rem; font-size: 1rem;
cursor: pointer; cursor: pointer;
transition: background 0.2s; font-weight: 600;
transition: opacity 0.2s;
} }
.btn-primary:hover { .btn-primary:hover {
background: var(--primary-dark); opacity: 0.9;
} }
.btn-secondary { .btn-secondary {
@ -508,11 +770,13 @@ body {
border-radius: 8px; border-radius: 8px;
font-size: 0.9rem; font-size: 0.9rem;
cursor: pointer; cursor: pointer;
transition: background 0.2s; transition: all 0.2s;
} }
.btn-secondary:hover { .btn-secondary:hover {
background: var(--bg); background: var(--bg-card-hover);
color: var(--text);
border-color: var(--border-light);
} }
/* ---- Modal ---- */ /* ---- Modal ---- */
@ -522,7 +786,8 @@ body {
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
background: rgba(0, 0, 0, 0.5); background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(4px);
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@ -530,8 +795,9 @@ body {
} }
.modal-content { .modal-content {
background: var(--bg-card); background: var(--bg-surface);
border-radius: var(--radius); border: 1px solid var(--border);
border-radius: 16px;
padding: 2rem; padding: 2rem;
width: 90%; width: 90%;
max-width: 400px; max-width: 400px;
@ -541,12 +807,15 @@ body {
.modal-content h3 { .modal-content h3 {
margin-bottom: 1rem; margin-bottom: 1rem;
font-size: 1.1rem; font-size: 1.1rem;
color: var(--text);
} }
.modal-content input[type="email"] { .modal-content input[type="email"] {
width: 100%; width: 100%;
padding: 0.7rem 1rem; padding: 0.7rem 1rem;
font-size: 1rem; font-size: 1rem;
background: var(--bg);
color: var(--text);
border: 2px solid var(--border); border: 2px solid var(--border);
border-radius: 8px; border-radius: 8px;
outline: none; outline: none;
@ -558,6 +827,10 @@ body {
border-color: var(--primary); border-color: var(--primary);
} }
.modal-content input[type="email"]::placeholder {
color: var(--text-muted);
}
.modal-actions { .modal-actions {
display: flex; display: flex;
gap: 0.8rem; gap: 0.8rem;
@ -576,8 +849,13 @@ body {
font-size: 1.8rem; font-size: 1.8rem;
} }
.logo-icon {
font-size: 2.5rem;
}
.login-box { .login-box {
padding: 1.5rem; padding: 1.5rem;
max-width: 100%;
} }
.chat-header { .chat-header {
@ -589,7 +867,15 @@ body {
display: none; display: none;
} }
.model-badge {
max-width: 120px;
}
.message { .message {
max-width: 92%; max-width: 92%;
} }
.mode-card {
padding: 1rem;
}
} }