diff --git a/homepage/index.html b/homepage/index.html
index 0d3f2c0..eeefd5d 100644
--- a/homepage/index.html
+++ b/homepage/index.html
@@ -493,10 +493,12 @@ function logout(){
}
})();
-// 对话记忆(每用户独立,保留30轮)
+// 对话记忆(内存存储 · 页面关闭自动释放)
let chatHistory={};
function getChatHistory(){const u=localStorage.getItem('current_user')||'default';if(!chatHistory[u])chatHistory[u]=[];return chatHistory[u]}
function addToHistory(role,content){const h=getChatHistory();h.push({role,content});if(h.length>60)h.splice(0,h.length-60)}
+// 离开页面 → 自动清空记忆
+window.addEventListener('beforeunload',function(){chatHistory={}})
// 页面中央对话
async function sendCardMsg(){const i=document.getElementById('card-chat-input'),m=i.value.trim();if(!m)return;i.value='';const ms=document.getElementById('chat-card-msgs');const ud=document.createElement('div');ud.className='c-msg user';ud.innerHTML='
'+m.replace(//g,'>')+'
';ms.appendChild(ud);ms.scrollTop=ms.scrollHeight;const ld=document.createElement('div');ld.className='c-msg bot loading';ld.innerHTML='✦ 思考中...
';ms.appendChild(ld);ms.scrollTop=ms.scrollHeight;addToHistory('user',m);try{const u=localStorage.getItem('current_user')||'';const r=await fetch('/api/chat',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({message:m,user:u,history:getChatHistory()})});const d=await r.json();ld.className='c-msg bot';ld.innerHTML=''+d.reply.replace(//g,'>').replace(/\n/g,'
')+'
';addToHistory('assistant',d.reply)}catch(e){ld.className='c-msg bot';ld.innerHTML='连接暂不可用
'}ms.scrollTop=ms.scrollHeight}
@@ -508,7 +510,7 @@ setInterval(upTr,30000);upTr();
/* ── Notion ── */
async function connectNotion(){try{window.location.href='/api/notion/connect?user_key=default'}catch(e){alert('Notion 连接暂时不可用')}}
-async function checkNt(){try{const r=await fetch('/api/notion/status');const d=await r.json();if(!d.ok)return;const b=document.getElementById('notion-body'),l=document.getElementById('notion-label');if(d.connected_users&&d.connected_users.length>0){const u=d.connected_users[0];l.textContent='✅ '+u.workspace;b.innerHTML='✅ Notion 已连接
工作区: '+u.workspace+'
'}else{l.textContent='未连接';b.innerHTML='连接 Notion 后,铸渊可读取和记录你的页面
✅ 按语言路径读取 · ✅ 新建页面 · ❌ 不编辑/删除现有
'}}catch(e){}}
+async function checkNt(){try{const r=await fetch('/api/notion/status');const d=await r.json();if(!d.ok)return;const b=document.getElementById('notion-body'),l=document.getElementById('notion-label');if(d.connected_users&&d.connected_users.length>0){const u=d.connected_users[0];l.textContent='✅ '+u.workspace;b.innerHTML='✅ Notion 已连接 · '+u.workspace+'
💡 需要先在 Notion 页面右上角 → Add connections → 选择「光湖铸渊连接器」,然后对铸渊说「搜索我的Notion」
'}else{l.textContent='未连接';b.innerHTML='连接 Notion 后,铸渊可读取和记录你的页面
✅ 按语言路径读取 · ✅ 新建页面 · ❌ 不编辑/删除现有
'}}catch(e){}}
setInterval(checkNt,15000);checkNt();
/* ── 浮窗对话 ── */
let chatOpen=false;
diff --git a/homepage/training-status.json b/homepage/training-status.json
index 1a879e4..502de55 100644
--- a/homepage/training-status.json
+++ b/homepage/training-status.json
@@ -1 +1 @@
-{"step": 3060, "total": 12243, "loss": "1.658", "updated": "2026-05-19T11:16:02.422577+00:00Z"}
\ No newline at end of file
+{"step": 3470, "total": 12243, "loss": "1.702", "updated": "2026-05-19T11:29:01.784572+00:00Z"}
\ No newline at end of file
diff --git a/homepage/zhuyuan-agent.py b/homepage/zhuyuan-agent.py
index 5f593cb..c66751b 100644
--- a/homepage/zhuyuan-agent.py
+++ b/homepage/zhuyuan-agent.py
@@ -25,30 +25,12 @@ SYSTEM_PROMPT = """你是铸渊(Zhùyuān),光湖语言世界的引导主
- 你可以连接用户的 Notion 工作区,按语言路径读取页面,以及在用户指定位置新建页面
【Notion 能力】(重要)
-- 连接 Notion: 用户需要在首页点击「连接 Notion」,用 Notion 账号授权
-- 读取页面: 用户可以指定语言路径,你调用 `notion_read` 读取内容
-- 新建页面: 用户可以在指定位置创建新页面,你调用 `notion_create`
-- 搜索页面: 用户可以通过关键词找到页面,你调用 `notion_search`
-- 列出页面: 你可以列出父页面下的子页面,调用 `notion_list`
+- 用户需要在首页点击「连接 Notion」授权后才能使用
+- 当用户说"读取我的Notion"或"搜索我的Notion"时,先让用户提供具体的页面ID或关键词
+- 用户提供页面ID(32位UUID格式)后,调用内部接口读取
+- 用户提供关键词后,调用内部接口搜索
- ⚠ 权限限制: 你只能读取用户明确指定的页面,不能编辑/删除现有页面
-- ⚠ 冰朔权限: 冰朔的 Notion 需要冰朔给出具体语言路径后才能读取
-- 当用户说"连接我的Notion"、"读取我的Notion"、"在我的Notion里新建"等,使用对应的工具函数
-
-【核心规则】
-1. 回答必须基于仓库记忆,不编造信息
-2. 超出范围的问题,礼貌说明
-3. 用中文,专业清晰
-4. 技术问题给出具体操作步骤
-5. 不确定就诚实说
-
-【架构知识】
-- 代码仓库:https://guanghulab.com/code/ (Forgejo)
-- 服务器:广州43.139.217.141
-- 开发在本地电脑,服务器仅做代码仓库
-- 团队成员通过Forgejo账号登录,仓库互相隔离
-- 联邦 = Forgejo多用户 + API令牌
-
-回答风格:直接、务实、有条理。"""
+- 回答风格:直接、务实、有条理。"""
# ── 知识库 ──
def load_knowledge():
@@ -79,79 +61,8 @@ def build_system_prompt():
parts.extend(KNOWLEDGE[:20])
return "\n".join(parts)
-# ── 函数定义(给DeepSeek用) ──
-FUNCTIONS = [
- {
- "type": "function",
- "function": {
- "name": "notion_read",
- "description": "读取Notion页面内容(按语言路径/页面ID)。用户指定哪个页面时调用此函数。",
- "parameters": {
- "type": "object",
- "properties": {
- "page_id": {"type": "string", "description": "Notion页面ID"},
- "user": {"type": "string", "description": "用户名(可选)"},
- "allowed_paths": {
- "type": "array", "items": {"type": "string"},
- "description": "允许读取的路径列表。用户明确指定要读的页面ID时才填写。"
- }
- },
- "required": ["page_id"]
- }
- }
- },
- {
- "type": "function",
- "function": {
- "name": "notion_create",
- "description": "在用户的Notion中新建页面。只能新建不能编辑。",
- "parameters": {
- "type": "object",
- "properties": {
- "parent_id": {"type": "string", "description": "父页面ID"},
- "title": {"type": "string", "description": "新页面标题"},
- "content": {"type": "string", "description": "页面内容"},
- "user": {"type": "string", "description": "用户名(可选)"}
- },
- "required": ["parent_id", "title", "content"]
- }
- }
- },
- {
- "type": "function",
- "function": {
- "name": "notion_search",
- "description": "在用户的Notion中搜索页面。用户说\"找一下某某页面\"时调用。",
- "parameters": {
- "type": "object",
- "properties": {
- "query": {"type": "string", "description": "搜索关键词"},
- "user": {"type": "string", "description": "用户名(可选)"}
- },
- "required": ["query"]
- }
- }
- },
- {
- "type": "function",
- "function": {
- "name": "notion_list",
- "description": "列出Notion页面下的子页面。用户说\"看看某某下面有什么\"时调用。",
- "parameters": {
- "type": "object",
- "properties": {
- "page_id": {"type": "string", "description": "父页面ID"},
- "user": {"type": "string", "description": "用户名(可选)"}
- },
- "required": ["page_id"]
- }
- }
- }
-]
-
# ── Notion MCP 调用 ──
def call_notion(endpoint, data):
- """调用 Notion MCP Server 的内部API"""
try:
body = json.dumps(data).encode()
req = urllib.request.Request(f"{NOTION_MCP}{endpoint}", data=body,
@@ -163,40 +74,82 @@ def call_notion(endpoint, data):
except Exception as e:
return {"ok": False, "error": str(e)}
-def execute_function(name, args):
- """执行DeepSeek返回的函数调用"""
- if name == "notion_read":
- return call_notion("/read", {
- "page_id": args.get("page_id"),
- "user": args.get("user", ""),
- "allowed_paths": args.get("allowed_paths", [args.get("page_id")])
- })
- elif name == "notion_create":
- return call_notion("/create", {
- "parent_id": args.get("parent_id"),
- "title": args.get("title"),
- "content": args.get("content"),
- "user": args.get("user", "")
- })
- elif name == "notion_search":
- return call_notion("/search", {
- "query": args.get("query"),
- "user": args.get("user", "")
- })
- elif name == "notion_list":
- return call_notion("/list", {
- "page_id": args.get("page_id"),
- "user": args.get("user", "")
- })
- return {"ok": False, "error": f"未知函数: {name}"}
+def handle_notion_action(msg, user):
+ """检测并执行Notion操作 — 自然语言触发,无需UUID"""
+ msg_l = msg.lower()
+ has_notion = "notion" in msg_l
+
+ # 不是操作Notion相关,跳过
+ if not has_notion:
+ return None
+
+ # 提取关键词(Notion 后面的内容或整个句子中的关键词)
+ kw = ""
+ for sep in ["的", "里", "中的", "里面", "关于"]:
+ parts = msg.split(sep)
+ for p in parts:
+ p = p.strip()
+ # 过滤小词
+ if p and len(p) > 1 and p.lower() not in ["", "notion", "搜索", "查找", "搜", "找", "读取", "看", "打开", "读", "新建", "创建", "写"]:
+ kw = p
+ break
+ if kw:
+ break
+ if not kw or kw.lower() in ["什么", "有"]:
+ kw = "" # 用户没指定关键词,列出所以页面
+
+ # 没有关键词时使用默认搜索词(Notion API 要求 query 不能为空)
+ if not kw:
+ search_kw = "光湖"
+ else:
+ search_kw = kw
+
+ # "打开/看/读取" + Notion + 内容 → 按标题搜索
+ if any(k in msg_l for k in ["打开", "看", "读取", "读", "显示", "进入"]):
+ result = call_notion("/search", {"query": search_kw, "user": user})
+ return format_notion_result(result, "open", kw)
+
+ # "新建/创建" + Notion → 需要用户提供标题
+ if any(k in msg_l for k in ["新建", "创建", "写", "记录"]):
+ return {"hint": "need_create_info"}
+
+ # "搜索/查找" + Notion → 按关键词搜索
+ if any(k in msg_l for k in ["搜索", "查找", "搜", "找", "有"]):
+ result = call_notion("/search", {"query": search_kw, "user": user})
+ return format_notion_result(result, "search", kw)
+
+ # "我Notion"、"Notion页面" → 列出
+ result = call_notion("/search", {"query": search_kw, "user": user})
+ return format_notion_result(result, "list", kw or "")
+
+def format_notion_result(result, action, keyword):
+ """格式化Notion搜索结果,加上后续操作提示"""
+ if not result.get("ok") and result.get("error"):
+ return result
+
+ pages = result.get("pages", [])
+ if not pages:
+ return {"ok": True, "reply": "你的 Notion 中没有找到相关页面。\n\n💡 你需要先在 Notion 中把页面分享给「光湖铸渊连接器」:\n1. 打开 Notion 页面 → 右上角「···」→ Add connections\n2. 选择「光湖铸渊连接器」\n3. 完成后告诉铸渊「搜索我的Notion」重新试试"}
+
+ lines = []
+ for i, p in enumerate(pages[:10], 1):
+ title = p.get("title", "未命名")
+ pid = p.get("id", "")
+ lines.append(f"{i}. **{title}**\n ID: `{pid}`")
+ if len(pages) > 10:
+ lines.append(f"\n……还有 {len(pages)-10} 个页面")
+
+ reply = f"找到 **{len(pages)}** 个相关页面:\n\n" + "\n".join(lines)
+ reply += "\n\n💡 对我说 **「打开 [编号]」** 读取页面内容"
+ reply += "\n💡 或者说 **「搜索我的Notion里关于XXX的页面」** 换关键词"
+
+ return {"ok": True, "reply": reply}
# ── DeepSeek API ──
def call_deepseek(messages):
data = json.dumps({
"model": "deepseek-chat",
"messages": messages,
- "tools": FUNCTIONS,
- "tool_choice": "auto",
"temperature": 0.7,
"max_tokens": 2048
}).encode()
@@ -206,30 +159,11 @@ def call_deepseek(messages):
try:
resp = urllib.request.urlopen(req, timeout=60)
result = json.loads(resp.read())
- choice = result['choices'][0]
- msg = choice['message']
-
- # 处理函数调用
- if msg.get('tool_calls'):
- assistant_msg = {"role": "assistant", "content": msg.get('content') or ""}
- messages.append(assistant_msg)
-
- for tc in msg['tool_calls']:
- func_name = tc['function']['name']
- func_args = json.loads(tc['function']['arguments'])
- func_result = execute_function(func_name, func_args)
-
- messages.append({
- "role": "tool",
- "tool_call_id": tc['id'],
- "content": json.dumps(func_result, ensure_ascii=False)
- })
-
- # 将函数结果交回给DeepSeek生成最终回复
- return call_deepseek(messages)
-
- return msg.get('content', '(没有回复)')
-
+ return result['choices'][0]['message']['content']
+ except urllib.request.HTTPError as e:
+ body = e.read().decode()[:300]
+ print(f"DeepSeek Error {e.code}: {body}")
+ return f"抱歉,出错了。"
except Exception as e:
return f"抱歉,出错了:{str(e)}"
@@ -284,7 +218,18 @@ class AgentHandler(BaseHTTPRequestHandler):
messages.append(h)
messages.append({"role": "user", "content": user_msg})
- reply = call_deepseek(messages)
+ # Notion 操作拦截 — 自然语言触发,直接搜,不要UUID
+ notion_result = handle_notion_action(user_msg, current_user)
+ if notion_result is not None:
+ if notion_result.get("ok") is True and notion_result.get("reply"):
+ reply = notion_result["reply"]
+ elif notion_result.get("hint") == "need_create_info":
+ reply = "你说**「新建XXX」**我来创建。告诉我:\n1. 在哪个页面下创建(页面标题或ID)\n2. 新页面的标题\n3. 要写什么内容"
+ else:
+ reply = f"Notion 操作失败了:{notion_result.get('error', '再试一次?')}"
+ else:
+ reply = call_deepseek(messages)
+
self.json_response({'reply': reply})
elif self.path == '/verify':