feat(iod): 重构数联网搜索功能

- 新增数联网设置页面
- 优化数联网搜索结果展示
- 添加数据集、科创场景和科技企业等不同类型的搜索结果
- 重构搜索结果卡片组件,支持加载状态和不同展示模式
- 更新数联网搜索相关的国际化文案
This commit is contained in:
zhaoweijie
2025-08-22 17:15:19 +08:00
parent efbf2a3eff
commit 17020e8755
33 changed files with 1321 additions and 773 deletions

View File

@@ -1,8 +1,14 @@
import { saveHistory, saveMessage } from "@/db"
import { setLastUsedChatModel, setLastUsedChatSystemPrompt } from "@/services/model-settings"
import {
setLastUsedChatModel,
setLastUsedChatSystemPrompt
} from "@/services/model-settings"
import { generateTitle } from "@/services/title"
import { ChatHistory } from "@/store/option"
import { updateDialog } from "@/web/iod"
import { AllIodRegistryEntry } from "@/types/iod.ts"
import { getDefaultIodSources } from "@/libs/iod.ts"
export const saveMessageOnError = async ({
e,
history,
@@ -62,7 +68,7 @@ export const saveMessageOnError = async ({
userMessage,
[image],
[],
[],
getDefaultIodSources(),
1,
message_type
)
@@ -74,13 +80,16 @@ export const saveMessageOnError = async ({
botMessage,
[],
[],
[],
getDefaultIodSources(),
2,
message_type
)
await setLastUsedChatModel(historyId, selectedModel)
if (prompt_id || prompt_content) {
await setLastUsedChatSystemPrompt(historyId, { prompt_content, prompt_id })
await setLastUsedChatSystemPrompt(historyId, {
prompt_content,
prompt_id
})
}
} else {
const title = await generateTitle(selectedModel, userMessage, userMessage)
@@ -93,7 +102,7 @@ export const saveMessageOnError = async ({
userMessage,
[image],
[],
[],
getDefaultIodSources(),
1,
message_type
)
@@ -105,14 +114,17 @@ export const saveMessageOnError = async ({
botMessage,
[],
[],
[],
getDefaultIodSources(),
2,
message_type
)
setHistoryId(newHistoryId.id)
await setLastUsedChatModel(newHistoryId.id, selectedModel)
if (prompt_id || prompt_content) {
await setLastUsedChatSystemPrompt(newHistoryId.id, { prompt_content, prompt_id })
await setLastUsedChatSystemPrompt(newHistoryId.id, {
prompt_content,
prompt_id
})
}
}
@@ -133,7 +145,8 @@ export const saveMessageOnSuccess = async ({
webSources,
iodSources,
message_source = "web-ui",
message_type, generationInfo,
message_type,
generationInfo,
prompt_id,
prompt_content,
reasoning_time_taken = 0
@@ -146,15 +159,15 @@ export const saveMessageOnSuccess = async ({
image: string
fullText: string
webSources: any[]
iodSources: any[]
message_source?: "copilot" | "web-ui",
iodSources: AllIodRegistryEntry
message_source?: "copilot" | "web-ui"
message_type?: string
generationInfo?: any
prompt_id?: string
prompt_content?: string
reasoning_time_taken?: number
}) => {
var botMessage;
var botMessage
if (historyId) {
if (!isRegenerate) {
await saveMessage(
@@ -164,7 +177,7 @@ export const saveMessageOnSuccess = async ({
message,
[image],
[],
[],
getDefaultIodSources(),
1,
message_type,
generationInfo,
@@ -187,7 +200,10 @@ export const saveMessageOnSuccess = async ({
updateDialog(historyId, botMessage)
await setLastUsedChatModel(historyId, selectedModel!)
if (prompt_id || prompt_content) {
await setLastUsedChatSystemPrompt(historyId, { prompt_content, prompt_id })
await setLastUsedChatSystemPrompt(historyId, {
prompt_content,
prompt_id
})
}
} else {
const title = await generateTitle(selectedModel, message, message)
@@ -199,7 +215,7 @@ export const saveMessageOnSuccess = async ({
message,
[image],
[],
[],
getDefaultIodSources(),
1,
message_type,
generationInfo,
@@ -222,7 +238,10 @@ export const saveMessageOnSuccess = async ({
setHistoryId(newHistoryId.id)
await setLastUsedChatModel(newHistoryId.id, selectedModel!)
if (prompt_id || prompt_content) {
await setLastUsedChatSystemPrompt(newHistoryId.id, { prompt_content, prompt_id })
await setLastUsedChatSystemPrompt(newHistoryId.id, {
prompt_content,
prompt_id
})
}
}
}

View File

@@ -11,7 +11,6 @@ import { useStoreMessageOption, type Message } from "~/store/option"
import { useStoreMessage } from "~/store"
import { SystemMessage } from "@langchain/core/messages"
import { getDataFromCurrentTab } from "~/libs/get-html"
import { MemoryVectorStore } from "langchain/vectorstores/memory"
import { memoryEmbedding } from "@/utils/memory-embeddings"
import { ChatHistory } from "@/store/option"
import {
@@ -42,6 +41,8 @@ import {
mergeReasoningContent,
removeReasoning
} from "@/libs/reasoning"
import { AllIodRegistryEntry } from "@/types/iod.ts"
import { getDefaultIodSources } from "@/libs/iod.ts"
export const useMessage = () => {
const {
@@ -188,15 +189,15 @@ export const useMessage = () => {
name: "You",
message,
webSources: [],
iodSources: [],
iodSources: getDefaultIodSources(),
images: []
},
{
isBot: true,
name: selectedModel,
message: "",
message: "",
webSources: [],
iodSources: [],
iodSources: getDefaultIodSources(),
id: generateMessageId
}
]
@@ -208,7 +209,7 @@ export const useMessage = () => {
name: selectedModel,
message: "▋",
webSources: [],
iodSources: [],
iodSources: getDefaultIodSources(),
id: generateMessageId
}
]
@@ -242,6 +243,7 @@ export const useMessage = () => {
}
isAlreadyExistEmbedding = keepTrackOfEmbedding[websiteUrl]
}
setMessages(newMessage)
const ollamaUrl = await getOllamaURL()
const embeddingModle = await defaultEmbeddingModelForRag()
@@ -348,14 +350,7 @@ export const useMessage = () => {
metadata: Record<string, any>
}[] = []
// TODO: update type
let iodSources: {
name: any
type: any
mode: string
url: string
pageContent: string
metadata: Record<string, any>
}[] = []
let iodSources: AllIodRegistryEntry = getDefaultIodSources()
if (chatWithWebsiteEmbedding) {
const docs = await vectorstore.similaritySearch(query, 4)
@@ -623,7 +618,7 @@ export const useMessage = () => {
name: "You",
message,
webSources: [],
iodSources: [],
iodSources: getDefaultIodSources(),
images: []
},
{
@@ -631,7 +626,7 @@ export const useMessage = () => {
name: selectedModel,
message: "▋",
webSources: [],
iodSources: [],
iodSources: getDefaultIodSources(),
id: generateMessageId
}
]
@@ -643,7 +638,7 @@ export const useMessage = () => {
name: selectedModel,
message: "▋",
webSources: [],
iodSources: [],
iodSources: getDefaultIodSources(),
id: generateMessageId
}
]
@@ -807,7 +802,7 @@ export const useMessage = () => {
image,
fullText,
webSources: [],
iodSources: [],
iodSources: getDefaultIodSources(),
message_source: "copilot",
generationInfo,
reasoning_time_taken: timetaken
@@ -912,7 +907,7 @@ export const useMessage = () => {
name: "You",
message,
webSources: [],
iodSources: [],
iodSources: getDefaultIodSources(),
images: [image]
},
{
@@ -920,7 +915,7 @@ export const useMessage = () => {
name: selectedModel,
message: "▋",
webSources: [],
iodSources: [],
iodSources: getDefaultIodSources(),
id: generateMessageId
}
]
@@ -932,7 +927,7 @@ export const useMessage = () => {
name: selectedModel,
message: "▋",
webSources: [],
iodSources: [],
iodSources: getDefaultIodSources(),
id: generateMessageId
}
]
@@ -1101,7 +1096,7 @@ export const useMessage = () => {
image,
fullText,
webSources: [],
iodSources: [],
iodSources: getDefaultIodSources(),
message_source: "copilot",
generationInfo,
reasoning_time_taken: timetaken
@@ -1145,7 +1140,7 @@ export const useMessage = () => {
isRegenerate: boolean,
messages: Message[],
history: ChatHistory,
signal: AbortSignal,
signal: AbortSignal
) => {
const url = await getOllamaURL()
setStreaming(true)
@@ -1203,7 +1198,7 @@ export const useMessage = () => {
name: "You",
message,
webSources: [],
iodSources: [],
iodSources: getDefaultIodSources(),
images: [image]
},
{
@@ -1211,7 +1206,7 @@ export const useMessage = () => {
name: selectedModel,
message: "▋",
webSources: [],
iodSources: [],
iodSources: getDefaultIodSources(),
id: generateMessageId
}
]
@@ -1223,7 +1218,7 @@ export const useMessage = () => {
name: selectedModel,
message: "▋",
webSources: [],
iodSources: [],
iodSources: getDefaultIodSources(),
id: generateMessageId
}
]
@@ -1300,8 +1295,12 @@ export const useMessage = () => {
query = removeReasoning(query)
}
const { prompt, webSources, iodSources } =
await getSystemPromptForWeb(query, [], webSearch, iodSearch)
const { prompt, webSources, iodSources } = await getSystemPromptForWeb(
query,
[],
webSearch,
iodSearch
)
setIsSearchingInternet(false)
// message = message.trim().replaceAll("\n", " ")
@@ -1556,7 +1555,7 @@ export const useMessage = () => {
name: "You",
message,
webSources: [],
iodSources: [],
iodSources: getDefaultIodSources(),
images: [image],
messageType: messageType
},
@@ -1565,7 +1564,7 @@ export const useMessage = () => {
name: selectedModel,
message: "▋",
webSources: [],
iodSources: [],
iodSources: getDefaultIodSources(),
id: generateMessageId
}
]
@@ -1577,7 +1576,7 @@ export const useMessage = () => {
name: selectedModel,
message: "▋",
webSources: [],
iodSources: [],
iodSources: getDefaultIodSources(),
id: generateMessageId
}
]
@@ -1724,7 +1723,7 @@ export const useMessage = () => {
image,
fullText,
webSources: [],
iodSources: [],
iodSources: getDefaultIodSources(),
message_source: "copilot",
message_type: messageType,
generationInfo,
@@ -1811,7 +1810,7 @@ export const useMessage = () => {
isRegenerate || false,
messages,
memory || history,
signal,
signal
)
} else {
await normalChatMode(

View File

@@ -2,15 +2,14 @@ import React from "react"
import { cleanUrl } from "~/libs/clean-url"
import {
defaultEmbeddingModelForRag,
geWebSearchFollowUpPrompt,
geWebSearchKeywordsPrompt,
getOllamaURL,
geWebSearchFollowUpPrompt,
promptForRag,
systemPromptForNonRagOption
} from "~/services/ollama"
import type { ChatHistory, Message, MeteringEntry } from "~/store/option"
import { SystemMessage } from "@langchain/core/messages"
import { useStoreMessageOption } from "~/store/option"
import { SystemMessage } from "@langchain/core/messages"
import {
deleteChatForEdit,
generateID,
@@ -47,14 +46,20 @@ import {
mergeReasoningContent,
removeReasoning
} from "@/libs/reasoning"
import { getDefaultIodSources } from "@/libs/iod.ts"
export const useMessageOption = () => {
const {
controller: abortController,
setController: setAbortController,
iodLoading,
setIodLoading,
currentMessageId,
setCurrentMessageId,
messages,
setMessages
setMessages,
} = usePageAssist()
const {
history,
setHistory,
@@ -113,6 +118,8 @@ export const useMessageOption = () => {
setIsProcessing(false)
setStreaming(false)
currentChatModelSettings.reset()
setIodLoading(false)
setCurrentMessageId("")
textareaRef?.current?.focus()
if (defaultInternetSearchOn) {
setWebSearch(true)
@@ -195,6 +202,7 @@ export const useMessageOption = () => {
})
let newMessage: Message[] = []
let generateMessageId = generateID()
setCurrentMessageId(generateMessageId)
const meter: MeteringEntry = {
id: generateMessageId,
queryContent: message,
@@ -214,15 +222,15 @@ export const useMessageOption = () => {
name: "You",
message,
webSources: [],
iodSources: [],
iodSources: getDefaultIodSources(),
images: [image]
},
{
isBot: true,
name: selectedModel,
message: "",
message: "",
webSources: [],
iodSources: [],
iodSources: getDefaultIodSources(),
id: generateMessageId
}
]
@@ -234,7 +242,7 @@ export const useMessageOption = () => {
name: selectedModel,
message: "▋",
webSources: [],
iodSources: [],
iodSources: getDefaultIodSources(),
id: generateMessageId
}
]
@@ -316,7 +324,9 @@ export const useMessageOption = () => {
// Currently only IoD search use keywords
if (iodSearch) {
// Extract keywords
console.log("query:"+query+" --> "+JSON.stringify(tokenizeInput(query)));
console.log(
"query:" + query + " --> " + JSON.stringify(tokenizeInput(query))
)
keywords = tokenizeInput(query)
/*
const questionPrompt = await geWebSearchKeywordsPrompt()
@@ -335,15 +345,35 @@ export const useMessageOption = () => {
*/
}
const { prompt, webSources, iodSources, iodSearchResults: iodData, iodTokenCount } =
await getSystemPromptForWeb(query, keywords, webSearch, iodSearch)
const {
prompt,
webSources,
iodSources,
iodSearchResults: iodData,
iodTokenCount
} = await getSystemPromptForWeb(query, keywords, webSearch, iodSearch)
setIodLoading(false)
console.log("prompt:\n" + prompt)
setIsSearchingInternet(false)
meter.prompt = prompt
meter.iodKeywords = keywords
meter.iodData = iodData
meter.iodData = Object.values(iodData).flat()
meter.iodTokenCount = iodTokenCount
setMessages((prev) => {
return prev.map((message) => {
if (message.id === generateMessageId) {
return {
...message,
webSources,
iodSources,
}
}
return message
})
})
// message = message.trim().replaceAll("\n", " ")
let humanMessage = await humanMessageFormatter({
@@ -511,20 +541,17 @@ export const useMessageOption = () => {
modelInputTokenCount: prompt.length,
modelOutputTokenCount: fullText.length,
model: ollama.modelName ?? ollama.model,
relatedDataCount: iodData?.length ?? 0,
relatedDataCount: Object.values(iodData).flat()?.length ?? 0,
timeTaken: new Date().getTime() - chatStartTime.getTime(),
date: chatStartTime.getTime(),
cot,
responseContent: content,
modelResponseContent: fullText,
modelResponseContent: fullText
}
const _meteringEntries = [
currentMeteringEntry,
...meteringEntries,
]
const _meteringEntries = [currentMeteringEntry, ...meteringEntries]
setCurrentMeteringEntry({
loading: false,
data: currentMeteringEntry,
data: currentMeteringEntry
})
setMeteringEntries(_meteringEntries)
localStorage.setItem("meteringEntries", JSON.stringify(_meteringEntries))
@@ -653,7 +680,7 @@ export const useMessageOption = () => {
setCurrentMeteringEntry({
loading: true,
data: meter,
data: meter
})
if (!isRegenerate) {
@@ -664,7 +691,7 @@ export const useMessageOption = () => {
name: "You",
message,
webSources: [],
iodSources: [],
iodSources: getDefaultIodSources(),
images: [image]
},
{
@@ -672,7 +699,7 @@ export const useMessageOption = () => {
name: selectedModel,
message: "▋",
webSources: [],
iodSources: [],
iodSources: getDefaultIodSources(),
id: generateMessageId
}
]
@@ -684,7 +711,7 @@ export const useMessageOption = () => {
name: selectedModel,
message: "▋",
webSources: [],
iodSources: [],
iodSources: getDefaultIodSources(),
id: generateMessageId
}
]
@@ -883,7 +910,6 @@ export const useMessageOption = () => {
setIsProcessing(false)
setStreaming(false)
// Save metering entry
const { cot, content } = responseResolver(fullText)
const currentMeteringEntry = {
@@ -891,20 +917,17 @@ export const useMessageOption = () => {
modelInputTokenCount: prompt.length,
modelOutputTokenCount: fullText.length,
model: ollama.modelName ?? ollama.model,
relatedDataCount: 0,
relatedDataCount: 0,
timeTaken: new Date().getTime() - chatStartTime.getTime(),
date: chatStartTime.getTime(),
cot,
responseContent: content,
modelResponseContent: fullText,
modelResponseContent: fullText
}
const _meteringEntries = [
currentMeteringEntry,
...meteringEntries,
]
const _meteringEntries = [currentMeteringEntry, ...meteringEntries]
setCurrentMeteringEntry({
loading: false,
data: currentMeteringEntry,
data: currentMeteringEntry
})
setMeteringEntries(_meteringEntries)
} catch (e) {
@@ -996,7 +1019,7 @@ export const useMessageOption = () => {
name: "You",
message,
webSources: [],
iodSources: [],
iodSources: getDefaultIodSources(),
images: []
},
{
@@ -1004,7 +1027,7 @@ export const useMessageOption = () => {
name: selectedModel,
message: "▋",
webSources: [],
iodSources: [],
iodSources: getDefaultIodSources(),
id: generateMessageId
}
]
@@ -1016,7 +1039,7 @@ export const useMessageOption = () => {
name: selectedModel,
message: "▋",
webSources: [],
iodSources: [],
iodSources: getDefaultIodSources(),
id: generateMessageId
}
]
@@ -1319,6 +1342,7 @@ export const useMessageOption = () => {
)
} else {
if (webSearch || iodSearch) {
setIodLoading(iodSearch)
await searchChatMode(
webSearch,
iodSearch,
@@ -1435,6 +1459,10 @@ export const useMessageOption = () => {
editMessage,
messages,
setMessages,
iodLoading,
currentMessageId,
setIodLoading,
setCurrentMessageId,
onSubmit,
setStreaming,
streaming,