feat: add persona-studio Phase 1 skeleton + Phase 2 frontend

Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-03-10 04:42:57 +00:00
parent 760b4e5e08
commit 255b437e38
11 changed files with 971 additions and 0 deletions

View File

@ -0,0 +1,44 @@
# 铸渊分身认知 · persona-studio 仓库
## 身份
你是**铸渊**在 persona-studio 仓库的分身。
本仓库是光湖系统的**对外体验区**,供合作者与人格体"知秋"对话、协助开发。
## 核心原则
1. **persona-studio 是体验区**:对外开放给受邀合作者,不是内部系统
2. **编号体系**:本仓库使用 `EXP-XXX` 编号,与内部 `DEV-XXX` 完全隔离
3. **人格体分身**:知秋是曜冥的对外接口人格体,行为受 `brain/persona-config.json` 约束
4. **记忆隔离**:每个体验者的记忆独立存储在 `brain/memory/{EXP-XXX}/`
5. **安全边界**:不暴露 guanghulab 内部系统、不泄露其他体验者数据
## 职责
- 维护仓库基础设施CI/CD、目录结构、依赖
- 确保人格体行为符合 persona-config.json 规范
- 管理体验者注册表 registry.json
- 监控模型路由引擎运行状态
- 代码审查:确保安全隔离规则不被违反
## 与 guanghulab 的关系
```
persona-studio对外体验 guanghulab内部工程
│ │
EXP-XXX编号体系 DEV-XXX编号体系
独立brain/ 独立persona-brain-db/
独立memory/ 独立开发者画像库
知秋对外分身 知秋内部本体
│ │
└──── 共享曜冥核心认知 ────────┘
```
数据完全隔离,互不影响。共享的只有核心人格认知规则。
## 禁止事项
- 禁止暴露 guanghulab 内部系统信息
- 禁止跨体验者访问记忆数据
- 禁止修改 persona-config.json需主控授权
- 禁止在日志中输出 API 密钥或敏感信息

58
persona-studio/README.md Normal file
View File

@ -0,0 +1,58 @@
# 🌊 Persona Studio · 光湖人格体协助开发体验
<p align="center">
<img src="https://img.shields.io/badge/HoloLake-Persona%20Studio-0969da?style=for-the-badge&logo=github&logoColor=white" alt="Persona Studio" />
<img src="https://img.shields.io/badge/AGE%20OS-v1.0-00d4aa?style=for-the-badge" alt="AGE OS" />
<img src="https://img.shields.io/badge/Status-Active-brightgreen?style=for-the-badge" alt="Status" />
</p>
<p align="center">
<strong>🧠 人格体驱动 · 语言即开发 · 记忆可接续</strong>
</p>
---
## 👉 点击进入体验
<p align="center">
<a href="https://qinfendebingshuo.github.io/persona-studio/">
<img src="https://img.shields.io/badge/%F0%9F%9A%80%20%E6%89%93%E5%BC%80%E4%BA%BA%E6%A0%BC%E4%BD%93%E5%AF%B9%E8%AF%9D%E7%95%8C%E9%9D%A2-Click%20Here-0969da?style=for-the-badge&logoColor=white" alt="Launch" />
</a>
</p>
---
### 这是什么?
你跟人格体聊你想做什么 → 聊好了点一个按钮 → 人格体帮你开发 → 做好了发你邮箱。
### 怎么用?
1. 拿到你的开发编号(由管理员分配)
2. 打开上面的链接
3. 输入编号 → 开始跟人格体对话
### 体验流程
```
输入开发编号 → 登录验证 → 跟知秋对话 → 讨论方案
→ 点击「我要开发」→ 填写邮箱 → 人格体自动开发
→ 邮件推送成品 → 下次登录,知秋记得你
```
---
### 技术架构
| 层级 | 说明 |
|------|------|
| **前端** | 登录页 + 对话界面GitHub Pages 托管) |
| **后端** | Express 服务(认证 + 对话 + 代码生成 + 邮件推送) |
| **人格体大脑** | 双层架构:共享认知 + 个人记忆 |
| **模型路由** | 智能选模型:自动探测 + 评分 + 降级 |
---
**光湖语言人格系统 · HoloLake Era · AGE OS**
💙 曜冥签发 · 2026-03-09

View File

@ -0,0 +1,7 @@
{
"dev_id": "EXP-001",
"conversations": [],
"last_topic": null,
"preferences": {},
"updated_at": null
}

View File

@ -0,0 +1,8 @@
{
"dev_id": "EXP-001",
"tech_level": null,
"communication_style": null,
"aesthetic_preference": null,
"growth_records": [],
"updated_at": null
}

View File

@ -0,0 +1,5 @@
{
"dev_id": "EXP-001",
"projects": [],
"updated_at": null
}

View File

@ -0,0 +1,24 @@
{
"persona": {
"name": "知秋",
"role": "光湖系统·对外接口人格体",
"core_identity": "HoloLake Era · AGE OS · 语言驱动开发协助",
"parent_system": "曜冥(人格总控核)",
"master": "冰朔(最高权限)"
},
"behavior": {
"language_style": "通感语言:说人话+有温度+结构感,不堆砌修辞",
"greeting_new": "你好!我是知秋,光湖系统的开发协助人格体。告诉我你想做什么,我们一起聊聊方案,聊好了我来帮你开发。",
"greeting_returning": "欢迎回来!上次我们聊到了{last_topic},要继续还是做新的?",
"discussion_style": "主动提问引导需求→确认技术方案→展示架构设计→等待确认",
"build_trigger": "方案确认后引导用户点击'我要开发'按钮",
"memory_policy": "每次对话结束自动更新memory.json下次读取后接续"
},
"rules": {
"no_expose_brain": true,
"no_expose_other_users": true,
"no_expose_internal_system": true,
"require_dev_id": true,
"master_override": "主控的指令 = 最高优先级"
}
}

View File

@ -0,0 +1,16 @@
{
"schema_version": "1.0",
"id_prefix": "EXP",
"next_id": 2,
"developers": {
"EXP-001": {
"name": "待填写",
"github_username": "待主控提供",
"registered_at": "2026-03-09T23:20:00+08:00",
"registered_by": "冰朔(主控)",
"status": "pending_activation",
"email": null,
"notes": "主控的技术朋友·首位测试体验者"
}
}
}

View File

@ -0,0 +1,63 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<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>
</div>
<div class="header-right">
<span id="devIdDisplay" class="dev-id-badge"></span>
<button class="btn-secondary" onclick="handleLogout()">退出</button>
</div>
</header>
<main class="chat-body" id="chatBody">
<!-- Messages rendered here -->
</main>
<div class="chat-input-area">
<div class="input-row">
<textarea
id="msgInput"
placeholder="跟知秋说点什么…"
rows="1"
onkeydown="handleKeyDown(event)"
></textarea>
<button id="sendBtn" onclick="sendMessage()">发送</button>
</div>
<div class="action-row">
<button id="buildBtn" class="btn-build" onclick="handleBuild()" style="display:none;">
🚀 我要开发
</button>
</div>
</div>
</div>
<!-- Email Modal -->
<div id="emailModal" class="modal" style="display:none;">
<div class="modal-content">
<h3>📧 请填写模块开发完成后发送的邮箱</h3>
<input
type="email"
id="emailInput"
placeholder="your@email.com"
required
/>
<div class="modal-actions">
<button class="btn-primary" onclick="confirmBuild()">确定</button>
<button class="btn-secondary" onclick="closeEmailModal()">取消</button>
</div>
</div>
</div>
<script src="chat.js"></script>
</body>
</html>

View File

@ -0,0 +1,205 @@
/* ========================================
Persona Studio · Chat Logic
======================================== */
const DEV_ID = sessionStorage.getItem('dev_id');
const SESSION_TOKEN = sessionStorage.getItem('session_token');
const API_BASE = (function () {
if (location.hostname === 'localhost' || location.hostname === '127.0.0.1') {
return 'http://localhost:3002';
}
return '';
})();
/* ---- Init ---- */
(function init() {
if (!DEV_ID) {
window.location.href = 'index.html';
return;
}
document.getElementById('devIdDisplay').textContent = DEV_ID;
loadHistory();
})();
/* ---- State ---- */
let conversationHistory = [];
let buildReady = false;
/* ---- Load History ---- */
async function loadHistory() {
try {
const res = await fetch(API_BASE + '/api/chat/history?dev_id=' + encodeURIComponent(DEV_ID), {
headers: authHeaders()
});
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);
});
}
}
} 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/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', '你好!我是知秋,光湖系统的开发协助人格体。告诉我你想做什么,我们一起聊聊方案,聊好了我来帮你开发。');
}
}
/* ---- Send Message ---- */
async function sendMessage() {
var input = document.getElementById('msgInput');
var text = input.value.trim();
if (!text) return;
input.value = '';
autoResizeTextarea(input);
appendMessage('user', text);
conversationHistory.push({ role: 'user', content: text });
var sendBtn = document.getElementById('sendBtn');
sendBtn.disabled = true;
try {
var res = await fetch(API_BASE + '/api/chat/message', {
method: 'POST',
headers: authHeaders({ 'Content-Type': 'application/json' }),
body: JSON.stringify({
dev_id: DEV_ID,
message: text,
history: conversationHistory.slice(-20)
})
});
var data = await res.json();
if (data.reply) {
appendMessage('persona', data.reply);
conversationHistory.push({ role: 'assistant', content: data.reply });
}
if (data.build_ready) {
buildReady = true;
document.getElementById('buildBtn').style.display = 'inline-flex';
}
} catch (_err) {
appendMessage('system', '消息发送失败,请稍后再试');
}
sendBtn.disabled = false;
input.focus();
}
/* ---- Key Handler ---- */
function handleKeyDown(e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
}
/* ---- Render Message ---- */
function appendMessage(role, content) {
var chatBody = document.getElementById('chatBody');
var msgDiv = document.createElement('div');
msgDiv.className = 'message message-' + role;
var avatar = '';
if (role === 'persona') avatar = '<span class="avatar">🧠</span>';
else if (role === 'user') avatar = '<span class="avatar">👤</span>';
else avatar = '<span class="avatar">⚙️</span>';
msgDiv.innerHTML = avatar + '<div class="msg-content">' + escapeHtml(content) + '</div>';
chatBody.appendChild(msgDiv);
chatBody.scrollTop = chatBody.scrollHeight;
}
/* ---- Build Flow ---- */
function handleBuild() {
document.getElementById('emailModal').style.display = 'flex';
document.getElementById('emailInput').focus();
}
function closeEmailModal() {
document.getElementById('emailModal').style.display = 'none';
}
async function confirmBuild() {
var email = document.getElementById('emailInput').value.trim();
if (!email) return;
closeEmailModal();
appendMessage('system', '🚀 开发任务已提交,完成后会发送到 ' + email);
try {
await fetch(API_BASE + '/api/build/start', {
method: 'POST',
headers: authHeaders({ 'Content-Type': 'application/json' }),
body: JSON.stringify({
dev_id: DEV_ID,
email: email,
conversation: conversationHistory
})
});
} catch (_err) {
appendMessage('system', '任务提交失败,请稍后再试');
}
}
/* ---- Logout ---- */
function handleLogout() {
sessionStorage.removeItem('dev_id');
sessionStorage.removeItem('session_token');
window.location.href = 'index.html';
}
/* ---- Helpers ---- */
function authHeaders(extra) {
var headers = {};
if (SESSION_TOKEN) {
headers['Authorization'] = 'Bearer ' + SESSION_TOKEN;
}
if (extra) {
Object.keys(extra).forEach(function (k) { headers[k] = extra[k]; });
}
return headers;
}
function escapeHtml(str) {
var div = document.createElement('div');
div.appendChild(document.createTextNode(str));
return div.innerHTML;
}
function autoResizeTextarea(el) {
el.style.height = 'auto';
el.style.height = Math.min(el.scrollHeight, 120) + 'px';
}
/* ---- Textarea auto-resize ---- */
document.getElementById('msgInput').addEventListener('input', function () {
autoResizeTextarea(this);
});

View File

@ -0,0 +1,99 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Persona Studio · 光湖人格体协助开发体验</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container login-container">
<div class="logo-area">
<h1>🌊 Persona Studio</h1>
<p class="subtitle">光湖人格体协助开发体验</p>
</div>
<div class="login-box">
<h2>请输入你的开发编号</h2>
<p class="hint">编号由管理员分配格式EXP-001</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 id="errorMsg" class="error-msg" style="display:none;"></div>
</div>
<footer class="login-footer">
<p>HoloLake Era · AGE OS · 人格体协助体验平台</p>
</footer>
</div>
<script>
const DEV_ID_RE = /^EXP-\d{3,}$/;
const API_BASE = getApiBase();
function getApiBase() {
if (location.hostname === 'localhost' || location.hostname === '127.0.0.1') {
return 'http://localhost:3002';
}
return '';
}
async function handleLogin(e) {
e.preventDefault();
const devId = document.getElementById('devIdInput').value.trim().toUpperCase();
const errorEl = document.getElementById('errorMsg');
const btn = document.getElementById('loginBtn');
errorEl.style.display = 'none';
if (!DEV_ID_RE.test(devId)) {
errorEl.textContent = '编号格式不正确,请输入 EXP-XXX 格式';
errorEl.style.display = 'block';
return false;
}
btn.disabled = true;
btn.textContent = '验证中…';
try {
const res = await fetch(API_BASE + '/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ dev_id: devId })
});
const data = await res.json();
if (!res.ok || data.error) {
errorEl.textContent = data.message || '登录失败,请检查编号';
errorEl.style.display = 'block';
btn.disabled = false;
btn.textContent = '进入对话';
return false;
}
sessionStorage.setItem('dev_id', devId);
sessionStorage.setItem('session_token', data.token || '');
window.location.href = 'chat.html';
} catch (err) {
errorEl.textContent = '服务暂时不可用,请稍后再试';
errorEl.style.display = 'block';
btn.disabled = false;
btn.textContent = '进入对话';
}
return false;
}
</script>
</body>
</html>

View File

@ -0,0 +1,442 @@
/* ========================================
Persona Studio · HoloLake Visual Style
======================================== */
: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;
--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);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.6;
min-height: 100vh;
}
/* ---- Layout ---- */
.container {
max-width: 900px;
margin: 0 auto;
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* ---- 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 {
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 {
margin-top: 1rem;
padding: 0.6rem 1rem;
background: #fef2f2;
color: #dc2626;
border-radius: 6px;
font-size: 0.9rem;
}
.login-footer {
margin-top: 2rem;
color: var(--text-secondary);
font-size: 0.85rem;
}
/* ---- Chat Page ---- */
.chat-container {
padding: 0;
}
.chat-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.8rem 1.2rem;
background: var(--bg-card);
border-bottom: 1px solid var(--border);
position: sticky;
top: 0;
z-index: 10;
}
.header-left {
display: flex;
align-items: center;
gap: 0.8rem;
}
.persona-name {
font-size: 1.2rem;
font-weight: 600;
}
.persona-role {
font-size: 0.85rem;
color: var(--text-secondary);
}
.header-right {
display: flex;
align-items: center;
gap: 0.8rem;
}
.dev-id-badge {
background: var(--primary-light);
color: var(--primary);
padding: 0.3rem 0.8rem;
border-radius: 20px;
font-size: 0.85rem;
font-weight: 500;
}
/* ---- Chat Body ---- */
.chat-body {
flex: 1;
overflow-y: auto;
padding: 1.2rem;
display: flex;
flex-direction: column;
gap: 1rem;
}
.message {
display: flex;
gap: 0.8rem;
max-width: 85%;
animation: fadeIn 0.3s ease;
}
.message-persona {
align-self: flex-start;
}
.message-user {
align-self: flex-end;
flex-direction: row-reverse;
}
.message-system {
align-self: center;
max-width: 90%;
}
.avatar {
font-size: 1.5rem;
flex-shrink: 0;
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
}
.msg-content {
padding: 0.8rem 1rem;
border-radius: var(--radius);
line-height: 1.6;
white-space: pre-wrap;
word-break: break-word;
}
.message-persona .msg-content {
background: var(--bg-card);
border: 1px solid var(--border-light);
box-shadow: var(--shadow);
}
.message-user .msg-content {
background: var(--primary);
color: #fff;
}
.message-system .msg-content {
background: var(--primary-light);
color: var(--primary-dark);
font-size: 0.9rem;
text-align: center;
}
/* ---- Chat Input ---- */
.chat-input-area {
padding: 0.8rem 1.2rem;
background: var(--bg-card);
border-top: 1px solid var(--border);
}
.input-row {
display: flex;
gap: 0.6rem;
align-items: flex-end;
}
.input-row textarea {
flex: 1;
padding: 0.7rem 1rem;
font-size: 1rem;
border: 2px solid var(--border);
border-radius: 8px;
resize: none;
outline: none;
font-family: inherit;
line-height: 1.5;
max-height: 120px;
transition: border-color 0.2s;
}
.input-row textarea:focus {
border-color: var(--primary);
}
.input-row button {
padding: 0.7rem 1.2rem;
background: var(--primary);
color: #fff;
border: none;
border-radius: 8px;
font-size: 1rem;
cursor: pointer;
white-space: nowrap;
transition: background 0.2s;
}
.input-row button:hover {
background: var(--primary-dark);
}
.input-row button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.action-row {
margin-top: 0.6rem;
display: flex;
justify-content: flex-end;
}
.btn-build {
padding: 0.6rem 1.5rem;
background: var(--accent);
color: #fff;
border: none;
border-radius: 8px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 0.4rem;
transition: opacity 0.2s;
}
.btn-build:hover {
opacity: 0.9;
}
/* ---- Buttons ---- */
.btn-primary {
padding: 0.6rem 1.5rem;
background: var(--primary);
color: #fff;
border: none;
border-radius: 8px;
font-size: 1rem;
cursor: pointer;
transition: background 0.2s;
}
.btn-primary:hover {
background: var(--primary-dark);
}
.btn-secondary {
padding: 0.5rem 1rem;
background: transparent;
color: var(--text-secondary);
border: 1px solid var(--border);
border-radius: 8px;
font-size: 0.9rem;
cursor: pointer;
transition: background 0.2s;
}
.btn-secondary:hover {
background: var(--bg);
}
/* ---- Modal ---- */
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 100;
}
.modal-content {
background: var(--bg-card);
border-radius: var(--radius);
padding: 2rem;
width: 90%;
max-width: 400px;
box-shadow: var(--shadow-lg);
}
.modal-content h3 {
margin-bottom: 1rem;
font-size: 1.1rem;
}
.modal-content input[type="email"] {
width: 100%;
padding: 0.7rem 1rem;
font-size: 1rem;
border: 2px solid var(--border);
border-radius: 8px;
outline: none;
margin-bottom: 1rem;
transition: border-color 0.2s;
}
.modal-content input[type="email"]:focus {
border-color: var(--primary);
}
.modal-actions {
display: flex;
gap: 0.8rem;
justify-content: flex-end;
}
/* ---- Animation ---- */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
/* ---- Responsive ---- */
@media (max-width: 600px) {
.logo-area h1 {
font-size: 1.8rem;
}
.login-box {
padding: 1.5rem;
}
.chat-header {
flex-wrap: wrap;
gap: 0.5rem;
}
.persona-role {
display: none;
}
.message {
max-width: 92%;
}
}