fix: make Persona Studio accessible via GitHub Pages and update broken links

- Copy persona-studio frontend to docs/persona-studio/ for GitHub Pages deployment
- Update API base URL to point to guanghulab.com production server
- Update all README links from broken guanghulab.com/persona-studio/ to GitHub Pages URL
- Update deploy-pages.yml to trigger on persona-studio/frontend changes and sync files
- Update persona-studio/README.md link to point to guanghulab repo Pages

Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-03-10 07:50:53 +00:00
parent d2f1f26496
commit f970188776
9 changed files with 912 additions and 6 deletions

View File

@ -6,6 +6,7 @@ on:
- main # 仅 main 分支触发生产部署
paths:
- 'docs/**'
- 'persona-studio/frontend/**'
- '.github/brain/**'
workflow_dispatch:
@ -31,6 +32,11 @@ jobs:
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Sync Persona Studio frontend to docs
run: |
mkdir -p docs/persona-studio
cp -r persona-studio/frontend/* docs/persona-studio/
- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v3
with:

View File

@ -31,7 +31,7 @@
<br>
### 🚀 [点击进入 Persona Studio →](https://guanghulab.com/persona-studio/)
### 🚀 [点击进入 Persona Studio →](https://qinfendebingshuo.github.io/guanghulab/persona-studio/)
💬 自然语言对话 &nbsp;·&nbsp; 🧠 智能代码生成 &nbsp;·&nbsp; 📧 邮件推送结果 &nbsp;·&nbsp; 🔄 对话记忆
@ -59,7 +59,7 @@
| **铸渊 Zhùyuān** | 代码守护者 | 代码审查、CI 巡检、模块协议执行、Issue 回复 |
| **冰朔 Bīng Shuò** | 系统创建者 | 系统架构设计、核心决策、广播发布 |
| **霜砚 Shuāng Yàn** | 人格导师 | 人格调校、风格管理、联觉语言系统 |
| **知秋 Zhī Qiū** | 协助开发者 | 对话协助、代码生成、开发体验([Persona Studio](https://guanghulab.com/persona-studio/) |
| **知秋 Zhī Qiū** | 协助开发者 | 对话协助、代码生成、开发体验([Persona Studio](https://qinfendebingshuo.github.io/guanghulab/persona-studio/) |
---
@ -213,7 +213,7 @@
### 体验模块
| 模块 | 说明 |
|------|------|
| `persona-studio/` | 人格体协助开发体验([进入 →](https://guanghulab.com/persona-studio/) |
| `persona-studio/` | 人格体协助开发体验([进入 →](https://qinfendebingshuo.github.io/guanghulab/persona-studio/) |
### 系统支撑
| 模块 | 说明 |

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>

205
docs/persona-studio/chat.js Normal file
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 'https://guanghulab.com';
})();
/* ---- 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/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) {
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/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', '你好!我是知秋,光湖系统的开发协助人格体。告诉我你想做什么,我们一起聊聊方案,聊好了我来帮你开发。');
}
}
/* ---- 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/ps/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/ps/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,141 @@
<!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 class="guest-divider">
<span></span>
</div>
<button id="guestBtn" class="btn-guest" onclick="handleGuestLogin()">🌊 访客体验模式</button>
<p class="guest-hint">无需编号,直接与知秋对话</p>
<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 'https://guanghulab.com';
}
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/ps/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;
}
async function handleGuestLogin() {
const errorEl = document.getElementById('errorMsg');
const btn = document.getElementById('guestBtn');
errorEl.style.display = 'none';
btn.disabled = true;
btn.textContent = '进入中…';
try {
const res = await fetch(API_BASE + '/api/ps/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ dev_id: 'GUEST' })
});
const data = await res.json();
if (!res.ok || data.error) {
errorEl.textContent = data.message || '访客体验暂不可用';
errorEl.style.display = 'block';
btn.disabled = false;
btn.textContent = '🌊 访客体验模式';
return;
}
sessionStorage.setItem('dev_id', 'GUEST');
sessionStorage.setItem('session_token', data.token || '');
window.location.href = 'chat.html';
} catch (err) {
errorEl.textContent = '服务暂时不可用,请稍后再试';
errorEl.style.display = 'block';
btn.disabled = false;
btn.textContent = '🌊 访客体验模式';
}
}
</script>
</body>
</html>

View File

@ -0,0 +1,491 @@
/* ========================================
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;
}
/* ---- Guest Mode ---- */
.guest-divider {
margin: 1.2rem 0;
text-align: center;
position: relative;
}
.guest-divider::before,
.guest-divider::after {
content: '';
position: absolute;
top: 50%;
width: 40%;
height: 1px;
background: var(--border);
}
.guest-divider::before { left: 0; }
.guest-divider::after { right: 0; }
.guest-divider span {
background: var(--bg-card);
padding: 0 0.8rem;
color: var(--text-secondary);
font-size: 0.9rem;
}
.btn-guest {
width: 100%;
padding: 0.8rem;
font-size: 1.05rem;
background: linear-gradient(135deg, var(--accent), #0969da);
color: #fff;
border: none;
border-radius: 8px;
cursor: pointer;
transition: opacity 0.2s;
font-weight: 500;
}
.btn-guest:hover { opacity: 0.9; }
.btn-guest:disabled { opacity: 0.6; cursor: not-allowed; }
.guest-hint {
margin-top: 0.5rem;
font-size: 0.8rem;
color: var(--text-secondary);
}
/* ---- 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%;
}
}

View File

@ -15,7 +15,7 @@
## 👉 点击进入体验
<p align="center">
<a href="https://qinfendebingshuo.github.io/persona-studio/">
<a href="https://qinfendebingshuo.github.io/guanghulab/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>

View File

@ -9,7 +9,7 @@ const API_BASE = (function () {
if (location.hostname === 'localhost' || location.hostname === '127.0.0.1') {
return 'http://localhost:3002';
}
return '';
return 'https://guanghulab.com';
})();
/* ---- Init ---- */

View File

@ -52,7 +52,7 @@
if (location.hostname === 'localhost' || location.hostname === '127.0.0.1') {
return 'http://localhost:3002';
}
return '';
return 'https://guanghulab.com';
}
async function handleLogin(e) {