Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Q
qianjiangb2b
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
AI-甘富林
qianjiangb2b
Commits
50510818
Commit
50510818
authored
Jun 18, 2026
by
AI-甘富林
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
多轮对话记忆
parent
6d7edaac
Changes
1
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
47 additions
and
11 deletions
+47
-11
index.ts
supabase/functions/dify-chat-stream/index.ts
+47
-11
No files found.
supabase/functions/dify-chat-stream/index.ts
View file @
50510818
...
@@ -113,6 +113,7 @@ async function loadConversationHistory(
...
@@ -113,6 +113,7 @@ async function loadConversationHistory(
maxMessages
=
20
maxMessages
=
20
):
Promise
<
HistoryMessage
[]
>
{
):
Promise
<
HistoryMessage
[]
>
{
try
{
try
{
console
.
log
(
`🔍 [ai-chat-stream] 查询历史: conv_id=
${
conversationId
}
user_id=
${
userId
}
limit=
${
maxMessages
}
`
);
const
{
data
,
error
}
=
await
supabase
const
{
data
,
error
}
=
await
supabase
.
from
(
'messages'
)
.
from
(
'messages'
)
.
select
(
'sender_type, content'
)
.
select
(
'sender_type, content'
)
...
@@ -122,11 +123,11 @@ async function loadConversationHistory(
...
@@ -122,11 +123,11 @@ async function loadConversationHistory(
.
limit
(
maxMessages
);
.
limit
(
maxMessages
);
if
(
error
)
{
if
(
error
)
{
console
.
error
(
'
[ai-chat-stream] 加载历史消息失败:'
,
error
);
console
.
error
(
'
❌ [ai-chat-stream] 加载历史消息失败:'
,
JSON
.
stringify
(
error
)
);
return
[];
return
[];
}
}
console
.
log
(
`📜 [ai-chat-stream]
Loaded
${
data
?.
length
||
0
}
history
messages
for
conversation
$
{
conversationId
}
`);
console
.
log
(
`📜 [ai-chat-stream]
历史查询结果: conversation_id=
${
conversationId
}
user_id=
${
userId
}
rows=
${
data
?.
length
||
0
}
`);
return (data || []).map((m: any) => ({
return (data || []).map((m: any) => ({
role: m.sender_type === 'user' ? 'user' : 'assistant',
role: m.sender_type === 'user' ? 'user' : 'assistant',
content: m.content,
content: m.content,
...
@@ -262,8 +263,11 @@ Deno.serve(async (req) => {
...
@@ -262,8 +263,11 @@ Deno.serve(async (req) => {
trace_id: traceId,
trace_id: traceId,
message: message?.substring(0, 80),
message: message?.substring(0, 80),
catalogCount: inputs?.catalog?.length || 0,
catalogCount: inputs?.catalog?.length || 0,
conversationId: conversationId || '(new)',
conversationId: conversationId || '(empty)',
localConversationId: localConversationId || '(not provided)',
localConversationId: localConversationId || '(empty)',
localConversationId_type: typeof localConversationId,
inputs_user_id: inputs?.user_id || '(empty)',
inputs_user_id_type: typeof inputs?.user_id,
});
});
const supabaseUrl = Deno.env.get('SUPABASE_URL')!;
const supabaseUrl = Deno.env.get('SUPABASE_URL')!;
...
@@ -307,8 +311,20 @@ Deno.serve(async (req) => {
...
@@ -307,8 +311,20 @@ Deno.serve(async (req) => {
// ─── 加载对话历史 ───
// ─── 加载对话历史 ───
let historyMessages: HistoryMessage[] = [];
let historyMessages: HistoryMessage[] = [];
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
const userId = (inputs.user_id && uuidRegex.test(inputs.user_id)) ? inputs.user_id : crypto.randomUUID();
const rawUserId = inputs.user_id;
const localConvId = localConversationId || crypto.randomUUID(); // 前端没传则后端生成,保障多轮记忆自给自足
const userIdPassed = !!(rawUserId && uuidRegex.test(rawUserId));
const userId = userIdPassed ? rawUserId : crypto.randomUUID();
const localConvIdPassed = !!(localConversationId && uuidRegex.test(localConversationId));
const localConvId = localConvIdPassed ? localConversationId! : crypto.randomUUID();
console.log('🔑 [ai-chat-stream] ID resolution:', {
rawUserId,
userIdPassed,
userId_used: userId,
rawLocalConvId: localConversationId,
localConvIdPassed,
localConvId_used: localConvId,
});
if (localConvId && userId) {
if (localConvId && userId) {
historyMessages = await loadConversationHistory(supabase, localConvId, userId, 20);
historyMessages = await loadConversationHistory(supabase, localConvId, userId, 20);
...
@@ -478,18 +494,29 @@ Deno.serve(async (req) => {
...
@@ -478,18 +494,29 @@ Deno.serve(async (req) => {
.
map
(
c
=>
({
id
:
c
.
id
,
name
:
c
.
name
}));
.
map
(
c
=>
({
id
:
c
.
id
,
name
:
c
.
name
}));
console
.
log
(
'🔎 [ai-chat-stream] Product IDs mentioned in text:'
,
JSON
.
stringify
(
mentionedIds
));
console
.
log
(
'🔎 [ai-chat-stream] Product IDs mentioned in text:'
,
JSON
.
stringify
(
mentionedIds
));
// ─── 保存消息到数据库
1
(服务端写入,保障多轮记忆)───
// ─── 保存消息到数据库(服务端写入,保障多轮记忆)───
if
(
localConvId
&&
userId
)
{
if
(
localConvId
&&
userId
)
{
const
baseTime
=
new
Date
();
const
baseTime
=
new
Date
();
const
userTs
=
baseTime
.
toISOString
();
const
userTs
=
baseTime
.
toISOString
();
const
aiTs
=
new
Date
(
baseTime
.
getTime
()
+
1
).
toISOString
();
// +1ms 保证顺序
const
aiTs
=
new
Date
(
baseTime
.
getTime
()
+
1
).
toISOString
();
// +1ms 保证顺序
console
.
log
(
'💾 [ai-chat-stream] 准备写入 DB:'
,
{
conversation_id
:
localConvId
,
user_id
:
userId
,
user_msg_len
:
message
.
length
,
ai_msg_len
:
fullAnswer
.
length
,
});
// 确保 conversations 表有对应行(访客场景下前端只写 localStorage,FK 约束会拒绝 messages 写入)
// 确保 conversations 表有对应行(访客场景下前端只写 localStorage,FK 约束会拒绝 messages 写入)
const
{
error
:
upsertConvError
}
=
await
supabase
.
from
(
'conversations'
).
upsert
({
const
{
error
:
upsertConvError
}
=
await
supabase
.
from
(
'conversations'
).
upsert
({
id
:
localConvId
,
id
:
localConvId
,
user_id
:
userId
,
user_id
:
userId
,
},
{
onConflict
:
'id'
});
},
{
onConflict
:
'id'
});
if
(
upsertConvError
)
console
.
error
(
'[ai-chat-stream] upsert conversations 失败:'
,
upsertConvError
);
if
(
upsertConvError
)
{
console
.
error
(
'❌ [ai-chat-stream] upsert conversations 失败:'
,
JSON
.
stringify
(
upsertConvError
));
}
else
{
console
.
log
(
'✅ [ai-chat-stream] conversations 已 upsert'
);
}
// 保存用户消息
// 保存用户消息
const
{
error
:
saveUserError
}
=
await
supabase
.
from
(
'messages'
).
insert
({
const
{
error
:
saveUserError
}
=
await
supabase
.
from
(
'messages'
).
insert
({
...
@@ -500,7 +527,11 @@ Deno.serve(async (req) => {
...
@@ -500,7 +527,11 @@ Deno.serve(async (req) => {
message_type
:
'text'
,
message_type
:
'text'
,
created_at
:
userTs
,
created_at
:
userTs
,
});
});
if
(
saveUserError
)
console
.
error
(
'[ai-chat-stream] 保存用户消息失败:'
,
saveUserError
,
{
code
:
saveUserError
?.
code
});
if
(
saveUserError
)
{
console
.
error
(
'❌ [ai-chat-stream] 保存用户消息失败:'
,
JSON
.
stringify
(
saveUserError
));
}
else
{
console
.
log
(
'✅ [ai-chat-stream] 用户消息已保存'
);
}
// 保存 AI 回复
// 保存 AI 回复
const
{
error
:
saveAiError
}
=
await
supabase
.
from
(
'messages'
).
insert
({
const
{
error
:
saveAiError
}
=
await
supabase
.
from
(
'messages'
).
insert
({
...
@@ -512,8 +543,13 @@ Deno.serve(async (req) => {
...
@@ -512,8 +543,13 @@ Deno.serve(async (req) => {
metadata
:
{
picks
,
intent
,
highlights
,
title
},
metadata
:
{
picks
,
intent
,
highlights
,
title
},
created_at
:
aiTs
,
created_at
:
aiTs
,
});
});
if
(
saveAiError
)
console
.
error
(
'[ai-chat-stream] 保存 AI 回复失败:'
,
saveAiError
,
{
code
:
saveAiError
?.
code
});
if
(
saveAiError
)
{
else
console
.
log
(
'💾 [ai-chat-stream] 消息已保存到 DB'
);
console
.
error
(
'❌ [ai-chat-stream] 保存 AI 回复失败:'
,
JSON
.
stringify
(
saveAiError
));
}
else
{
console
.
log
(
'✅ [ai-chat-stream] AI 回复已保存'
);
}
}
else
{
console
.
error
(
'❌ [ai-chat-stream] 跳过 DB 写入 — localConvId 或 userId 为空:'
,
{
localConvId
,
userId
});
}
}
// ─── 发送最终元数据 ───
// ─── 发送最终元数据 ───
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment