feat: add IoD search

This commit is contained in:
Nex Zhu 2025-02-14 18:17:12 +08:00
parent 691575e449
commit e8471f1802
33 changed files with 524 additions and 104 deletions

View File

@ -109,7 +109,7 @@
"translate": "ترجمة",
"custom": "مخصص"
},
"citations": "الاقتباسات",
"webCitations": "الاقتباسات",
"segmented": {
"ollama": "نماذج Ollama",
"custom": "نماذج مخصصة"

View File

@ -106,7 +106,7 @@
"translate": "Oversæt",
"custom": "Brugerdefineret"
},
"citations": "Citater",
"webCitations": "Citater",
"downloadCode": "Download Kode",
"date": {
"pinned": "Fastgjort",

View File

@ -106,7 +106,7 @@
"translate": "Übersetzen",
"custom": "Benutzerdefiniert"
},
"citations": "Zitate",
"webCitations": "Zitate",
"downloadCode": "Code herunterladen",
"date": {
"pinned": "Angepinnt",

View File

@ -39,6 +39,7 @@
},
"copyToClipboard": "Copy to clipboard",
"webSearch": "Searching the web",
"iodSearch": "Searching the Internet of Data",
"regenerate": "Regenerate",
"edit": "Edit",
"delete": "Delete",
@ -136,7 +137,8 @@
"translate": "Translate",
"custom": "Custom"
},
"citations": "Citations",
"webCitations": "Web Citations",
"iodCitations": "Internet of Data Citations",
"segmented": {
"ollama": "Ollama Models",
"custom": "Custom Models"

View File

@ -20,6 +20,7 @@
},
"tooltip": {
"searchInternet": "Search Internet",
"searchIod": "Search Internet of Data",
"speechToText": "Speech to Text",
"uploadImage": "Upload Image",
"stopStreaming": "Stop Streaming",

View File

@ -105,7 +105,7 @@
"rephrase": "Reformular",
"translate": "Traducir"
},
"citations": "Citas",
"webCitations": "Citas",
"downloadCode": "Descargar Código",
"date": {
"pinned": "Fijado",

View File

@ -99,7 +99,7 @@
},
"advanced": "تنظیمات بیشتر مدل"
},
"citations": "منابع",
"webCitations": "منابع",
"downloadCode": "دانلود کد",
"date": {
"pinned": "پین شده",

View File

@ -105,7 +105,7 @@
"rephrase": "Reformuler",
"translate": "Traduire"
},
"citations": "Citations",
"webCitations": "Citations",
"downloadCode": "Télécharger le code",
"date": {
"pinned": "Épinglé",

View File

@ -105,7 +105,7 @@
"rephrase": "Riformulare",
"translate": "Tradurre"
},
"citations": "Citazioni",
"webCitations": "Citazioni",
"downloadCode": "Scarica Codice",
"date": {
"pinned": "Fissato",

View File

@ -105,7 +105,7 @@
"rephrase": "言い換え",
"translate": "翻訳"
},
"citations": "引用",
"webCitations": "引用",
"downloadCode": "コードをダウンロード",
"date": {
"pinned": "固定",

View File

@ -105,7 +105,7 @@
"rephrase": "다르게 표현",
"translate": "번역"
},
"citations": "인용",
"webCitations": "인용",
"downloadCode": "코드 다운로드",
"date": {
"pinned": "고정됨",

View File

@ -104,7 +104,7 @@
"rephrase": "പുനഃരൂപീകരിക്കുക",
"translate": "വിവർത്തനം ചെയ്യുക"
},
"citations": "ഉദ്ധരണികൾ",
"webCitations": "ഉദ്ധരണികൾ",
"downloadCode": "കോഡ് ഡൗൺലോഡ് ചെയ്യുക",
"date": {
"pinned": "പിൻ ചെയ്തത്",

View File

@ -106,7 +106,7 @@
"translate": "Oversett",
"custom": "Egendefinert"
},
"citations": "Sitater",
"webCitations": "Sitater",
"downloadCode": "Last ned kode",
"date": {
"pinned": "Festet",

View File

@ -105,7 +105,7 @@
"rephrase": "Reformular",
"translate": "Traduzir"
},
"citations": "Citações",
"webCitations": "Citações",
"downloadCode": "Baixar Código",
"date": {
"pinned": "Fixado",

View File

@ -105,7 +105,7 @@
"rephrase": "Перефразировать",
"translate": "Перевести"
},
"citations": "Цитаты",
"webCitations": "Цитаты",
"downloadCode": "Скачать код",
"date": {
"pinned": "Закреплено",

View File

@ -106,7 +106,7 @@
"translate": "Översätt",
"custom": "Custom"
},
"citations": "Citat",
"webCitations": "Citat",
"segmented": {
"ollama": "Ollama-modeller",
"custom": "Custom modeller"

View File

@ -106,7 +106,7 @@
"translate": "Перекласти",
"custom": "Власне"
},
"citations": "Цитати",
"webCitations": "Цитати",
"segmented": {
"ollama": "Моделі Ollama",
"custom": "Власні моделі"

View File

@ -38,7 +38,8 @@
}
},
"copyToClipboard": "复制到剪贴板",
"webSearch": "搜索网络",
"webSearch": "搜索万维网",
"iodSearch": "搜索数联网",
"regenerate": "重新生成",
"edit": "编辑",
"delete": "删除",
@ -105,7 +106,8 @@
"rephrase": "重述",
"translate": "翻译"
},
"citations": "引用",
"webCitations": "万维网引用",
"iodCitations": "数联网引用",
"downloadCode": "下载代码",
"date": {
"pinned": "已置顶",

View File

@ -19,7 +19,8 @@
}
},
"tooltip": {
"searchInternet": "搜索互联网",
"searchInternet": "搜索万维网",
"searchIod": "搜索数联网",
"speechToText": "语音到文本",
"uploadImage": "上传图片",
"stopStreaming": "停止流媒体",

View File

@ -18,7 +18,7 @@ import { useTTS } from "@/hooks/useTTS"
import { tagColors } from "@/utils/color"
import { removeModelSuffix } from "@/db/models"
import { GenerationInfo } from "./GenerationInfo"
import { parseReasoning, } from "@/libs/reasoning"
import { parseReasoning } from "@/libs/reasoning"
import { humanizeMilliseconds } from "@/utils/humanize-milliseconds"
type Props = {
message: string
@ -36,7 +36,8 @@ type Props = {
isProcessing: boolean
webSearch?: {}
isSearchingInternet?: boolean
sources?: any[]
webSources?: any[]
iodSources?: any[]
hideEditAndRegenerate?: boolean
onSourceClick?: (source: any) => void
isTTSEnabled?: boolean
@ -166,7 +167,7 @@ export const PlaygroundMessage = (props: Props) => {
</div>
)}
{props.isBot && props?.sources && props?.sources.length > 0 && (
{props.isBot && props?.webSources && props?.webSources.length > 0 && (
<Collapse
className="mt-6"
ghost
@ -175,15 +176,44 @@ export const PlaygroundMessage = (props: Props) => {
key: "1",
label: (
<div className="italic text-gray-500 dark:text-gray-400">
{t("citations")}
{t("webCitations")}
</div>
),
children: (
<div className="mb-3 flex flex-wrap gap-2">
{props?.sources?.map((source, index) => (
{props?.webSources?.map((source, index) => (
<MessageSource
onSourceClick={props.onSourceClick}
key={index}
index={index}
source={source}
/>
))}
</div>
)
}
]}
/>
)}
{props.isBot && props?.iodSources && props?.iodSources.length > 0 && (
<Collapse
className="mt-6"
ghost
items={[
{
key: "1",
label: (
<div className="italic text-gray-500 dark:text-gray-400">
{t("iodCitations")}
</div>
),
children: (
<div className="mb-3 flex flex-wrap gap-2">
{props?.iodSources?.map((source, index) => (
<MessageSource
onSourceClick={props.onSourceClick}
key={index}
index={index}
source={source}
/>
))}

View File

@ -1,6 +1,9 @@
import { useState } from "react"
import type React from "react"
import { KnowledgeIcon } from "@/components/Option/Knowledge/KnowledgeIcon"
type Props = {
index: number
source: {
name?: string
url?: string
@ -8,11 +11,20 @@ type Props = {
type?: string
pageContent?: string
content?: string
doId?: string
description?: string
}
onSourceClick?: (source: any) => void
}
export const MessageSource: React.FC<Props> = ({ source, onSourceClick }) => {
export const MessageSource: React.FC<Props> = ({
index,
source,
onSourceClick
}) => {
// Add state for tracking and content visibility
const [showContent, setShowContent] = useState(false)
if (source?.mode === "rag" || source?.mode === "chat") {
return (
<button
@ -26,12 +38,46 @@ export const MessageSource: React.FC<Props> = ({ source, onSourceClick }) => {
)
}
const onContextMenu = (e: React.MouseEvent) => {
e.preventDefault()
e.stopPropagation
setShowContent(true)
}
return (
<div className="block items-center gap-1 text-xs text-gray-800 dark:text-gray-100 mb-1">
<span className="text-xs font-medium"></span>{" "}
<a
href={source?.url}
target="_blank"
className="inline-flex cursor-pointer transition-shadow duration-300 ease-in-out hover:shadow-lg items-center rounded-md bg-gray-100 p-1 text-xs text-gray-800 border border-gray-300 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-100 opacity-80 hover:opacity-100">
onContextMenu={onContextMenu}
className="inline-block cursor-pointer transition-shadow duration-300 ease-in-out hover:shadow-lg items-center rounded-md bg-gray-100 p-1 text-xs text-gray-800 border border-gray-300 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-100 opacity-80 hover:opacity-100">
{source.doId ? (
<>
<span className="text-xs">
[{index + 1}] doid: {source.doId}
</span>
<br />
<span className="text-xs">{source.name}</span>
{showContent && (
<div className="mt-2 p-2 border-t border-gray-200 dark:border-gray-700">
{source.content || source.pageContent || source.description}
</div>
)}
</>
) : (
<>
<span className="text-xs">
[{index + 1}] {source.name}
</span>
{showContent && (
<div className="mt-2 p-2 border-t border-gray-200 dark:border-gray-700">
{source.content || source.pageContent}
</div>
)}
</>
)}
</a>
</div>
)
}

View File

@ -36,7 +36,8 @@ export const PlaygroundChat = () => {
onRengerate={regenerateLastMessage}
isProcessing={streaming}
isSearchingInternet={isSearchingInternet}
sources={message.sources}
webSources={message.webSources}
iodSources={message.iodSources}
onEditFormSubmit={(value, isSend) => {
editMessage(index, value, !message.isBot, isSend)
}}

View File

@ -13,7 +13,7 @@ import { getVariable } from "@/utils/select-variable"
import { useTranslation } from "react-i18next"
import { KnowledgeSelect } from "../Knowledge/KnowledgeSelect"
import { useSpeechRecognition } from "@/hooks/useSpeechRecognition"
import { PiGlobe } from "react-icons/pi"
import { PiGlobe, PiNetwork } from "react-icons/pi"
import { handleChatInputKeyDown } from "@/utils/key-down"
import { getIsSimpleInternetSearch } from "@/services/search"
@ -34,6 +34,8 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
streaming: isSending,
webSearch,
setWebSearch,
iodSearch,
setIodSearch,
selectedQuickPrompt,
textareaRef,
setSelectedQuickPrompt,
@ -301,6 +303,7 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
<div className="mt-2 flex justify-between items-center">
<div className="flex">
{!selectedKnowledge && (
<div>
<Tooltip title={t("tooltip.searchInternet")}>
<div className="inline-flex items-center gap-2">
<PiGlobe
@ -314,6 +317,20 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
/>
</div>
</Tooltip>
<Tooltip title={t("tooltip.searchIod")} className="ml-3">
<div className="inline-flex items-center gap-2">
<PiNetwork
className={`h-5 w-5 dark:text-gray-300 `}
/>
<Switch
value={iodSearch}
onChange={(e) => setIodSearch(e)}
checkedChildren={t("form.webSearch.on")}
unCheckedChildren={t("form.webSearch.off")}
/>
</div>
</Tooltip>
</div>
)}
</div>
<div className="flex !justify-end gap-3">

View File

@ -39,7 +39,8 @@ export const SidePanelBody = () => {
message_type={message.messageType}
isProcessing={streaming}
isSearchingInternet={isSearchingInternet}
sources={message.sources}
webSources={message.webSources}
iodSources={message.iodSources}
onEditFormSubmit={(value) => {
editMessage(index, value, !message.isBot)
}}

View File

@ -29,7 +29,8 @@ type Message = {
role: string
content: string
images?: string[]
sources?: string[]
webSources?: string[]
iodSources?: string[]
search?: WebSearch
createdAt: number
reasoning_time_taken?: number
@ -254,7 +255,8 @@ export const saveMessage = async (
role: string,
content: string,
images: string[],
source?: any[],
webSources?: any[],
iodSources?: any[],
time?: number,
message_type?: string,
generationInfo?: any,
@ -273,7 +275,8 @@ export const saveMessage = async (
content,
images,
createdAt,
sources: source,
webSources,
iodSources,
messageType: message_type,
generationInfo: generationInfo,
reasoning_time_taken
@ -303,7 +306,8 @@ export const formatToMessage = (messages: MessageHistory): MessageType[] => {
isBot: message.role === "assistant",
message: message.content,
name: message.name,
sources: message?.sources || [],
webSources: message?.webSources || [],
iodSources: message?.iodSources || [],
images: message.images || [],
generationInfo: message?.generationInfo,
reasoning_time_taken: message?.reasoning_time_taken

View File

@ -62,6 +62,7 @@ export const saveMessageOnError = async ({
userMessage,
[image],
[],
[],
1,
message_type
)
@ -73,6 +74,7 @@ export const saveMessageOnError = async ({
botMessage,
[],
[],
[],
2,
message_type
)
@ -91,6 +93,7 @@ export const saveMessageOnError = async ({
userMessage,
[image],
[],
[],
1,
message_type
)
@ -102,6 +105,7 @@ export const saveMessageOnError = async ({
botMessage,
[],
[],
[],
2,
message_type
)
@ -126,7 +130,8 @@ export const saveMessageOnSuccess = async ({
message,
image,
fullText,
source,
webSources,
iodSources,
message_source = "web-ui",
message_type, generationInfo,
prompt_id,
@ -140,7 +145,8 @@ export const saveMessageOnSuccess = async ({
message: string
image: string
fullText: string
source: any[]
webSources: any[]
iodSources: any[]
message_source?: "copilot" | "web-ui",
message_type?: string
generationInfo?: any
@ -157,6 +163,7 @@ export const saveMessageOnSuccess = async ({
message,
[image],
[],
[],
1,
message_type,
generationInfo,
@ -169,7 +176,8 @@ export const saveMessageOnSuccess = async ({
"assistant",
fullText,
[],
source,
webSources,
iodSources,
2,
message_type,
generationInfo,
@ -189,6 +197,7 @@ export const saveMessageOnSuccess = async ({
message,
[image],
[],
[],
1,
message_type,
generationInfo,
@ -200,7 +209,8 @@ export const saveMessageOnSuccess = async ({
"assistant",
fullText,
[],
source,
webSources,
iodSources,
2,
message_type,
generationInfo,

View File

@ -59,6 +59,8 @@ export const useMessage = () => {
setIsSearchingInternet,
webSearch,
setWebSearch,
iodSearch,
setIodSearch,
isSearchingInternet
} = useStoreMessageOption()
const [defaultInternetSearchOn] = useStorage("defaultInternetSearchOn", false)
@ -185,14 +187,16 @@ export const useMessage = () => {
isBot: false,
name: "You",
message,
sources: [],
webSources: [],
iodSources: [],
images: []
},
{
isBot: true,
name: selectedModel,
message: "▋",
sources: [],
webSources: [],
iodSources: [],
id: generateMessageId
}
]
@ -203,7 +207,8 @@ export const useMessage = () => {
isBot: true,
name: selectedModel,
message: "▋",
sources: [],
webSources: [],
iodSources: [],
id: generateMessageId
}
]
@ -334,7 +339,16 @@ export const useMessage = () => {
}
let context: string = ""
let source: {
let webSources: {
name: any
type: any
mode: string
url: string
pageContent: string
metadata: Record<string, any>
}[] = []
// TODO: update type
let iodSources: {
name: any
type: any
mode: string
@ -346,7 +360,7 @@ export const useMessage = () => {
if (chatWithWebsiteEmbedding) {
const docs = await vectorstore.similaritySearch(query, 4)
context = formatDocs(docs)
source = docs.map((doc) => {
webSources = docs.map((doc) => {
return {
...doc,
name: doc?.metadata?.source || "untitled",
@ -365,7 +379,7 @@ export const useMessage = () => {
.slice(0, maxWebsiteContext)
}
source = [
webSources = [
{
name: embedURL,
type: type,
@ -476,7 +490,8 @@ export const useMessage = () => {
return {
...message,
message: fullText,
sources: source,
webSources,
iodSources,
generationInfo,
reasoning_time_taken: timetaken
}
@ -506,7 +521,8 @@ export const useMessage = () => {
message,
image,
fullText,
source,
webSources,
iodSources,
message_source: "copilot",
generationInfo,
reasoning_time_taken: timetaken
@ -606,14 +622,16 @@ export const useMessage = () => {
isBot: false,
name: "You",
message,
sources: [],
webSources: [],
iodSources: [],
images: []
},
{
isBot: true,
name: selectedModel,
message: "▋",
sources: [],
webSources: [],
iodSources: [],
id: generateMessageId
}
]
@ -624,7 +642,8 @@ export const useMessage = () => {
isBot: true,
name: selectedModel,
message: "▋",
sources: [],
webSources: [],
iodSources: [],
id: generateMessageId
}
]
@ -787,7 +806,8 @@ export const useMessage = () => {
message,
image,
fullText,
source: [],
webSources: [],
iodSources: [],
message_source: "copilot",
generationInfo,
reasoning_time_taken: timetaken
@ -891,14 +911,16 @@ export const useMessage = () => {
isBot: false,
name: "You",
message,
sources: [],
webSources: [],
iodSources: [],
images: [image]
},
{
isBot: true,
name: selectedModel,
message: "▋",
sources: [],
webSources: [],
iodSources: [],
id: generateMessageId
}
]
@ -909,7 +931,8 @@ export const useMessage = () => {
isBot: true,
name: selectedModel,
message: "▋",
sources: [],
webSources: [],
iodSources: [],
id: generateMessageId
}
]
@ -1077,7 +1100,8 @@ export const useMessage = () => {
message,
image,
fullText,
source: [],
webSources: [],
iodSources: [],
message_source: "copilot",
generationInfo,
reasoning_time_taken: timetaken
@ -1114,12 +1138,14 @@ export const useMessage = () => {
}
const searchChatMode = async (
webSearch: boolean,
iodSearch,
message: string,
image: string,
isRegenerate: boolean,
messages: Message[],
history: ChatHistory,
signal: AbortSignal
signal: AbortSignal,
) => {
const url = await getOllamaURL()
setStreaming(true)
@ -1176,14 +1202,16 @@ export const useMessage = () => {
isBot: false,
name: "You",
message,
sources: [],
webSources: [],
iodSources: [],
images: [image]
},
{
isBot: true,
name: selectedModel,
message: "▋",
sources: [],
webSources: [],
iodSources: [],
id: generateMessageId
}
]
@ -1194,7 +1222,8 @@ export const useMessage = () => {
isBot: true,
name: selectedModel,
message: "▋",
sources: [],
webSources: [],
iodSources: [],
id: generateMessageId
}
]
@ -1271,7 +1300,8 @@ export const useMessage = () => {
query = removeReasoning(query)
}
const { prompt, source } = await getSystemPromptForWeb(query)
const { prompt, webSources, iodSources } =
await getSystemPromptForWeb(query, [], webSearch, iodSearch)
setIsSearchingInternet(false)
// message = message.trim().replaceAll("\n", " ")
@ -1394,7 +1424,8 @@ export const useMessage = () => {
return {
...message,
message: fullText,
sources: source,
webSources,
iodSources,
generationInfo,
reasoning_time_taken: timetaken
}
@ -1424,7 +1455,8 @@ export const useMessage = () => {
message,
image,
fullText,
source,
webSources,
iodSources,
generationInfo,
reasoning_time_taken: timetaken
})
@ -1523,7 +1555,8 @@ export const useMessage = () => {
isBot: false,
name: "You",
message,
sources: [],
webSources: [],
iodSources: [],
images: [image],
messageType: messageType
},
@ -1531,7 +1564,8 @@ export const useMessage = () => {
isBot: true,
name: selectedModel,
message: "▋",
sources: [],
webSources: [],
iodSources: [],
id: generateMessageId
}
]
@ -1542,7 +1576,8 @@ export const useMessage = () => {
isBot: true,
name: selectedModel,
message: "▋",
sources: [],
webSources: [],
iodSources: [],
id: generateMessageId
}
]
@ -1688,7 +1723,8 @@ export const useMessage = () => {
message,
image,
fullText,
source: [],
webSources: [],
iodSources: [],
message_source: "copilot",
message_type: messageType,
generationInfo,
@ -1766,14 +1802,16 @@ export const useMessage = () => {
)
} else {
if (chatMode === "normal") {
if (webSearch) {
if (webSearch || iodSearch) {
await searchChatMode(
webSearch,
iodSearch,
message,
image,
isRegenerate || false,
messages,
memory || history,
signal
signal,
)
} else {
await normalChatMode(
@ -1906,6 +1944,8 @@ export const useMessage = () => {
regenerateLastMessage,
webSearch,
setWebSearch,
iodSearch,
setIodSearch,
isSearchingInternet,
selectedQuickPrompt,
setSelectedQuickPrompt,

View File

@ -3,6 +3,7 @@ import { cleanUrl } from "~/libs/clean-url"
import {
defaultEmbeddingModelForRag,
geWebSearchFollowUpPrompt,
geWebSearchKeywordsPrompt,
getOllamaURL,
promptForRag,
systemPromptForNonRagOption
@ -67,6 +68,8 @@ export const useMessageOption = () => {
setChatMode,
webSearch,
setWebSearch,
iodSearch,
setIodSearch,
isSearchingInternet,
setIsSearchingInternet,
selectedQuickPrompt,
@ -111,6 +114,8 @@ export const useMessageOption = () => {
}
const searchChatMode = async (
webSearch: boolean,
iodSearch: boolean,
message: string,
image: string,
isRegenerate: boolean,
@ -172,14 +177,16 @@ export const useMessageOption = () => {
isBot: false,
name: "You",
message,
sources: [],
webSources: [],
iodSources: [],
images: [image]
},
{
isBot: true,
name: selectedModel,
message: "▋",
sources: [],
webSources: [],
iodSources: [],
id: generateMessageId
}
]
@ -190,7 +197,8 @@ export const useMessageOption = () => {
isBot: true,
name: selectedModel,
message: "▋",
sources: [],
webSources: [],
iodSources: [],
id: generateMessageId
}
]
@ -204,6 +212,7 @@ export const useMessageOption = () => {
setIsSearchingInternet(true)
let query = message
let keywords: string[] = []
if (newMessage.length > 2) {
let questionPrompt = await geWebSearchFollowUpPrompt()
@ -268,7 +277,23 @@ export const useMessageOption = () => {
query = removeReasoning(query)
}
const { prompt, source } = await getSystemPromptForWeb(query)
// Currently only IoD search use keywords
if (iodSearch) {
// Extract keywords
const questionPrompt = await geWebSearchKeywordsPrompt()
const promptForQuestion = questionPrompt.replaceAll("{query}", query)
const response = await ollama.invoke(promptForQuestion)
let res = response.content.toString()
res = removeReasoning(res)
keywords = res.replace(/^Keywords:/i, '').split(', ').map(k => k.trim())
}
const { prompt, webSources, iodSources } = await getSystemPromptForWeb(
query,
keywords,
webSearch,
iodSearch,
)
setIsSearchingInternet(false)
// message = message.trim().replaceAll("\n", " ")
@ -390,7 +415,8 @@ export const useMessageOption = () => {
return {
...message,
message: fullText,
sources: source,
webSources,
iodSources,
generationInfo,
reasoning_time_taken: timetaken
}
@ -420,7 +446,8 @@ export const useMessageOption = () => {
message,
image,
fullText,
source,
webSources,
iodSources,
generationInfo,
reasoning_time_taken: timetaken
})
@ -552,14 +579,16 @@ export const useMessageOption = () => {
isBot: false,
name: "You",
message,
sources: [],
webSources: [],
iodSources: [],
images: [image]
},
{
isBot: true,
name: selectedModel,
message: "▋",
sources: [],
webSources: [],
iodSources: [],
id: generateMessageId
}
]
@ -570,7 +599,8 @@ export const useMessageOption = () => {
isBot: true,
name: selectedModel,
message: "▋",
sources: [],
webSources: [],
iodSources: [],
id: generateMessageId
}
]
@ -855,14 +885,16 @@ export const useMessageOption = () => {
isBot: false,
name: "You",
message,
sources: [],
webSources: [],
iodSources: [],
images: []
},
{
isBot: true,
name: selectedModel,
message: "▋",
sources: [],
webSources: [],
iodSources: [],
id: generateMessageId
}
]
@ -873,7 +905,8 @@ export const useMessageOption = () => {
isBot: true,
name: selectedModel,
message: "▋",
sources: [],
webSources: [],
iodSources: [],
id: generateMessageId
}
]
@ -1076,7 +1109,7 @@ export const useMessageOption = () => {
return {
...message,
message: fullText,
sources: source,
webSources: source,
generationInfo,
reasoning_time_taken: timetaken
}
@ -1175,8 +1208,10 @@ export const useMessageOption = () => {
signal
)
} else {
if (webSearch) {
if (webSearch || iodSearch) {
await searchChatMode(
webSearch,
iodSearch,
message,
image,
isRegenerate,
@ -1311,6 +1346,8 @@ export const useMessageOption = () => {
regenerateLastMessage,
webSearch,
setWebSearch,
iodSearch,
setIodSearch,
isSearchingInternet,
setIsSearchingInternet,
selectedQuickPrompt,

View File

@ -58,6 +58,30 @@ Follow-up question: {question}
Rephrased question:
`
const DEFAULT_WEBSEARCH_KEYWORDS_PROMPT = `Extract the most important keywords from the query (at most 3), and give me English and Chinese versions of the keywords.
The result format should be: keyword_1, keyword_2, ..., keyword_n
Example:
Query: What are the symptoms of a heart attack?
Keywords: symptoms, , heart attack,
Query: 什么是物联网?
Keywords: Internet of Things, IoT,
Query: 人工智能的发展趋势
Keywords: Artificial Intelligence, AI, , trend,
Query: {query}
Keywords:
`
export const getOllamaURL = async () => {
const ollamaURL = await storage.get("ollamaURL")
if (!ollamaURL || ollamaURL.length === 0) {
@ -411,6 +435,18 @@ export const setWebPrompts = async (prompt: string, followUpPrompt: string) => {
await setWebSearchFollowUpPrompt(followUpPrompt)
}
export const geWebSearchKeywordsPrompt = async () => {
const prompt = await storage.get("webSearchKeywordsPrompt")
if (!prompt || prompt.length === 0) {
return DEFAULT_WEBSEARCH_KEYWORDS_PROMPT
}
return prompt
}
export const setWebSearchKeywordsPrompt = async (prompt: string) => {
await storage.set("webSearchKeywordsPrompt", prompt)
}
export const getPageShareUrl = async () => {
const pageShareUrl = await storage.get("pageShareUrl")
if (!pageShareUrl || pageShareUrl.length === 0) {

View File

@ -14,7 +14,8 @@ export type Message = {
isBot: boolean
name: string
message: string
sources: any[]
webSources: any[]
iodSources: any[]
images?: string[]
search?: WebSearch
reasoning_time_taken?: number
@ -52,6 +53,8 @@ type State = {
setIsEmbedding: (isEmbedding: boolean) => void
webSearch: boolean
setWebSearch: (webSearch: boolean) => void
iodSearch: boolean
setIodSearch: (iodSearch: boolean) => void
isSearchingInternet: boolean
setIsSearchingInternet: (isSearchingInternet: boolean) => void
@ -100,6 +103,8 @@ export const useStoreMessageOption = create<State>((set) => ({
setIsEmbedding: (isEmbedding) => set({ isEmbedding }),
webSearch: false,
setWebSearch: (webSearch) => set({ webSearch }),
iodSearch: false,
setIodSearch: (iodSearch) => set({ iodSearch }),
isSearchingInternet: false,
setIsSearchingInternet: (isSearchingInternet) => set({ isSearchingInternet }),
selectedSystemPrompt: null,

View File

@ -11,7 +11,8 @@ export type Message = {
isBot: boolean
name: string
message: string
sources: any[]
webSources: any[]
iodSources: any[]
images?: string[]
search?: WebSearch
messageType?: string

148
src/web/iod.ts Normal file
View File

@ -0,0 +1,148 @@
import { cleanUrl } from "@/libs/clean-url"
import { PageAssistHtmlLoader } from "@/loader/html"
import { pageAssistEmbeddingModel } from "@/models/embedding"
import { defaultEmbeddingModelForRag, getOllamaURL } from "@/services/ollama"
import {
getIsSimpleInternetSearch,
totalSearchResults
} from "@/services/search"
import { getPageAssistTextSplitter } from "@/utils/text-splitter"
import type { Document } from "@langchain/core/documents"
import { MemoryVectorStore } from "langchain/vectorstores/memory"
const makeRegSearchParams = (count: number, keyword: string) => ({
action: "executeContract",
contractID: "BDBrowser",
operation: "sendRequestDirectly",
arg: {
id: "670E241C9937B3537047C87053E3AA36",
doipUrl: "tcp://reg01.public.internetofdata.cn:21037",
op: "Search",
attributes: {
offset: 0,
count,
bodyBase64Encoded: false,
searchMode: [
{
key: "data_type",
type: "MUST",
value: "paper"
},
// {
// key: "title",
// type: "MUST",
// value: keyword,
// },
{
key: "description",
type: "MUST",
value: keyword
}
]
},
body: ""
}
})
export const localIodSearch = async (query: string, keywords: string[]) => {
const TOTAL_SEARCH_RESULTS = await totalSearchResults()
const results = (
await Promise.all(
keywords.map(async (keyword) => {
const abortController = new AbortController()
setTimeout(() => abortController.abort(), 10000)
const params = makeRegSearchParams(TOTAL_SEARCH_RESULTS, keyword)
return fetch("http://47.93.156.31:21033/SCIDE/SCManager", {
method: "POST",
body: JSON.stringify(params),
signal: abortController.signal
})
.then((response) => response.json())
.then((res) => {
if (res.status !== "Success") {
console.log(res)
return []
}
const body = JSON.parse(res.result.body)
if (body.code !== 0) {
console.log(body)
return []
}
const results =
body.data?.results?.filter((r) => r.url || r.pdf_url) || []
results.forEach((r) => {
r.url = r.url || r.pdf_url
})
return results
})
.catch((e) => {
console.log(e)
return []
})
})
)
).flat()
return results
}
const ARXIV_URL = /^https:\/\/arxiv.org\//
export const searchIod = async (query: string, keywords: string[]) => {
const searchResults = await localIodSearch(query, keywords)
const isSimpleMode = await getIsSimpleInternetSearch()
if (isSimpleMode) {
await getOllamaURL()
return searchResults
}
const docs: Document<Record<string, any>>[] = []
for (const result of searchResults) {
let url = result.url
if (ARXIV_URL.test(result.url)) {
url = result.url.replace("/pdf/", "/abs/").replace(".pdf", "")
}
const loader = new PageAssistHtmlLoader({
html: "",
url
})
const documents = await loader.loadByURL()
documents.forEach((doc) => {
docs.push(doc)
})
}
const ollamaUrl = await getOllamaURL()
const embeddingModle = await defaultEmbeddingModelForRag()
const ollamaEmbedding = await pageAssistEmbeddingModel({
model: embeddingModle || "",
baseUrl: cleanUrl(ollamaUrl)
})
const textSplitter = await getPageAssistTextSplitter()
const chunks = await textSplitter.splitDocuments(docs)
const store = new MemoryVectorStore(ollamaEmbedding)
await store.addDocuments(chunks)
const resultsWithEmbeddings = await store.similaritySearch(query, 3)
const searchResult = resultsWithEmbeddings.map((result) => {
return {
url: result.metadata.url,
content: result.pageContent
}
})
return searchResult
}

View File

@ -8,6 +8,7 @@ import { getWebsiteFromQuery, processSingleWebsite } from "./website"
import { searxngSearch } from "./search-engines/searxng"
import { braveAPISearch } from "./search-engines/brave-api"
import { webBaiduSearch } from "./search-engines/baidu"
import { searchIod } from "./iod"
const getHostName = (url: string) => {
try {
@ -37,30 +38,66 @@ const searchWeb = (provider: string, query: string) => {
}
}
export const getSystemPromptForWeb = async (query: string) => {
export const getSystemPromptForWeb = async (
query: string,
keywords: string[] = [],
webSearch = true,
iodSearch = false
) => {
try {
const websiteVisit = getWebsiteFromQuery(query)
let search: {
url: any;
content: string;
let webSearchResults: {
url: any
content: string
}[] = []
// let search_results_web = ""
if (webSearch) {
const isVisitSpecificWebsite = await getIsVisitSpecificWebsite()
if (isVisitSpecificWebsite && websiteVisit.hasUrl) {
const url = websiteVisit.url
const queryWithoutUrl = websiteVisit.queryWithouUrls
search = await processSingleWebsite(url, queryWithoutUrl)
webSearchResults = await processSingleWebsite(url, queryWithoutUrl)
} else {
const searchProvider = await getSearchProvider()
search = await searchWeb(searchProvider, query)
webSearchResults = await searchWeb(searchProvider, query)
}
// search_results_web = webSearchResults
// .map(
// (result, idx) =>
// `<result source="${result.url}" id="${idx}">${result.content}</result>`
// )
// .join("\n")
}
const search_results = search
let iodSearchResults: {
doId: string
name: string
url?: string
// pdf_url?: string
description: string
}[] = []
// let search_results_iod = ""
if (iodSearch) {
iodSearchResults = await searchIod(query, keywords)
// search_results_iod = iodSearchResults
// .map(
// (result, idx) =>
// `<result source="${result.url}" id="${idx}">${result.content}</result>`
// )
// .join("\n")
}
const search_results = iodSearchResults.map((res) => ({
url: `${res.doId}: ${res.name}`,
content: res.description
}))
.concat(
webSearchResults
)
.map(
(result, idx) =>
`<result source="${result.url}" id="${idx}">${result.content}</result>`
@ -77,13 +114,14 @@ export const getSystemPromptForWeb = async (query: string) => {
return {
prompt,
source: search.map((result) => {
webSources: webSearchResults.map((result) => {
return {
url: result.url,
name: getHostName(result.url),
type: "url"
}
})
}),
iodSources: iodSearchResults,
}
} catch (e) {
console.error(e)