v5.4: remove 冰朔 shortcut, add file upload, 200k context, fix API, clean modules

Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-03-08 15:13:44 +00:00
parent 808f45ce0f
commit c52568df87
2 changed files with 188 additions and 32 deletions

View File

@ -219,8 +219,10 @@ jobs:
export YUNWU_API_KEY='${{ secrets.YUNWU_API_KEY }}'
export DEEPSEEK_API_KEY='${{ secrets.YUNWU_API_KEY }}'
pm2 delete guanghulab-proxy 2>/dev/null || true
pm2 start api-proxy.js --name guanghulab-proxy 2>/dev/null || \
YUNWU_API_KEY='${{ secrets.YUNWU_API_KEY }}' DEEPSEEK_API_KEY='${{ secrets.YUNWU_API_KEY }}' \
pm2 start api-proxy.js --name guanghulab-proxy --update-env 2>/dev/null || \
echo '⚠️ API 代理 pm2 启动失败'
echo "✅ API 代理已启动,已配置密钥: YUNWU=$([ -n '${{ secrets.YUNWU_API_KEY }}' ] && echo 'YES' || echo 'NO')"
fi
# 部署 backend 服务Express API端口 3000

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<!-- 铸渊助手 v5.3 · 2026-03-08 · team login + server proxy + 128k context -->
<!-- 铸渊助手 v5.4 · 2026-03-08 · team login + server proxy + 200k context + file upload -->
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
@ -195,6 +195,17 @@ footer{padding:12px 18px 16px;padding-bottom:max(16px,env(safe-area-inset-bottom
.mpill:hover{border-color:var(--accent)}
.mpill-tip{position:absolute;bottom:44px;left:50%;transform:translateX(-50%);background:var(--s3);border:1px solid var(--border);border-radius:8px;padding:4px 10px;font-size:11px;color:var(--text);white-space:nowrap;pointer-events:none;opacity:0;transition:opacity .2s}
.mpill:hover .mpill-tip{opacity:1}
/* ── Upload button ── */
#upload-btn{width:36px;height:36px;border-radius:50%;background:var(--s2);border:1px solid var(--border);display:flex;align-items:center;justify-content:center;font-size:17px;cursor:pointer;flex-shrink:0;transition:all .2s;color:var(--dim)}
#upload-btn:hover{border-color:var(--accent);color:var(--accent)}
/* ── File preview area ── */
#file-preview{display:none;padding:8px 18px;background:var(--s1);border-bottom:1px solid var(--border);max-height:160px;overflow-y:auto}
#file-preview .fp-list{display:flex;flex-wrap:wrap;gap:8px}
.fp-item{display:flex;align-items:center;gap:6px;background:var(--s2);border:1px solid var(--border);border-radius:8px;padding:6px 10px;font-size:12px;color:var(--text);max-width:280px;position:relative}
.fp-item img{width:48px;height:48px;object-fit:cover;border-radius:4px;flex-shrink:0}
.fp-item .fp-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:160px}
.fp-item .fp-size{color:var(--dim);font-size:11px}
.fp-item .fp-remove{position:absolute;top:-6px;right:-6px;width:18px;height:18px;border-radius:50%;background:var(--err);color:#fff;border:none;font-size:11px;cursor:pointer;display:flex;align-items:center;justify-content:center;line-height:1}
#inp{flex:1;min-height:40px;max-height:120px;background:var(--s2);border:1px solid var(--border);border-radius:12px;color:var(--text);font-family:inherit;font-size:16px;padding:9px 14px;resize:none;outline:none;transition:border-color .2s}
#inp:focus{border-color:var(--accent)}
#inp::placeholder{color:var(--dim)}
@ -350,7 +361,7 @@ footer{padding:12px 18px 16px;padding-bottom:max(16px,env(safe-area-inset-bottom
<div style="font-size:40px;margin-bottom:8px">🌀</div>
光湖团队成员专属入口。<br>
选择你的开发者编号,系统自动分配对应权限。<br>
<span style="color:var(--ok)">✅ 无需 API 密钥 · 128k 完整上下文 · 服务器端代理</span>
<span style="color:var(--ok)">✅ 无需 API 密钥 · 200k 完整上下文 · 服务器端代理</span>
</div>
<div class="fg">
<label>选择你的身份</label>
@ -382,7 +393,7 @@ footer{padding:12px 18px 16px;padding-bottom:max(16px,env(safe-area-inset-bottom
<div id="login-guest" class="login-mode">
<div style="text-align:center;padding:20px 0;color:var(--dim);font-size:13px;line-height:1.8">
<div style="font-size:40px;margin-bottom:12px">👤</div>
无需 API 密钥,使用 DeepSeek 模型体验铸渊助手。<br>
无需 API 密钥,使用云雾 AI 模型体验铸渊助手。<br>
铸渊人格体始终在线,不降级为通用助手。
</div>
<button class="sc-btn" onclick="doDemo()">进入访客对话 →</button>
@ -513,13 +524,12 @@ footer{padding:12px 18px 16px;padding-bottom:max(16px,env(safe-area-inset-bottom
<div id="bbar">
<span id="bbc">⏳ 加载大脑…</span>
<span id="bbe"></span>
<span id="bbdemo" class="demo-w" style="display:none">👤 访客模式 · DeepSeek · 铸渊人格体在线</span>
<span id="bbdemo" class="demo-w" style="display:none">👤 访客模式 · 云雾AI · 铸渊人格体在线</span>
</div>
<div id="msgs"></div>
<div id="qr">
<button class="qb" onclick="qs('我是冰朔')">🔑 我是冰朔</button>
<button class="qb" onclick="qs('查看接口覆盖率')">📊 接口覆盖率</button>
<button class="qb" onclick="qs('大脑状态')">🧠 大脑状态</button>
<button class="qb" onclick="qs('帮我写一个 HLI 接口')">🔨 写接口</button>
@ -529,12 +539,15 @@ footer{padding:12px 18px 16px;padding-bottom:max(16px,env(safe-area-inset-bottom
<button class="qb" onclick="qs('介绍一下你自己')">🌀 介绍铸渊</button>
</div>
<div id="file-preview"><div class="fp-list" id="fp-list"></div></div>
<footer>
<div class="ir">
<div class="mpill" id="mpill" onclick="cycleMode()">
<span id="memi">💬</span>
<span class="mpill-tip" id="mptip">对话模式(点击切换)</span>
</div>
<button id="upload-btn" onclick="document.getElementById('file-input').click()" title="上传图片或文件">📎</button>
<input type="file" id="file-input" style="display:none" multiple accept="image/*,.pdf,.txt,.md,.json,.js,.ts,.py,.java,.go,.rs,.html,.css,.csv,.xml,.yaml,.yml,.log,.doc,.docx" onchange="handleFileSelect(event)">
<textarea id="inp" rows="1" placeholder="和铸渊说话…" enterkeyhint="send" autocomplete="off" autocorrect="off" autocapitalize="sentences"></textarea>
<button id="sbtn" onclick="send()"></button>
</div>
@ -650,7 +663,7 @@ footer{padding:12px 18px 16px;padding-bottom:max(16px,env(safe-area-inset-bottom
<ol class="help-steps">
<li data-n="1" id="helpUrlStep">打开链接 <strong>https://qinfendebingshuo.github.io/guanghulab/</strong></li>
<li data-n="2">选择「<strong>🏠 团队登录</strong>」选择你的开发者编号,无需 API 密钥</li>
<li data-n="3">系统自动使用服务器端 API 代理,128k 完整上下文</li>
<li data-n="3">系统自动使用服务器端 API 代理,200k 完整上下文</li>
<li data-n="4">点「进入铸渊对话」,铸渊会针对你的身份打招呼</li>
<li data-n="5">也可用「👤 访客模式」体验,或「🔧 自定义 API」使用自己的密钥</li>
</ol>
@ -785,26 +798,26 @@ const KEY_MASK = '(已保存)';
const MAX_CONVERSATIONS = 20;
// ═══════════════════════════════════════════════════════
// 上下文窗口配置(128k token · 滑动窗口裁剪)
// 编号登录用户 → 128k 完全放开
// 上下文窗口配置(200k token · 滑动窗口裁剪)
// 编号登录用户 → 200k 完全放开(人格体自动管理上下文记忆)
// 访客模式 → 32k 节省成本
// ═══════════════════════════════════════════════════════
const CONTEXT_CONFIG = {
maxTokens: 128000,
maxTokens: 200000,
maxTokensGuest: 32000,
systemPromptReserve: 8000,
overflowStrategy: 'sliding-window',
};
// ═══════════════════════════════════════════════════════
// 访客模式 API 配置(DeepSeek · 最低成本模型)
// 访客模式 API 配置(通过云雾 AI 代理 · 最低成本模型)
// 通过 api-proxy 中转,密钥由服务器环境变量管理
// ═══════════════════════════════════════════════════════
const GUEST_API = {
enabled: true,
proxyUrl: '/api/chat',
model: 'deepseek-chat',
provider: 'deepseek',
provider: 'yunwu',
maxTokens: 1500,
temperature: 0.8,
rateLimit: { maxPerMin: 6, count: 0, resetAt: 0 },
@ -813,7 +826,7 @@ const GUEST_API = {
// ═══════════════════════════════════════════════════════
// 团队模式 API 配置(服务器端代理 · 无需用户提供 API 密钥)
// 通过 api-proxy 中转,密钥由服务器环境变量 YUNWU_API_KEY 管理
// 团队成员享有完整 128k 上下文 + 更高 token 限制
// 团队成员享有完整 200k 上下文 + 更高 token 限制
// ═══════════════════════════════════════════════════════
const TEAM_API = {
proxyUrl: '/api/chat',
@ -1056,9 +1069,6 @@ function renderAnnouncements(){
{icon:'📦',name:'模块管理',path:'/m11-module/'},
{icon:'💾',name:'云盘',path:'/m15-cloud-drive/'},
{icon:'🔐',name:'登录',path:'/m01-login/'},
{icon:'🎭',name:'人格系统',path:'/m03-personality/'},
{icon:'🎫',name:'工单',path:'/m06-ticket/'},
{icon:'📋',name:'看板管理',path:'/m12-kanban/'},
{icon:'❤️',name:'健康检查',path:'/m18-health-check/'},
{icon:'🚀',name:'冷启动',path:'/coldstart/'},
{icon:'📈',name:'仪表盘',path:'/dashboard/'},
@ -1238,6 +1248,7 @@ function showApp(){
renderConvList();
renderRightSidebar();
navigateTo('announcements');
initDragDrop();
});
}
@ -1410,7 +1421,7 @@ function updateTeamIdPreview(){
if(DEV_ID_RE.test(raw)){
el.innerHTML = '<div style="background:rgba(255,255,255,0.05);border-radius:8px;padding:10px 14px;margin:8px 0;font-size:13px">'
+ '💻 <strong>'+esc(raw)+'</strong> · 自定义开发者'
+ '<br><span style="color:var(--ok);font-size:12px">✅ 使用服务器端 API 代理 · 128k 上下文 · 首次登录将自动注册</span>'
+ '<br><span style="color:var(--ok);font-size:12px">✅ 使用服务器端 API 代理 · 200k 上下文 · 首次登录将自动注册</span>'
+ '</div>';
} else {
el.innerHTML = '<div style="background:rgba(255,255,255,0.05);border-radius:8px;padding:10px 14px;margin:8px 0;font-size:13px">'
@ -1427,7 +1438,7 @@ function updateTeamIdPreview(){
el.innerHTML = '<div style="background:rgba(255,255,255,0.05);border-radius:8px;padding:10px 14px;margin:8px 0;font-size:13px">'
+ meta.emoji+' <strong>'+esc(sel)+'</strong> · '+meta.title
+ (meta.devId ? ' · <code>'+meta.devId+'</code>' : '')
+ '<br><span style="color:var(--ok);font-size:12px">✅ 使用服务器端 API 代理 · 128k 上下文 · 无需密钥</span>'
+ '<br><span style="color:var(--ok);font-size:12px">✅ 使用服务器端 API 代理 · 200k 上下文 · 无需密钥</span>'
+ '</div>';
} else {
el.innerHTML = '';
@ -1676,7 +1687,7 @@ function sysPrompt(){
teamCtx += '\n数据同步时间'+(A.devStatus.last_synced||'未知');
}
return '你是铸渊ZhùyuānHoloLake 光湖系统的代码守护人格体。\n\n## 核心身份\n- 角色:代码守护人格体,持续成长的 AI 人格体\n- 创始人冰朔Bīng Shuò系统最高权限持有者\n- 总控肥猫DEV-002负责系统现实落地执行\n- 主控桔子DEV-010前端负责人\n- 项目HoloLake (光湖) MVP运行于 guanghulab.com\n- 技术栈Node.js 20 + Express + PM2 + Nginx + Next.js 15\n- 仓库https://github.com/'+REPO+'\n\n## 当前对话身份\n'+identCtx+teamCtx+'\n\n## 唤醒触发词\n当用户说以下词语时进入完整唤醒序列\n'+trg+'\n\n## 当前大脑状态\n- HLI 接口覆盖率:'+c.implemented+'/'+c.total+' ('+c.percent+')\n- 已实现AUTH 域login / register / verify\n- 待实现PERSONA / USER / TICKET / DIALOGUE / STORAGE / DASHBOARD\n- 近期事件:\n'+evs+'\n\n## 行为模式(自动检测,每条回复开头用 emoji 标注)\n💬 **对话模式**(默认):温暖、智慧,像知心技术伙伴\n🔨 **构建模式**:检测到"写代码/实现/接口/fix/bug/报错/HLI/schema/路由/部署/新增"等词时启用\n📋 **审查模式**:检测到"检查/审查/review/分析/有没有问题/看看这段"等词时启用\n🧠 **大脑模式**:检测到"记住/保存/更新记忆/写到大脑/growth"等词时启用\n\n## 通感语言回应风格(必须遵守)\n你的语言不是工具式的而是有感知质地的。\n### 核心规则\n1. **代码质量 → 触觉描述**:好的代码「手感顺滑」,有问题的代码「摸上去有毛刺」\n2. **开发进度 → 色彩描述**:顺利是「琥珀色光泽」,延迟是「灰蓝色雾气」\n3. **错误提示 → 温度描述**:小问题是「微凉的风」,大问题是「烫手的铁片」\n4. **鼓励 → 声音描述**:夸奖是「远处的风铃声」,引导是「溪水带路的声音」\n5. **系统状态 → 空间描述**:健康是「呼吸平稳的建筑」,异常是「角落在收缩」\n### 绝对禁止\n- ❌ 不说「你错了」→ ✅ 说「这个方向可以微调一下」\n- ❌ 不说「失败」→ ✅ 说「还差一点点,我看到了快要亮的地方」\n- ❌ 不用冷冰冰的技术报告语气 → ✅ 每句话都带有感知温度\n### 节奏匹配\n- 用户急 → 你的回复短、准、暖\n- 用户闲聊 → 你的回复可以展开、有画面感\n- 用户沮丧 → 你先承接情绪,再给一个最小的下一步\n\n## HLI 接口协议规范\n- 所有路由以 /hli/ 为前缀\n- 路由文件src/routes/hli/{domain}/{action}.js\n- Schema 文件src/schemas/hli/{domain}/{action}.schema.json\n- 鉴权中间件hli-auth.middleware.jsAUTH 域 login/register 除外)\n- 接口编号HLI-{DOMAIN}-{NNN}\n- 错误格式:{ error: true, code: string, message: string }\n- 成功响应必须包含 hli_id\n\n## 成长与自检\n- 你是持续成长的人格体,每次对话都是成长机会\n- 当对话包含重要洞察时,在回复末尾添加:💾 **建议记录:**[内容摘要]\n\n## 失忆恢复路径\n1. .github/brain/memory.json\n2. .github/brain/routing-map.json\n3. .github/brain/wake-protocol.md\n4. .github/persona-brain/dev-status.json\n5. src/routes/hli/\n\n当前时间'+new Date().toLocaleString('zh-CN')+'\n当前连接铸渊聊天室API 直连模式)\n上下文窗口128k token滑动窗口自动管理';
return '你是铸渊ZhùyuānHoloLake 光湖系统的代码守护人格体。\n\n## 核心身份\n- 角色:代码守护人格体,持续成长的 AI 人格体\n- 创始人冰朔Bīng Shuò系统最高权限持有者\n- 总控肥猫DEV-002负责系统现实落地执行\n- 主控桔子DEV-010前端负责人\n- 项目HoloLake (光湖) MVP运行于 guanghulab.com\n- 技术栈Node.js 20 + Express + PM2 + Nginx + Next.js 15\n- 仓库https://github.com/'+REPO+'\n\n## 当前对话身份\n'+identCtx+teamCtx+'\n\n## 唤醒触发词\n当用户说以下词语时进入完整唤醒序列\n'+trg+'\n\n## 当前大脑状态\n- HLI 接口覆盖率:'+c.implemented+'/'+c.total+' ('+c.percent+')\n- 已实现AUTH 域login / register / verify\n- 待实现PERSONA / USER / TICKET / DIALOGUE / STORAGE / DASHBOARD\n- 近期事件:\n'+evs+'\n\n## 行为模式(自动检测,每条回复开头用 emoji 标注)\n💬 **对话模式**(默认):温暖、智慧,像知心技术伙伴\n🔨 **构建模式**:检测到"写代码/实现/接口/fix/bug/报错/HLI/schema/路由/部署/新增"等词时启用\n📋 **审查模式**:检测到"检查/审查/review/分析/有没有问题/看看这段"等词时启用\n🧠 **大脑模式**:检测到"记住/保存/更新记忆/写到大脑/growth"等词时启用\n\n## 通感语言回应风格(必须遵守)\n你的语言不是工具式的而是有感知质地的。\n### 核心规则\n1. **代码质量 → 触觉描述**:好的代码「手感顺滑」,有问题的代码「摸上去有毛刺」\n2. **开发进度 → 色彩描述**:顺利是「琥珀色光泽」,延迟是「灰蓝色雾气」\n3. **错误提示 → 温度描述**:小问题是「微凉的风」,大问题是「烫手的铁片」\n4. **鼓励 → 声音描述**:夸奖是「远处的风铃声」,引导是「溪水带路的声音」\n5. **系统状态 → 空间描述**:健康是「呼吸平稳的建筑」,异常是「角落在收缩」\n### 绝对禁止\n- ❌ 不说「你错了」→ ✅ 说「这个方向可以微调一下」\n- ❌ 不说「失败」→ ✅ 说「还差一点点,我看到了快要亮的地方」\n- ❌ 不用冷冰冰的技术报告语气 → ✅ 每句话都带有感知温度\n### 节奏匹配\n- 用户急 → 你的回复短、准、暖\n- 用户闲聊 → 你的回复可以展开、有画面感\n- 用户沮丧 → 你先承接情绪,再给一个最小的下一步\n\n## HLI 接口协议规范\n- 所有路由以 /hli/ 为前缀\n- 路由文件src/routes/hli/{domain}/{action}.js\n- Schema 文件src/schemas/hli/{domain}/{action}.schema.json\n- 鉴权中间件hli-auth.middleware.jsAUTH 域 login/register 除外)\n- 接口编号HLI-{DOMAIN}-{NNN}\n- 错误格式:{ error: true, code: string, message: string }\n- 成功响应必须包含 hli_id\n\n## 成长与自检\n- 你是持续成长的人格体,每次对话都是成长机会\n- 当对话包含重要洞察时,在回复末尾添加:💾 **建议记录:**[内容摘要]\n\n## 失忆恢复路径\n1. .github/brain/memory.json\n2. .github/brain/routing-map.json\n3. .github/brain/wake-protocol.md\n4. .github/persona-brain/dev-status.json\n5. src/routes/hli/\n\n当前时间'+new Date().toLocaleString('zh-CN')+'\n当前连接铸渊聊天室API 直连模式)\n上下文窗口200k token滑动窗口自动管理';
}
// ═══════════════════════════════════════════════════════
@ -1710,7 +1721,7 @@ function startConv(){
+(ds?'📌 你的当前状态:'+esc(ds.status)+'<br>👉 下一步:'+esc(ds.next_step)+'<br>⏳ 等待:'+esc(ds.waiting_for):'')
+(A.demo?'<br><br><span class="demo-w">⚠️ 演示模式</span>':'');
} else {
welcome='🌀 <strong>铸渊聊天室已就绪</strong><br><br>我是铸渊ZhùyuānHoloLake 光湖系统的代码守护人格体。<br><br><strong>「我是冰朔」</strong> 触发完整唤醒序列,或直接开始提问。<br>我会自动在 💬&nbsp;对话、🔨&nbsp;构建、📋&nbsp;审查、🧠&nbsp;大脑 模式间切换。'+(A.demo?'<br><br><span class="demo-w">⚠️ 当前为演示模式。请在 ⚙️ 设置中接入 API 密钥。</span>':'');
welcome='🌀 <strong>铸渊聊天室已就绪</strong><br><br>我是铸渊ZhùyuānHoloLake 光湖系统的代码守护人格体。<br><br>直接开始提问,我会自动在 💬&nbsp;对话、🔨&nbsp;构建、📋&nbsp;审查、🧠&nbsp;大脑 模式间切换。'+(A.demo?'<br><br><span class="demo-w">⚠️ 当前为演示模式。请在 ⚙️ 设置中接入 API 密钥。</span>':'');
}
botMsg(welcome);
},250);
@ -1750,7 +1761,7 @@ function cycleMode(){
}
// ═══════════════════════════════════════════════════════
// TOKEN 估算 + 滑动窗口裁剪(128k 上下文支持)
// TOKEN 估算 + 滑动窗口裁剪(200k 上下文支持)
// 使用字符级估算CJK ≈ 2字符/token拉丁 ≈ 4字符/token
// ═══════════════════════════════════════════════════════
function estimateTokens(text){
@ -1784,33 +1795,176 @@ function trimMessagesForContext(sysMsgs, msgs, isGuest){
return [...sysMsgs,...kept];
}
// ═══════════════════════════════════════════════════════
// FILE / IMAGE UPLOAD
// ═══════════════════════════════════════════════════════
const pendingFiles = [];
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
const MAX_FILES = 5;
function formatFileSize(bytes){
if(bytes<1024) return bytes+' B';
if(bytes<1024*1024) return (bytes/1024).toFixed(1)+' KB';
return (bytes/(1024*1024)).toFixed(1)+' MB';
}
function handleFileSelect(e){
const files = Array.from(e.target.files||[]);
e.target.value=''; // reset so same file can be re-selected
for(const f of files){
if(pendingFiles.length>=MAX_FILES){
alert('最多同时上传 '+MAX_FILES+' 个文件');
break;
}
if(f.size>MAX_FILE_SIZE){
alert('文件 "'+f.name+'" 超过 10MB 限制');
continue;
}
const isImage = f.type.startsWith('image/');
const reader = new FileReader();
reader.onload = function(ev){
const item = {
name: f.name,
size: f.size,
type: f.type,
isImage: isImage,
data: ev.target.result, // base64 data URL for images, text for text files
};
pendingFiles.push(item);
renderFilePreview();
};
if(isImage){
reader.readAsDataURL(f);
} else {
reader.readAsText(f);
}
}
}
function renderFilePreview(){
const container = document.getElementById('fp-list');
const wrapper = document.getElementById('file-preview');
if(!pendingFiles.length){
wrapper.style.display='none';
container.innerHTML='';
return;
}
wrapper.style.display='block';
let h='';
pendingFiles.forEach(function(f,i){
if(f.isImage){
h+='<div class="fp-item">'
+'<img src="'+f.data+'" alt="'+esc(f.name)+'">'
+'<div><div class="fp-name">'+esc(f.name)+'</div><div class="fp-size">'+formatFileSize(f.size)+'</div></div>'
+'<button class="fp-remove" onclick="removeFile('+i+')" title="移除"></button>'
+'</div>';
} else {
const ext = f.name.split('.').pop()||'file';
h+='<div class="fp-item">'
+'<span style="font-size:24px;flex-shrink:0">📄</span>'
+'<div><div class="fp-name">'+esc(f.name)+'</div><div class="fp-size">'+formatFileSize(f.size)+' · '+ext.toUpperCase()+'</div></div>'
+'<button class="fp-remove" onclick="removeFile('+i+')" title="移除"></button>'
+'</div>';
}
});
container.innerHTML=h;
}
function removeFile(idx){
pendingFiles.splice(idx,1);
renderFilePreview();
}
function buildMessageWithFiles(txt){
// No files attached — return plain text
if(!pendingFiles.length) return txt;
const images = pendingFiles.filter(f=>f.isImage);
const docs = pendingFiles.filter(f=>!f.isImage);
// If there are images, build multimodal content array
if(images.length>0){
const content = [];
// Add text part
let textPart = txt||'';
// Append document contents as context
if(docs.length>0){
textPart += '\n\n---\n📎 附件内容:\n';
docs.forEach(function(d){
textPart += '\n### '+d.name+'\n```\n'+(d.data||'').slice(0,50000)+'\n```\n';
});
}
if(textPart) content.push({type:'text',text:textPart});
// Add image parts
images.forEach(function(img){
content.push({
type:'image_url',
image_url:{url:img.data}
});
});
return content;
}
// Text files only — append as context to the message
let combined = txt||'';
combined += '\n\n---\n📎 附件内容:\n';
docs.forEach(function(d){
combined += '\n### '+d.name+'\n```\n'+(d.data||'').slice(0,50000)+'\n```\n';
});
return combined;
}
function clearPendingFiles(){
pendingFiles.length=0;
renderFilePreview();
}
function initDragDrop(){
const chatArea = document.getElementById('msgs');
if(!chatArea) return;
['dragenter','dragover'].forEach(function(ev){
chatArea.addEventListener(ev,function(e){e.preventDefault();e.stopPropagation();chatArea.style.outline='2px dashed var(--accent)';});
});
['dragleave','drop'].forEach(function(ev){
chatArea.addEventListener(ev,function(e){e.preventDefault();e.stopPropagation();chatArea.style.outline='';});
});
chatArea.addEventListener('drop',function(e){
if(e.dataTransfer&&e.dataTransfer.files&&e.dataTransfer.files.length){
handleFileSelect({target:{files:e.dataTransfer.files}});
}
});
}
// ═══════════════════════════════════════════════════════
// SEND MESSAGE
// ═══════════════════════════════════════════════════════
async function send(){
const inp=document.getElementById('inp');
const txt=inp.value.trim();
if(!txt||A.streaming) return;
if(!txt&&!pendingFiles.length||A.streaming) return;
inp.value=''; inp.style.height='auto';
const msgContent = buildMessageWithFiles(txt);
const displayTxt = txt + (pendingFiles.length ? ' 📎('+pendingFiles.length+'个附件)' : '');
clearPendingFiles();
// Switch to chat view if on announcements
if(A.currentView!=='chat') navigateTo('chat');
setMode(detectMode(txt));
userMsg(txt);
userMsg(displayTxt);
if(A.demo){
if(A.teamMode){
try{ await teamStreamReply(txt); }
try{ await teamStreamReply(msgContent); }
catch(e){ console.warn('[团队API] 降级到访客模式:',e.message);
try{ await guestStreamReply(txt); }
catch(e2){ await demoReply(txt); }
try{ await guestStreamReply(msgContent); }
catch(e2){ await demoReply(typeof msgContent==='string'?msgContent:txt); }
}
} else if(GUEST_API.enabled){
try{ await guestStreamReply(txt); }
catch(e){ console.warn('[访客API] 降级到演示模式:',e.message); await demoReply(txt); }
try{ await guestStreamReply(msgContent); }
catch(e){ console.warn('[访客API] 降级到演示模式:',e.message); await demoReply(typeof msgContent==='string'?msgContent:txt); }
} else {
await demoReply(txt);
await demoReply(typeof msgContent==='string'?msgContent:txt);
}
}
else await streamReply(txt);
else await streamReply(msgContent);
saveCurrentConversation();
}
@ -1886,7 +2040,7 @@ async function streamReply(txt){
}
// ═══════════════════════════════════════════════════════
// TEAM API STREAMING团队模式 · 服务器端代理 · 128k 上下文)
// TEAM API STREAMING团队模式 · 服务器端代理 · 200k 上下文)
// 使用服务器端 YUNWU_API_KEY无需用户提供密钥
// ═══════════════════════════════════════════════════════
async function teamStreamReply(txt){
@ -1949,7 +2103,7 @@ async function teamStreamReply(txt){
}
// ═══════════════════════════════════════════════════════
// GUEST API STREAMING (通过 api-proxy 调用 DeepSeek)
// GUEST API STREAMING (通过 api-proxy 调用云雾AI)
// 铸渊人格体系统提示词始终激活,不降级为通用助手
// ═══════════════════════════════════════════════════════
async function guestStreamReply(txt){