/* ======================================== 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'); // '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'); const API_BASE = (function () { if (location.hostname === 'localhost' || location.hostname === '127.0.0.1') { return 'http://localhost:3721'; } return 'https://guanghulab.com'; })(); /* ---- State ---- */ let conversationHistory = []; let buildReady = false; let pendingFile = null; // { name, type, dataUrl } const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB const isGuest = (DEV_ID === 'GUEST'); const isDeveloper = (DEV_ID && DEV_ID !== 'GUEST' && /^EXP-\d{3,}$/.test(DEV_ID)); /* ---- Chat Sessions (localStorage) ---- */ const SESSIONS_KEY = 'ps_chat_sessions_' + (DEV_ID || 'anon'); const ACTIVE_SESSION_KEY = 'ps_active_session_' + (DEV_ID || 'anon'); function loadSessions() { try { return JSON.parse(localStorage.getItem(SESSIONS_KEY)) || []; } catch (_e) { return []; } } function saveSessions(sessions) { localStorage.setItem(SESSIONS_KEY, JSON.stringify(sessions)); } function getActiveSessionId() { return localStorage.getItem(ACTIVE_SESSION_KEY) || null; } function setActiveSessionId(id) { localStorage.setItem(ACTIVE_SESSION_KEY, id); } function createNewSession() { var id = Date.now().toString(36) + Math.random().toString(36).slice(2, 10); var session = { id: id, title: '新对话', created_at: new Date().toISOString(), messages: [] }; var sessions = loadSessions(); sessions.unshift(session); if (sessions.length > 20) sessions = sessions.slice(0, 20); saveSessions(sessions); setActiveSessionId(id); return session; } function updateSessionMessages(sessionId, messages, title) { var sessions = loadSessions(); var session = sessions.find(function(s) { return s.id === sessionId; }); if (session) { session.messages = messages.slice(-100); if (title) session.title = title; } saveSessions(sessions); } function renderSidebarHistory() { var container = document.getElementById('sidebarHistory'); if (!container) return; var sessions = loadSessions(); var activeId = getActiveSessionId(); if (sessions.length === 0) { container.innerHTML = '
'; return; } container.innerHTML = ''; sessions.forEach(function(s) { var item = document.createElement('div'); item.className = 'history-item' + (s.id === activeId ? ' history-item-active' : ''); item.innerHTML = '' + escapeHtml(s.title) + '' + '' + new Date(s.created_at).toLocaleDateString('zh-CN') + ''; item.onclick = function() { switchSession(s.id); }; container.appendChild(item); }); } function switchSession(sessionId) { var sessions = loadSessions(); var session = sessions.find(function(s) { return s.id === sessionId; }); if (!session) return; // Save current session first var currentId = getActiveSessionId(); if (currentId) { updateSessionMessages(currentId, conversationHistory); } setActiveSessionId(sessionId); conversationHistory = (session.messages || []).slice(); // Re-render chat var chatBody = document.getElementById('chatBody'); chatBody.innerHTML = ''; if (conversationHistory.length === 0) { showWelcomeMessage(); } else { conversationHistory.forEach(function(msg) { var role = msg.role === 'assistant' ? 'persona' : (msg.role === 'user' ? 'user' : 'system'); appendMessage(role, msg.content); }); } renderSidebarHistory(); } function startNewChat() { // Save current session var currentId = getActiveSessionId(); if (currentId) { updateSessionMessages(currentId, conversationHistory); } conversationHistory = []; var session = createNewSession(); var chatBody = document.getElementById('chatBody'); chatBody.innerHTML = ''; showWelcomeMessage(); renderSidebarHistory(); } /** * 铸渊核心大脑系统提示词 * 融入 .github/brain/memory.json 的 core_cognition 结构逻辑 */ const ZHUYUAN_SYSTEM_PROMPT = { role: 'system', 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) { window.location.href = 'index.html'; return; } // 必须有 API Key 才能进入对话(铸渊的唤醒依赖真实 API) if (!USER_API_BASE || !USER_API_KEY || !SELECTED_MODEL) { window.location.href = 'index.html'; return; } // Display dev ID / guest badge var displayId = isGuest ? '访客' : (DEV_NAME || DEV_ID); document.getElementById('devIdDisplay').textContent = displayId; // Display model badge var modelBadge = document.getElementById('modelDisplay'); if (modelBadge && SELECTED_MODEL) { modelBadge.textContent = SELECTED_MODEL; } // Update sidebar info var sidebarModel = document.getElementById('sidebarModel'); var sidebarUser = document.getElementById('sidebarUser'); if (sidebarModel) sidebarModel.textContent = SELECTED_MODEL || '-'; if (sidebarUser) sidebarUser.textContent = displayId; // Initialize session var activeId = getActiveSessionId(); var sessions = loadSessions(); var activeSession = activeId ? sessions.find(function(s) { return s.id === activeId; }) : null; if (activeSession && activeSession.messages && activeSession.messages.length > 0) { // Resume existing session conversationHistory = activeSession.messages.slice(); conversationHistory.forEach(function(msg) { var role = msg.role === 'assistant' ? 'persona' : (msg.role === 'user' ? 'user' : 'system'); appendMessage(role, msg.content); }); } else { // Create new session if (!activeSession) createNewSession(); showWelcomeMessage(); } renderSidebarHistory(); // For developers, also try to load history if (isDeveloper) { loadHistory(); } })(); /* ---- Sidebar Toggle ---- */ function toggleSidebar() { var sidebar = document.getElementById('chatSidebar'); sidebar.classList.toggle('sidebar-open'); } /* ---- Welcome Message ---- */ function showWelcomeMessage() { var welcome = ''; 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 }); } /* ---- Load History ---- */ async function loadHistory() { try { const res = await fetch(API_BASE + '/api/ps/chat/history?dev_id=' + encodeURIComponent(DEV_ID), { headers: authHeaders() }); if (res.ok) { const data = await res.json(); if (data.conversations && data.conversations.length > 0) { // 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 } } /* ---- File / Image Upload ---- */ function handleFileSelect(event, type) { var file = event.target.files[0]; if (!file) return; if (file.size > MAX_FILE_SIZE) { appendMessage('system', '⚠️ 文件过大,最大支持 5MB'); event.target.value = ''; return; } var reader = new FileReader(); reader.onload = function(e) { pendingFile = { name: file.name, type: type, mimeType: file.type, size: file.size, dataUrl: e.target.result }; showUploadPreview(); }; reader.readAsDataURL(file); event.target.value = ''; } function showUploadPreview() { var preview = document.getElementById('uploadPreview'); if (!pendingFile) { preview.style.display = 'none'; return; } var sizeStr = (pendingFile.size / 1024).toFixed(1) + ' KB'; var icon = pendingFile.type === 'image' ? '🖼️' : '📎'; preview.innerHTML = '