Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 1 addition & 17 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 2 additions & 3 deletions frontend/src/components/BuildingPopover/BuildingPopover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -495,12 +495,11 @@ export function BuildingPopover({

<div className="popover-chat-input-row">
<input ref={inputRef} className="popover-chat-input" type="text"
placeholder={`问问关于${building.name}的问题…`}
placeholder={chatLoading ? 'AI 思考中…' : `问问关于${building.name}的问题…`}
value={chatInput} onChange={e => setChatInput(e.target.value)}
onKeyDown={e => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendQuestion(); } }}
onFocus={() => { inputBlurGuard.current = false; }}
onBlur={() => { inputBlurGuard.current = true; setTimeout(() => { inputBlurGuard.current = false; }, 300); }}
disabled={chatLoading} />
onBlur={() => { inputBlurGuard.current = true; setTimeout(() => { inputBlurGuard.current = false; }, 300); }} />
<button className="popover-chat-send" onClick={sendQuestion}
disabled={!chatInput.trim() || chatLoading} aria-label="发送">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none"
Expand Down
7 changes: 7 additions & 0 deletions frontend/src/components/ChatWidget/ChatWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,13 @@ export function ChatWidget({ selectedBuilding, onViewBuilding }: ChatWidgetProps
timestamp: Date.now(),
}]);
setSuggestions(getRelatedQuestions(text, 3));
}).catch(() => {
setMessages(prev => [...prev, {
id: `a-${Date.now()}`, role: 'assistant',
content: '抱歉,AI 服务暂时不可用,请稍后重试。',
timestamp: Date.now(),
}]);
}).finally(() => {
setIsLoading(false);
});
}, [isLoading]);
Expand Down
48 changes: 38 additions & 10 deletions functions/api/chat.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,23 +63,37 @@ export async function onRequestPost(context) {
webResults = await searchWeb(env, question, 3);
}

if (env.LLM_API_KEY && env.LLM_API_URL) {
answer = await callLLM(env, question, buildingId, buildingName, buildingCtx, qaContext, webResults);
if (bestScore >= 60 && qaContext.length > 0) {
answer = qaContext[0].answer;
} else if (env.LLM_API_KEY && env.LLM_API_URL) {
try {
answer = await callLLM(env, question, buildingId, buildingName, buildingCtx, qaContext, webResults);
} catch (llmErr) {
answer = qaContext.length > 0
? qaContext[0].answer
: null;
}
} else if (qaContext.length > 0) {
answer = qaContext[0].answer;
}

if (!answer) {
answer = '\u667a\u80fd\u95ee\u7b54\u670d\u52a1\u6682\u672a\u914d\u7f6e\uff0c\u8bf7\u8054\u7cfb\u7ba1\u7406\u5458\u3002';
answer = env.LLM_API_KEY
? '\u62b1\u6b49\uff0c\u668a\u80fd\u95ee\u7b54\u670d\u52a1\u6682\u65f6\u4e0d\u53ef\u7528\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5\u3002'
: '\u667a\u80fd\u95ee\u7b54\u670d\u52a1\u6682\u672a\u914d\u7f6e\uff0c\u8bf7\u8054\u7cfb\u7ba1\u7406\u5458\u3002';
}

if (db) {
await db.prepare(
'INSERT INTO chat_logs (id, question, answer, building_id, building_name) VALUES (?, ?, ?, ?, ?)'
).bind(
`chat-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
question, answer, buildingId || null, buildingName || null
).run();
try {
await db.prepare(
'INSERT INTO chat_logs (id, question, answer, building_id, building_name) VALUES (?, ?, ?, ?, ?)'
).bind(
`chat-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
question, answer, buildingId || null, buildingName || null
).run();
} catch (logErr) {
// 日志写入失败不影响主响应
}
}

return jsonResponse({ answer, sources });
Expand All @@ -94,7 +108,21 @@ export async function onRequestPost(context) {
function jsonResponse(data, status = 200) {
return new Response(JSON.stringify(data), {
status,
headers: { 'Content-Type': 'application/json; charset=utf-8' },
headers: {
'Content-Type': 'application/json; charset=utf-8',
'Access-Control-Allow-Origin': '*',
},
});
}

export async function onRequestOptions() {
return new Response(null, {
status: 204,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
},
});
}

Expand Down
Loading