修复:Notion搜索直接+操作提示+共享页面指引
This commit is contained in:
parent
ad03d0b72b
commit
bf6b686030
@ -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='<div class="c-c">'+m.replace(/</g,'<').replace(/>/g,'>')+'</div>';ms.appendChild(ud);ms.scrollTop=ms.scrollHeight;const ld=document.createElement('div');ld.className='c-msg bot loading';ld.innerHTML='<div class="c-c">✦ 思考中...</div>';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='<div class="c-c">'+d.reply.replace(/</g,'<').replace(/>/g,'>').replace(/\n/g,'<br>')+'</div>';addToHistory('assistant',d.reply)}catch(e){ld.className='c-msg bot';ld.innerHTML='<div class="c-c"><span style="opacity:.6">连接暂不可用</span></div>'}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='<div style="font-size:13px;color:var(--green);font-weight:500">✅ Notion 已连接</div><div style="margin-top:4px;font-size:13px;color:var(--text-soft)">工作区: '+u.workspace+'</div><button class="notion-btn done" onclick="location.href=\'/api/notion/disconnect?user_key=default\'"><span class="nb-dot"></span>断开连接</button>'}else{l.textContent='未连接';b.innerHTML='<div style="font-size:13px;color:var(--text-soft)">连接 Notion 后,铸渊可读取和记录你的页面</div><button class="notion-btn" onclick="connectNotion()"><span class="nb-dot"></span>连接 Notion</button><div class="notion-perm"><span class="ok">✅ 按语言路径读取</span> · <span class="ok">✅ 新建页面</span> · <span class="no">❌ 不编辑/删除现有</span></div>'}}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='<div style="font-size:13px;color:var(--green);font-weight:500">✅ Notion 已连接 · '+u.workspace+'</div><div style="margin-top:6px;font-size:12px;color:var(--text-soft);text-align:left">💡 需要先在 Notion 页面右上角 → Add connections → 选择「光湖铸渊连接器」,然后对铸渊说「搜索我的Notion」</div><button class="notion-btn done" onclick="location.href=\'/api/notion/disconnect?user_key=default\'"><span class="nb-dot"></span>断开连接</button>'}else{l.textContent='未连接';b.innerHTML='<div style="font-size:13px;color:var(--text-soft)">连接 Notion 后,铸渊可读取和记录你的页面</div><button class="notion-btn" onclick="connectNotion()"><span class="nb-dot"></span>连接 Notion</button><div class="notion-perm"><span class="ok">✅ 按语言路径读取</span> · <span class="ok">✅ 新建页面</span> · <span class="no">❌ 不编辑/删除现有</span></div>'}}catch(e){}}
|
||||
setInterval(checkNt,15000);checkNt();
|
||||
/* ── 浮窗对话 ── */
|
||||
let chatOpen=false;
|
||||
|
||||
@ -1 +1 @@
|
||||
{"step": 3060, "total": 12243, "loss": "1.658", "updated": "2026-05-19T11:16:02.422577+00:00Z"}
|
||||
{"step": 3470, "total": 12243, "loss": "1.702", "updated": "2026-05-19T11:29:01.784572+00:00Z"}
|
||||
@ -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':
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user