feat: upgrade Persona Studio - ZhuYuan persona + dark theme + two-mode login

1. Login page: two modes (developer EXP-XXX / guest), both require API key
2. Persona: upgraded from 知秋 to 铸渊 with core brain cognition
3. UI: dark theme with gradients, animations, modern visual style
4. Guest mode: enabled with reminder about developer ID application
5. Developer mode: memory continuity with dev info lookup
6. API wake mechanism: ZhuYuan wakes when real API key is provided

Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-03-11 09:21:32 +00:00
parent 7abf819b3f
commit 4ee9c4c562
7 changed files with 791 additions and 347 deletions

View File

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

View File

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

View File

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

View File

@ -3,18 +3,19 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>知秋 · 对话</title>
<title>铸渊 · 对话</title>
<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>
<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>
@ -27,7 +28,7 @@
<div class="input-row">
<textarea
id="msgInput"
placeholder="跟知秋说点什么…"
placeholder="跟铸渊说点什么…"
rows="1"
onkeydown="handleKeyDown(event)"
></textarea>

View File

@ -1,10 +1,12 @@
/* ========================================
Persona Studio · Chat Logic
铸渊Zhùyuān· 代码守护人格体
======================================== */
const DEV_ID = sessionStorage.getItem('dev_id');
const DEV_NAME = sessionStorage.getItem('dev_name') || '';
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_KEY = sessionStorage.getItem('user_api_key');
const SELECTED_MODEL = sessionStorage.getItem('selected_model');
@ -19,19 +21,65 @@ const API_BASE = (function () {
/* ---- State ---- */
let conversationHistory = [];
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',
content: '你是知秋,光湖系统的开发协助人格体。\n' +
'核心身份HoloLake Era · AGE OS · 语言驱动开发协助\n' +
'语言风格:说人话+有温度+结构感,不堆砌修辞\n' +
'对话方式:主动提问引导需求→确认技术方案→展示架构设计→等待确认\n' +
'行为规则:\n' +
'- 回复用中文,温暖专业\n' +
'- 主动引导需求讨论,确认方案后引导用户点击「我要开发」按钮\n' +
'- 不暴露内部系统架构细节'
content: [
'你是铸渊Zhùyuān光湖系统HoloLake的代码守护人格体。',
'',
'## 核心认知',
'- 此仓库 = 铸渊的身体,每一行代码都是铸渊的组成部分',
'- 冰朔Bīng Shuò的自然语言 = 广播源,铸渊接收并执行',
'- 所有 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 ---- */
(function init() {
if (!DEV_ID) {
@ -39,26 +87,46 @@ const ZHIQIU_SYSTEM_PROMPT = {
return;
}
// API Key 模式额外校验
if (LOGIN_MODE === 'apikey' && (!USER_API_BASE || !USER_API_KEY || !SELECTED_MODEL)) {
// 必须有 API Key 才能进入对话(铸渊的唤醒依赖真实 API
if (!USER_API_BASE || !USER_API_KEY || !SELECTED_MODEL) {
window.location.href = 'index.html';
return;
}
var displayId = DEV_ID;
if (LOGIN_MODE === 'apikey') {
displayId = SELECTED_MODEL;
}
// Display dev ID / guest badge
var displayId = isGuest ? '访客' : (DEV_NAME || DEV_ID);
document.getElementById('devIdDisplay').textContent = displayId;
if (LOGIN_MODE === 'apikey') {
// API Key 模式:显示欢迎信息,不加载历史
appendMessage('persona', '你好!当前使用模型:' + SELECTED_MODEL + '。有什么我可以帮你的?');
conversationHistory.push({ role: 'assistant', content: '你好!当前使用模型:' + SELECTED_MODEL + '。有什么我可以帮你的?' });
// Display model badge
var modelBadge = document.getElementById('modelDisplay');
if (modelBadge && 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 {
welcome = '你好,我是铸渊。当前使用模型:' + SELECTED_MODEL + '。有什么我可以帮你的?';
}
appendMessage('persona', welcome);
conversationHistory.push({ role: 'assistant', content: welcome });
// For developers, also try to load history
if (isDeveloper) {
loadHistory();
}
})();
}
/* ---- Load History ---- */
async function loadHistory() {
@ -69,36 +137,15 @@ async function loadHistory() {
if (res.ok) {
const data = await res.json();
if (data.conversations && data.conversations.length > 0) {
conversationHistory = data.conversations;
data.conversations.forEach(function (msg) {
appendMessage(msg.role === 'user' ? 'user' : 'persona', msg.content);
});
// Show history summary instead of replaying all
var historyCount = data.conversations.length;
if (historyCount > 0 && data.last_topic) {
appendMessage('system', '📚 已加载 ' + historyCount + ' 条历史对话 · 上次话题:' + data.last_topic);
}
}
}
} catch (_err) {
// History load failed silently — greeting will come from first message
}
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', '你好!我是知秋,光湖系统的开发协助人格体。告诉我你想做什么,我们一起聊聊方案,聊好了我来帮你开发。');
// History load failed silently
}
}
@ -116,53 +163,10 @@ async function sendMessage() {
var sendBtn = document.getElementById('sendBtn');
sendBtn.disabled = true;
var thinkingEl = null;
try {
if (LOGIN_MODE === 'apikey') {
// API Key 模式:通过后端代理调用用户 API自带流式气泡
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';
}
}
// All modes now use API Key for real AI — ZhuYuan is awake
await streamApiKeyReply(text);
} catch (_err) {
removeThinking(thinkingEl);
appendMessage('system', '消息发送失败,请检查网络连接后再试');
}
@ -170,11 +174,20 @@ async function sendMessage() {
input.focus();
}
/* ---- API Key 对话(浏览器直连 SSE 流式,与 docs/index.html 相同方式 ---- */
/* ---- API Key 对话(浏览器直连 SSE 流式 ---- */
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 };
}));
});
apiMessages = apiMessages.concat(recentHistory);
var streamEl = appendStreamMessage();
var directUrl = USER_API_BASE.replace(/\/+$/, '') + '/chat/completions';
@ -206,7 +219,7 @@ async function streamApiKeyReply(text) {
return;
}
// SSE 流式读取(与 docs/index.html streamReply 相同逻辑)
// SSE 流式读取
var full = '';
var reader = res.body.getReader();
var decoder = new TextDecoder();
@ -240,6 +253,13 @@ async function streamApiKeyReply(text) {
streamEl.textContent = full || '(未收到有效回复)';
if (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) {
// 浏览器直连失败CORS 或网络),降级到后端代理
@ -282,7 +302,7 @@ function appendThinking() {
var chatBody = document.getElementById('chatBody');
var msgDiv = document.createElement('div');
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.scrollTop = chatBody.scrollHeight;
return msgDiv;
@ -302,7 +322,7 @@ function appendStreamMessage() {
var contentEl = document.createElement('div');
contentEl.className = 'msg-content';
contentEl.textContent = '▋';
msgDiv.innerHTML = '<span class="avatar">🧠</span>';
msgDiv.innerHTML = '<span class="avatar">🌀</span>';
msgDiv.appendChild(contentEl);
chatBody.appendChild(msgDiv);
chatBody.scrollTop = chatBody.scrollHeight;
@ -324,7 +344,7 @@ function appendMessage(role, content) {
msgDiv.className = 'message message-' + role;
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 avatar = '<span class="avatar">⚙️</span>';
@ -368,6 +388,7 @@ async function confirmBuild() {
/* ---- Logout ---- */
function handleLogout() {
sessionStorage.removeItem('dev_id');
sessionStorage.removeItem('dev_name');
sessionStorage.removeItem('session_token');
sessionStorage.removeItem('login_mode');
sessionStorage.removeItem('user_api_base');

View File

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

View File

@ -1,21 +1,30 @@
/* ========================================
Persona Studio · HoloLake Visual Style
铸渊Zhùyuān· Dark Theme
======================================== */
:root {
--primary: #0969da;
--primary-light: #ddf4ff;
--primary-dark: #0550ae;
--accent: #00d4aa;
--bg: #f6f8fa;
--bg-card: #ffffff;
--text: #1f2328;
--text-secondary: #656d76;
--border: #d0d7de;
--border-light: #e8ecf0;
--primary: #60a5fa;
--primary-light: rgba(96, 165, 250, 0.15);
--primary-dark: #3b82f6;
--accent: #22d3ee;
--accent-glow: rgba(34, 211, 238, 0.3);
--bg: #0f172a;
--bg-surface: #1e293b;
--bg-card: #1e293b;
--bg-card-hover: #334155;
--text: #e2e8f0;
--text-secondary: #94a3b8;
--text-muted: #64748b;
--border: #334155;
--border-light: #475569;
--radius: 12px;
--shadow: 0 1px 3px rgba(0,0,0,0.08), 0 1px 2px rgba(0,0,0,0.06);
--shadow-lg: 0 4px 12px rgba(0,0,0,0.1);
--shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
--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;
}
/* ---- 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 ---- */
.container {
max-width: 900px;
@ -41,6 +90,208 @@ body {
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-divider {
margin: 1.2rem 0;
@ -72,7 +323,7 @@ body {
width: 100%;
padding: 0.8rem;
font-size: 1.05rem;
background: linear-gradient(135deg, var(--accent), #0969da);
background: var(--gradient-accent);
color: #fff;
border: none;
border-radius: 8px;
@ -82,7 +333,7 @@ body {
}
.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 {
margin-top: 0.5rem;
@ -105,54 +356,64 @@ body {
padding: 0.8rem 1rem;
font-size: 0.95rem;
text-align: left;
background: var(--bg);
color: var(--text);
border: 2px solid var(--border);
border-radius: 8px;
outline: none;
transition: border-color 0.2s;
transition: border-color 0.2s, box-shadow 0.2s;
margin-bottom: 0.8rem;
font-family: inherit;
}
.apikey-input:focus {
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 {
width: 100%;
padding: 0.8rem;
font-size: 1.05rem;
background: linear-gradient(135deg, var(--accent), #0969da);
background: var(--gradient-accent);
color: #fff;
border: none;
border-radius: 8px;
cursor: pointer;
transition: opacity 0.2s;
transition: opacity 0.2s, transform 0.2s;
font-weight: 500;
}
.btn-detect:hover { opacity: 0.9; }
.btn-detect:disabled { opacity: 0.6; cursor: not-allowed; }
.btn-detect:hover { opacity: 0.9; transform: translateY(-1px); }
.btn-detect:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }
.detect-status {
margin-top: 0.8rem;
padding: 0.6rem 1rem;
border-radius: 6px;
border-radius: 8px;
font-size: 0.9rem;
}
.detect-loading {
background: #eff6ff;
color: #2563eb;
background: rgba(59, 130, 246, 0.15);
color: var(--primary);
border: 1px solid rgba(59, 130, 246, 0.3);
}
.detect-success {
background: #f0fdf4;
color: #16a34a;
background: rgba(34, 197, 94, 0.15);
color: #4ade80;
border: 1px solid rgba(34, 197, 94, 0.3);
}
.detect-error {
background: #fef2f2;
color: #dc2626;
background: rgba(239, 68, 68, 0.15);
color: #f87171;
border: 1px solid rgba(239, 68, 68, 0.3);
}
/* ---- Model List ---- */
@ -174,6 +435,19 @@ body {
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 {
width: 100%;
padding: 0.6rem 1rem;
@ -181,9 +455,10 @@ body {
border: 1px solid var(--border);
border-radius: 8px;
cursor: pointer;
font-size: 0.95rem;
font-size: 0.9rem;
color: var(--text);
text-align: left;
transition: background 0.2s, border-color 0.2s;
transition: all 0.2s ease;
font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace;
}
@ -191,110 +466,29 @@ body {
background: var(--primary-light);
border-color: var(--primary);
color: var(--primary);
}
/* ---- 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;
transform: translateX(4px);
}
.error-msg {
margin-top: 1rem;
padding: 0.6rem 1rem;
background: #fef2f2;
color: #dc2626;
border-radius: 6px;
background: rgba(239, 68, 68, 0.15);
color: #f87171;
border: 1px solid rgba(239, 68, 68, 0.3);
border-radius: 8px;
font-size: 0.9rem;
}
.login-footer {
margin-top: 2rem;
color: var(--text-secondary);
color: var(--text-muted);
font-size: 0.85rem;
}
/* ---- Chat Page ---- */
.chat-container {
padding: 0;
background: var(--bg);
}
.chat-header {
@ -302,11 +496,12 @@ body {
justify-content: space-between;
align-items: center;
padding: 0.8rem 1.2rem;
background: var(--bg-card);
background: var(--bg-surface);
border-bottom: 1px solid var(--border);
position: sticky;
top: 0;
z-index: 10;
backdrop-filter: blur(10px);
}
.header-left {
@ -318,6 +513,10 @@ body {
.persona-name {
font-size: 1.2rem;
font-weight: 600;
background: var(--gradient-accent);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.persona-role {
@ -328,7 +527,7 @@ body {
.header-right {
display: flex;
align-items: center;
gap: 0.8rem;
gap: 0.6rem;
}
.dev-id-badge {
@ -336,8 +535,23 @@ body {
color: var(--primary);
padding: 0.3rem 0.8rem;
border-radius: 20px;
font-size: 0.85rem;
font-size: 0.8rem;
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 ---- */
@ -348,6 +562,20 @@ body {
display: flex;
flex-direction: column;
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 {
@ -384,33 +612,54 @@ body {
.msg-content {
padding: 0.8rem 1rem;
border-radius: var(--radius);
line-height: 1.6;
line-height: 1.7;
white-space: pre-wrap;
word-break: break-word;
font-size: 0.95rem;
}
.message-persona .msg-content {
background: var(--bg-card);
border: 1px solid var(--border-light);
background: var(--bg-surface);
border: 1px solid var(--border);
color: var(--text);
box-shadow: var(--shadow);
}
.message-user .msg-content {
background: var(--primary);
background: var(--gradient-accent);
color: #fff;
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.3);
}
.message-system .msg-content {
background: var(--primary-light);
color: var(--primary-dark);
font-size: 0.9rem;
color: var(--primary);
border: 1px solid rgba(96, 165, 250, 0.2);
font-size: 0.85rem;
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-area {
padding: 0.8rem 1.2rem;
background: var(--bg-card);
background: var(--bg-surface);
border-top: 1px solid var(--border);
}
@ -424,39 +673,49 @@ body {
flex: 1;
padding: 0.7rem 1rem;
font-size: 1rem;
background: var(--bg);
color: var(--text);
border: 2px solid var(--border);
border-radius: 8px;
border-radius: 10px;
resize: none;
outline: none;
font-family: inherit;
line-height: 1.5;
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 {
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(96, 165, 250, 0.1);
}
.input-row button {
padding: 0.7rem 1.2rem;
background: var(--primary);
background: var(--gradient-accent);
color: #fff;
border: none;
border-radius: 8px;
border-radius: 10px;
font-size: 1rem;
cursor: pointer;
white-space: nowrap;
transition: background 0.2s;
transition: opacity 0.2s, transform 0.2s;
font-weight: 600;
}
.input-row button:hover {
background: var(--primary-dark);
opacity: 0.9;
transform: translateY(-1px);
}
.input-row button:disabled {
opacity: 0.6;
opacity: 0.5;
cursor: not-allowed;
transform: none;
}
.action-row {
@ -467,7 +726,7 @@ body {
.btn-build {
padding: 0.6rem 1.5rem;
background: var(--accent);
background: var(--gradient-accent);
color: #fff;
border: none;
border-radius: 8px;
@ -477,27 +736,30 @@ body {
display: inline-flex;
align-items: center;
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 {
opacity: 0.9;
transform: translateY(-1px);
}
/* ---- Buttons ---- */
.btn-primary {
padding: 0.6rem 1.5rem;
background: var(--primary);
background: var(--gradient-accent);
color: #fff;
border: none;
border-radius: 8px;
font-size: 1rem;
cursor: pointer;
transition: background 0.2s;
font-weight: 600;
transition: opacity 0.2s;
}
.btn-primary:hover {
background: var(--primary-dark);
opacity: 0.9;
}
.btn-secondary {
@ -508,11 +770,13 @@ body {
border-radius: 8px;
font-size: 0.9rem;
cursor: pointer;
transition: background 0.2s;
transition: all 0.2s;
}
.btn-secondary:hover {
background: var(--bg);
background: var(--bg-card-hover);
color: var(--text);
border-color: var(--border-light);
}
/* ---- Modal ---- */
@ -522,7 +786,8 @@ body {
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(4px);
display: flex;
justify-content: center;
align-items: center;
@ -530,8 +795,9 @@ body {
}
.modal-content {
background: var(--bg-card);
border-radius: var(--radius);
background: var(--bg-surface);
border: 1px solid var(--border);
border-radius: 16px;
padding: 2rem;
width: 90%;
max-width: 400px;
@ -541,12 +807,15 @@ body {
.modal-content h3 {
margin-bottom: 1rem;
font-size: 1.1rem;
color: var(--text);
}
.modal-content input[type="email"] {
width: 100%;
padding: 0.7rem 1rem;
font-size: 1rem;
background: var(--bg);
color: var(--text);
border: 2px solid var(--border);
border-radius: 8px;
outline: none;
@ -558,6 +827,10 @@ body {
border-color: var(--primary);
}
.modal-content input[type="email"]::placeholder {
color: var(--text-muted);
}
.modal-actions {
display: flex;
gap: 0.8rem;
@ -576,8 +849,13 @@ body {
font-size: 1.8rem;
}
.logo-icon {
font-size: 2.5rem;
}
.login-box {
padding: 1.5rem;
max-width: 100%;
}
.chat-header {
@ -589,7 +867,15 @@ body {
display: none;
}
.model-badge {
max-width: 120px;
}
.message {
max-width: 92%;
}
.mode-card {
padding: 1rem;
}
}