1 Commits

Author SHA1 Message Date
CaiHQ
e5cca132da naive search 2025-02-13 17:22:05 +08:00
51 changed files with 325 additions and 1244 deletions

View File

@@ -10,8 +10,6 @@ Page Assist supports Chromium-based browsers like Chrome, Brave, and Edge, as we
[![Chrome Web Store](https://pub-35424b4473484be483c0afa08c69e7da.r2.dev/UV4C4ybeBTsZt43U4xis.png)](https://chrome.google.com/webstore/detail/page-assist/jfgfiigpkhlkbnfnbobbkinehhfdhndo) [![Chrome Web Store](https://pub-35424b4473484be483c0afa08c69e7da.r2.dev/UV4C4ybeBTsZt43U4xis.png)](https://chrome.google.com/webstore/detail/page-assist/jfgfiigpkhlkbnfnbobbkinehhfdhndo)
[![Firefox Add-on](https://pub-35424b4473484be483c0afa08c69e7da.r2.dev/get-the-addon.png)](https://addons.mozilla.org/en-US/firefox/addon/page-assist/) [![Firefox Add-on](https://pub-35424b4473484be483c0afa08c69e7da.r2.dev/get-the-addon.png)](https://addons.mozilla.org/en-US/firefox/addon/page-assist/)
[![Edge Add-on](https://pub-35424b4473484be483c0afa08c69e7da.r2.dev/edge-addon.png)](https://microsoftedge.microsoft.com/addons/detail/page-assist-a-web-ui-fo/ogkogooadflifpmmidmhjedogicnhooa)
Checkout the Demo (v1.0.0): Checkout the Demo (v1.0.0):

BIN
bun.lockb Executable file → Normal file

Binary file not shown.

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,30 +1,29 @@
{ {
"newChat": "New Chat", "newChat": "New Chat",
"selectAPrompt": "Select a Prompt", "selectAPrompt": "Select a Prompt",
"githubRepository": "GitHub Repository", "githubRepository": "GitHub Repository",
"settings": "Settings", "settings": "Settings",
"metering": "Metering", "sidebarTitle": "Chat History",
"sidebarTitle": "Chat History", "error": "Error",
"error": "Error", "somethingWentWrong": "Something went wrong",
"somethingWentWrong": "Something went wrong", "validationSelectModel": "Please select a model to continue",
"validationSelectModel": "Please select a model to continue", "deleteHistoryConfirmation": "Are you sure you want to delete this history?",
"deleteHistoryConfirmation": "Are you sure you want to delete this history?", "editHistoryTitle": "Enter a new title",
"editHistoryTitle": "Enter a new title", "temporaryChat": "Temporary Chat",
"temporaryChat": "Temporary Chat", "more": {
"more": { "copy": {
"copy": { "group": "Copy",
"group": "Copy", "asText": "Copy as Text",
"asText": "Copy as Text", "asMarkdown": "Copy as Markdown",
"asMarkdown": "Copy as Markdown", "success": "Copied to clipboard!"
"success": "Copied to clipboard!" },
}, "download": {
"download": { "group": "Download",
"group": "Download", "text": "Text File (.txt)",
"text": "Text File (.txt)", "markdown": "Markdown (.md)",
"markdown": "Markdown (.md)", "json": "JSON File (.json)",
"json": "JSON File (.json)", "image": "Image (.png)"
"image": "Image (.png)" },
}, "share": "Share"
"share": "Share" }
}
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,29 +1,28 @@
{ {
"newChat": "新聊天", "newChat": "新聊天",
"selectAPrompt": "选择一个提示词", "selectAPrompt": "本地回答",
"githubRepository": "GitHub 仓库", "githubRepository": "GitHub 仓库",
"settings": "设置", "settings": "设置",
"metering": "计量", "sidebarTitle": "聊天历史",
"sidebarTitle": "聊天历史", "error": "错误",
"error": "错误", "somethingWentWrong": "出现了错误",
"somethingWentWrong": "出现了错误", "validationSelectModel": "请选择一个模型以继续",
"validationSelectModel": "请选择一个模型以继续", "deleteHistoryConfirmation": "你确定要删除这个历史记录吗?",
"deleteHistoryConfirmation": "你确定要删除这个历史记录吗?", "editHistoryTitle": "输入一个新的标题",
"editHistoryTitle": "输入一个新的标题", "temporaryChat": "临时聊天",
"temporaryChat": "临时聊天", "more": {
"more": { "copy": {
"copy": { "group": "复制",
"group": "复制", "asText": "复制为文本",
"asText": "复制为文本", "asMarkdown": "复制为 Markdown",
"asMarkdown": "复制为 Markdown", "success": "复制到剪贴板!"
"success": "已复制到剪贴板!" },
}, "download": {
"download": { "group": "下载",
"group": "下载", "text": "文本文件 (.txt)",
"text": "文本文件 (.txt)", "markdown": "Markdown 文件 (.md)",
"markdown": "Markdown 文件 (.md)", "json": "JSON 文件 (.json)"
"json": "JSON 文件 (.json)" },
}, "share": "分享"
"share": "分享" }
}
} }

View File

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

View File

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

View File

@@ -1,9 +1,6 @@
import { useState } from "react"
import type React from "react"
import { KnowledgeIcon } from "@/components/Option/Knowledge/KnowledgeIcon" import { KnowledgeIcon } from "@/components/Option/Knowledge/KnowledgeIcon"
type Props = { type Props = {
index: number
source: { source: {
name?: string name?: string
url?: string url?: string
@@ -11,72 +8,42 @@ type Props = {
type?: string type?: string
pageContent?: string pageContent?: string
content?: string content?: string
doId?: string
description?: string
} }
key: number
onSourceClick?: (source: any) => void onSourceClick?: (source: any) => void
index: number
} }
export const MessageSource: React.FC<Props> = ({ export const MessageSource: React.FC<Props> = ({ source, key, onSourceClick, index}) => {
index,
source,
onSourceClick
}) => {
// Add state for tracking and content visibility
const [showContent, setShowContent] = useState(false)
if (source?.mode === "rag" || source?.mode === "chat") { if (source?.mode === "rag" || source?.mode === "chat") {
return ( return (
<button <div className="block items-center gap-1 text-xs text-gray-800 dark:text-gray-100 mb-1">
onClick={() => { <span className="text-xs font-medium">[{index + 1}]</span> {/* 显示序号 */}
onSourceClick && onSourceClick(source)
}}
className="inline-flex gap-2 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">
<KnowledgeIcon type={source.type} className="h-3 w-3" />
<span className="text-xs">{source.name}</span> <span className="text-xs">{source.name}</span>
</button> <a
) href={source?.url}
} target="_blank"
className="text-xs text-blue-500 hover:underline"
const onContextMenu = (e: React.MouseEvent) => { onClick={(e) => {
e.preventDefault() e.preventDefault(); // 阻止默认的链接行为
e.stopPropagation onSourceClick && onSourceClick(source); // 调用自定义点击事件
setShowContent(true) }}
>
{source.url}
</a>
</div>
);
} }
return ( return (
<div className="block items-center gap-1 text-xs text-gray-800 dark:text-gray-100 mb-1"> <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>{" "} <span className="text-xs font-medium">[{index + 1}]</span> {/* 显示序号 */}
<a <a
href={source?.url} href={source?.url}
target="_blank" target="_blank"
onContextMenu={onContextMenu} className="text-xs text-blue-500 hover:underline"
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 ? ( {source.name}
<>
<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> </a>
</div> </div>
) )

View File

@@ -5,7 +5,6 @@ import {
ChevronRight, ChevronRight,
CogIcon, CogIcon,
ComputerIcon, ComputerIcon,
Slice,
GithubIcon, GithubIcon,
PanelLeftIcon, PanelLeftIcon,
ZapIcon ZapIcon
@@ -241,14 +240,7 @@ export const Header: React.FC<Props> = ({
<CogIcon className="w-6 h-6" /> <CogIcon className="w-6 h-6" />
</NavLink> </NavLink>
</Tooltip> </Tooltip>
<Tooltip title={t("metering")}> </div>
<NavLink
to="/metering"
className="!text-gray-500 dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
<Slice className="w-6 h-6" />
</NavLink>
</Tooltip>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,203 +0,0 @@
import {
Card,
List,
Table,
Tag,
Space,
TableProps,
Divider,
Typography,
Tooltip
} from "antd"
import { NavLink, useParams } from "react-router-dom"
import { useStoreMessageOption } from "@/store/option.tsx"
import { useMemo } from "react"
interface DataType {
key: string
name: string
doId: number
data_space: string
content: string
tokenCount: number
}
const columns: TableProps<DataType>["columns"] = [
{
title: '序号',
key: 'index',
width: 100,
render: (_text, _record, index) => index + 1, // 索引从0开始+1后从1显示
},
{
title: "标识",
dataIndex: "doId",
key: "doId"
},
{
title: "提供方",
dataIndex: "data_space",
key: "data_space"
},
{
title: "token数量",
key: "tokenCount",
dataIndex: "tokenCount",
width: 100
},
{
title: "内容",
key: "content",
dataIndex: "content",
ellipsis: {
showTitle: false
},
render: (content) => (
<Tooltip placement="topLeft" title={content}>
{content}
</Tooltip>
)
}
]
export const ListDetail = () => {
const { chatMessages } = useStoreMessageOption()
const { id } = useParams()
const record = useMemo(
() => chatMessages.find((item) => item.id === id),
[chatMessages]
)
const modelData = useMemo(
() => [
{
key: "大模型输入token数",
value: record.modelInputTokenCount
},
{
key: "大模型输出token数",
value: record.modelOutputTokenCount
},
{
key: "模型",
value: record.model
}
],
[record]
)
const inputTokenData = useMemo(
() => [
{
key: "内容:",
value: record.queryContent
},
{
key: "token数量:",
value: record.queryContent.length
}
],
[record]
)
const keywordsData = useMemo(
() => [
{
key: "token数量:",
value: record.iodKeywords.reduce((acc, cur) => acc + cur.length, 0)
},
{
key: "内容:",
value: record.iodKeywords.join(", ")
}
],
[record]
)
const responseContent = useMemo(
() => [
{
key: "token数量:",
value: record.modelResponseContent.length
},
{
key: "内容:",
value: record.modelResponseContent
}
],
[record]
)
return (
<div className="p-[1rem] pt-[4rem]">
<List
grid={{ gutter: 16, column: 3 }}
dataSource={modelData}
renderItem={(item) => (
<List.Item>
<Card title={item.key}>{item.value}</Card>
</List.Item>
)}
style={{ marginBottom: "2rem" }}
/>
<Space direction="vertical" size={10}>
<Divider orientation="left">token详情</Divider>
<List
bordered
header={<div></div>}
dataSource={inputTokenData}
renderItem={(item) => (
<List.Item style={{ justifyContent: "flex-start" }}>
<Typography.Paragraph style={{ marginBottom: 0 }} className="mr-1">
{item.key}
</Typography.Paragraph>
<Tooltip placement="topLeft" style={{ marginBottom: 0 }} title={item.value}>
{item.value}
</Tooltip>
</List.Item>
)}
style={{ marginBottom: "1rem" }}
/>
<Card title="数联网引用数据">
<Table<DataType> columns={columns} dataSource={record.iodData} />
</Card>
</Space>
<Space direction="vertical" size={10}>
<Divider orientation="left">token详情</Divider>
<List
bordered
dataSource={keywordsData}
header={<div></div>}
renderItem={(item) => (
<List.Item style={{ justifyContent: "flex-start" }}>
<Typography.Text className="mr-1" style={{ marginBottom: 0 }}>{item.key}</Typography.Text>
<Tooltip style={{ marginBottom: 0 }} placement="topLeft" title={item.value}>
{item.value}
</Tooltip>
</List.Item>
)}
/>
<List
bordered
dataSource={responseContent}
header={<div></div>}
renderItem={(item) => (
<List.Item
style={{ justifyContent: "flex-start", alignItems: "center" }}>
<Typography.Text
className="mt-0 mr-1 w-20"
style={{ marginBottom: 0 }}>
{item.key}
</Typography.Text>
<Typography.Paragraph
style={{ marginBottom: 0 }}
ellipsis={{ tooltip: item.value, rows: 2, expandable: true }}>
{item.value}
</Typography.Paragraph>
</List.Item>
)}
/>
</Space>
</div>
)
}

View File

@@ -1,165 +0,0 @@
import React, { useMemo } from "react"
import { ChatMessage, useStoreMessageOption } from "@/store/option"
import { Card, List, Table, Tag, Space, TableProps, Tooltip } from "antd"
import { NavLink } from "react-router-dom"
import { formatDate } from "@/utils/date"
const columns: TableProps<ChatMessage>["columns"] = [
{
title: '序号',
key: 'index',
width: 100,
render: (_text, _record, index) => index + 1, // 索引从0开始+1后从1显示
},
{
title: "问题",
dataIndex: "queryContent",
key: "queryContent"
},
{
title: "提示词全文",
dataIndex: "prompt",
key: "prompt",
ellipsis: {
showTitle: false
},
render: (prompt) => (
<Tooltip placement="topLeft" title={prompt}>
{prompt}
</Tooltip>
),
width: "10%"
},
{
title: "思维链",
key: "thinkingChain",
dataIndex: "thinkingChain",
ellipsis: {
showTitle: false
},
render: (responseContent) => (
<Tooltip placement="topLeft" title={responseContent}>
{responseContent}
</Tooltip>
),
width: "10%"
},
{
title: "回答",
dataIndex: "responseContent",
key: "responseContent",
ellipsis: {
showTitle: false
},
render: (responseContent) => (
<Tooltip placement="topLeft" title={responseContent}>
{responseContent}
</Tooltip>
),
width: "10%"
},
{
title: "关联数据个数",
dataIndex: "relatedDataCount",
key: "relatedDataCount"
},
{
title: "数联网token",
dataIndex: "iodDataTokenCount",
key: "iodDataTokenCount"
},
{
title: "大模型token",
key: "largeModelToken",
dataIndex: "largeModelToken",
render: (_, record) => {
return (
<div>{record.modelInputTokenCount + record.modelOutputTokenCount}</div>
)
}
},
{
title: "日期",
dataIndex: "date",
key: "date",
render: (date) => {
return <div>{formatDate(date ?? new Date())}</div>
}
},
{
title: "耗时",
key: "timeTaken",
dataIndex: "timeTaken"
},
{
title: "操作",
key: "action",
render: (_, record) => (
<Space size="middle">
{/* <a>Invite {record.name}</a> */}
<NavLink to={`/metering/list/${record.id}`}>
<a></a>
</NavLink>
</Space>
)
}
]
export const MeteringDetail = () => {
const { chatMessages } = useStoreMessageOption()
const data = useMemo(
() => [
{
key: "对话数量",
value: chatMessages.length
},
{
key: "数联网输入token数",
value: chatMessages.reduce((acc, cur) => {
for (const item of cur.iodKeywords) {
acc += item.length
}
return acc
}, 0)
},
{
key: "数联网输出token数",
value: chatMessages.reduce((acc, cur) => acc + cur.iodDataTokenCount, 0)
},
{
key: "大模型输入token数",
value: chatMessages.reduce(
(acc, cur) => acc + cur.modelInputTokenCount,
0
)
},
{
key: "大模型输出token数",
value: chatMessages.reduce(
(acc, cur) => acc + cur.modelOutputTokenCount,
0
)
}
],
[chatMessages]
)
return (
<div className="p-4 pt-[4rem]">
<List
grid={{ gutter: 16, column: 5 }}
dataSource={data}
split={false}
renderItem={(item) => (
<List.Item>
<Card title={item.key}>{item.value}</Card>
</List.Item>
)}
/>
<Table<ChatMessage> columns={columns} dataSource={chatMessages} />
</div>
)
}

View File

@@ -36,7 +36,7 @@ export const PlaygroundChat = () => {
onRengerate={regenerateLastMessage} onRengerate={regenerateLastMessage}
isProcessing={streaming} isProcessing={streaming}
isSearchingInternet={isSearchingInternet} isSearchingInternet={isSearchingInternet}
webSources={message.webSources} sources={message.sources}
iodSources={message.iodSources} iodSources={message.iodSources}
onEditFormSubmit={(value, isSend) => { onEditFormSubmit={(value, isSend) => {
editMessage(index, value, !message.isBot, 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 { useTranslation } from "react-i18next"
import { KnowledgeSelect } from "../Knowledge/KnowledgeSelect" import { KnowledgeSelect } from "../Knowledge/KnowledgeSelect"
import { useSpeechRecognition } from "@/hooks/useSpeechRecognition" import { useSpeechRecognition } from "@/hooks/useSpeechRecognition"
import { PiGlobe, PiNetwork } from "react-icons/pi" import { PiGlobe } from "react-icons/pi"
import { handleChatInputKeyDown } from "@/utils/key-down" import { handleChatInputKeyDown } from "@/utils/key-down"
import { getIsSimpleInternetSearch } from "@/services/search" import { getIsSimpleInternetSearch } from "@/services/search"
@@ -34,8 +34,6 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
streaming: isSending, streaming: isSending,
webSearch, webSearch,
setWebSearch, setWebSearch,
iodSearch,
setIodSearch,
selectedQuickPrompt, selectedQuickPrompt,
textareaRef, textareaRef,
setSelectedQuickPrompt, setSelectedQuickPrompt,
@@ -128,6 +126,7 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
} }
}, [transcript]) }, [transcript])
/*
React.useEffect(() => { React.useEffect(() => {
if (selectedQuickPrompt) { if (selectedQuickPrompt) {
const word = getVariable(selectedQuickPrompt) const word = getVariable(selectedQuickPrompt)
@@ -144,7 +143,7 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
} }
} }
}, [selectedQuickPrompt]) }, [selectedQuickPrompt])
*/
const queryClient = useQueryClient() const queryClient = useQueryClient()
const { mutateAsync: sendMessage } = useMutation({ const { mutateAsync: sendMessage } = useMutation({
@@ -301,38 +300,6 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
{...form.getInputProps("message")} {...form.getInputProps("message")}
/> />
<div className="mt-2 flex justify-between items-center"> <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
className={`h-5 w-5 dark:text-gray-300 `}
/>
<Switch
value={webSearch}
onChange={(e) => setWebSearch(e)}
checkedChildren={t("form.webSearch.on")}
unCheckedChildren={t("form.webSearch.off")}
/>
</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"> <div className="flex !justify-end gap-3">
{!selectedKnowledge && ( {!selectedKnowledge && (
<Tooltip title={t("tooltip.uploadImage")}> <Tooltip title={t("tooltip.uploadImage")}>

View File

@@ -0,0 +1,13 @@
<Tooltip title={t("tooltip.searchInternet")}>
<div className="inline-flex items-center gap-2">
<PiGlobe
className={`h-5 w-5 dark:text-gray-300 `}
/>
<Switch
value={webSearch}
onChange={(e) => setWebSearch(e)}
checkedChildren={t("form.webSearch.on")}
unCheckedChildren={t("form.webSearch.off")}
/>
</div>
</Tooltip>

View File

@@ -32,6 +32,7 @@ type Props = {
setHistoryId: (historyId: string) => void setHistoryId: (historyId: string) => void
setSelectedModel: (model: string) => void setSelectedModel: (model: string) => void
setSelectedSystemPrompt: (prompt: string) => void setSelectedSystemPrompt: (prompt: string) => void
setSelectedQuickPrompt: (prompt: string | undefined) => void
setSystemPrompt: (prompt: string) => void setSystemPrompt: (prompt: string) => void
clearChat: () => void clearChat: () => void
temporaryChat: boolean temporaryChat: boolean
@@ -46,6 +47,7 @@ export const Sidebar = ({
setHistoryId, setHistoryId,
setSelectedModel, setSelectedModel,
setSelectedSystemPrompt, setSelectedSystemPrompt,
setSelectedQuickPrompt,
clearChat, clearChat,
historyId, historyId,
setSystemPrompt, setSystemPrompt,

View File

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

View File

@@ -29,8 +29,8 @@ type Message = {
role: string role: string
content: string content: string
images?: string[] images?: string[]
webSources?: string[] sources?: string[]
iodSources?: string[] iodSources?:string[]
search?: WebSearch search?: WebSearch
createdAt: number createdAt: number
reasoning_time_taken?: number reasoning_time_taken?: number
@@ -239,7 +239,7 @@ export const generateID = () => {
export const saveHistory = async ( export const saveHistory = async (
title: string, title: string,
is_rag?: boolean, is_rag?: boolean,
message_source?: "copilot" | "web-ui" message_source?: "copilot" | "web-ui",
) => { ) => {
const id = generateID() const id = generateID()
const createdAt = Date.now() const createdAt = Date.now()
@@ -255,8 +255,8 @@ export const saveMessage = async (
role: string, role: string,
content: string, content: string,
images: string[], images: string[],
webSources?: any[], source?: any[],
iodSources?: any[], iodSource?:any[],
time?: number, time?: number,
message_type?: string, message_type?: string,
generationInfo?: any, generationInfo?: any,
@@ -275,8 +275,8 @@ export const saveMessage = async (
content, content,
images, images,
createdAt, createdAt,
webSources, sources: source,
iodSources, iodSources:iodSource,
messageType: message_type, messageType: message_type,
generationInfo: generationInfo, generationInfo: generationInfo,
reasoning_time_taken reasoning_time_taken
@@ -306,7 +306,7 @@ export const formatToMessage = (messages: MessageHistory): MessageType[] => {
isBot: message.role === "assistant", isBot: message.role === "assistant",
message: message.content, message: message.content,
name: message.name, name: message.name,
webSources: message?.webSources || [], sources: message?.sources || [],
iodSources: message?.iodSources || [], iodSources: message?.iodSources || [],
images: message.images || [], images: message.images || [],
generationInfo: message?.generationInfo, generationInfo: message?.generationInfo,

View File

@@ -130,8 +130,8 @@ export const saveMessageOnSuccess = async ({
message, message,
image, image,
fullText, fullText,
webSources, source,
iodSources, iodSource,
message_source = "web-ui", message_source = "web-ui",
message_type, generationInfo, message_type, generationInfo,
prompt_id, prompt_id,
@@ -145,8 +145,8 @@ export const saveMessageOnSuccess = async ({
message: string message: string
image: string image: string
fullText: string fullText: string
webSources: any[] source: any[]
iodSources: any[] iodSource: any[]
message_source?: "copilot" | "web-ui", message_source?: "copilot" | "web-ui",
message_type?: string message_type?: string
generationInfo?: any generationInfo?: any
@@ -176,8 +176,8 @@ export const saveMessageOnSuccess = async ({
"assistant", "assistant",
fullText, fullText,
[], [],
webSources, source,
iodSources, iodSource,
2, 2,
message_type, message_type,
generationInfo, generationInfo,
@@ -209,8 +209,8 @@ export const saveMessageOnSuccess = async ({
"assistant", "assistant",
fullText, fullText,
[], [],
webSources, source,
iodSources, iodSource,
2, 2,
message_type, message_type,
generationInfo, generationInfo,

View File

@@ -59,8 +59,6 @@ export const useMessage = () => {
setIsSearchingInternet, setIsSearchingInternet,
webSearch, webSearch,
setWebSearch, setWebSearch,
iodSearch,
setIodSearch,
isSearchingInternet isSearchingInternet
} = useStoreMessageOption() } = useStoreMessageOption()
const [defaultInternetSearchOn] = useStorage("defaultInternetSearchOn", false) const [defaultInternetSearchOn] = useStorage("defaultInternetSearchOn", false)
@@ -187,16 +185,16 @@ export const useMessage = () => {
isBot: false, isBot: false,
name: "You", name: "You",
message, message,
webSources: [], sources: [],
iodSources: [], iodSources:[],
images: [] images: []
}, },
{ {
isBot: true, isBot: true,
name: selectedModel, name: selectedModel,
message: "▋", message: "▋",
webSources: [], sources: [],
iodSources: [], iodSources:[],
id: generateMessageId id: generateMessageId
} }
] ]
@@ -207,8 +205,8 @@ export const useMessage = () => {
isBot: true, isBot: true,
name: selectedModel, name: selectedModel,
message: "▋", message: "▋",
webSources: [], sources: [],
iodSources: [], iodSources:[],
id: generateMessageId id: generateMessageId
} }
] ]
@@ -339,16 +337,7 @@ export const useMessage = () => {
} }
let context: string = "" let context: string = ""
let webSources: { let source: {
name: any
type: any
mode: string
url: string
pageContent: string
metadata: Record<string, any>
}[] = []
// TODO: update type
let iodSources: {
name: any name: any
type: any type: any
mode: string mode: string
@@ -360,7 +349,7 @@ export const useMessage = () => {
if (chatWithWebsiteEmbedding) { if (chatWithWebsiteEmbedding) {
const docs = await vectorstore.similaritySearch(query, 4) const docs = await vectorstore.similaritySearch(query, 4)
context = formatDocs(docs) context = formatDocs(docs)
webSources = docs.map((doc) => { source = docs.map((doc) => {
return { return {
...doc, ...doc,
name: doc?.metadata?.source || "untitled", name: doc?.metadata?.source || "untitled",
@@ -379,7 +368,7 @@ export const useMessage = () => {
.slice(0, maxWebsiteContext) .slice(0, maxWebsiteContext)
} }
webSources = [ source = [
{ {
name: embedURL, name: embedURL,
type: type, type: type,
@@ -490,8 +479,7 @@ export const useMessage = () => {
return { return {
...message, ...message,
message: fullText, message: fullText,
webSources, sources: source,
iodSources,
generationInfo, generationInfo,
reasoning_time_taken: timetaken reasoning_time_taken: timetaken
} }
@@ -512,7 +500,7 @@ export const useMessage = () => {
content: fullText content: fullText
} }
]) ])
const iodSource = []
await saveMessageOnSuccess({ await saveMessageOnSuccess({
historyId, historyId,
setHistoryId, setHistoryId,
@@ -521,8 +509,8 @@ export const useMessage = () => {
message, message,
image, image,
fullText, fullText,
webSources, source,
iodSources, iodSource,
message_source: "copilot", message_source: "copilot",
generationInfo, generationInfo,
reasoning_time_taken: timetaken reasoning_time_taken: timetaken
@@ -622,15 +610,15 @@ export const useMessage = () => {
isBot: false, isBot: false,
name: "You", name: "You",
message, message,
webSources: [], sources: [],
iodSources: [], iodSources:[],
images: [] images: []
}, },
{ {
isBot: true, isBot: true,
name: selectedModel, name: selectedModel,
message: "▋", message: "▋",
webSources: [], sources: [],
iodSources: [], iodSources: [],
id: generateMessageId id: generateMessageId
} }
@@ -642,7 +630,7 @@ export const useMessage = () => {
isBot: true, isBot: true,
name: selectedModel, name: selectedModel,
message: "▋", message: "▋",
webSources: [], sources: [],
iodSources: [], iodSources: [],
id: generateMessageId id: generateMessageId
} }
@@ -806,8 +794,8 @@ export const useMessage = () => {
message, message,
image, image,
fullText, fullText,
webSources: [], source: [],
iodSources: [], iodSource:[],
message_source: "copilot", message_source: "copilot",
generationInfo, generationInfo,
reasoning_time_taken: timetaken reasoning_time_taken: timetaken
@@ -911,7 +899,7 @@ export const useMessage = () => {
isBot: false, isBot: false,
name: "You", name: "You",
message, message,
webSources: [], sources: [],
iodSources: [], iodSources: [],
images: [image] images: [image]
}, },
@@ -919,7 +907,7 @@ export const useMessage = () => {
isBot: true, isBot: true,
name: selectedModel, name: selectedModel,
message: "▋", message: "▋",
webSources: [], sources: [],
iodSources: [], iodSources: [],
id: generateMessageId id: generateMessageId
} }
@@ -931,7 +919,7 @@ export const useMessage = () => {
isBot: true, isBot: true,
name: selectedModel, name: selectedModel,
message: "▋", message: "▋",
webSources: [], sources: [],
iodSources: [], iodSources: [],
id: generateMessageId id: generateMessageId
} }
@@ -1100,8 +1088,8 @@ export const useMessage = () => {
message, message,
image, image,
fullText, fullText,
webSources: [], source: [],
iodSources: [], iodSource:[],
message_source: "copilot", message_source: "copilot",
generationInfo, generationInfo,
reasoning_time_taken: timetaken reasoning_time_taken: timetaken
@@ -1138,14 +1126,12 @@ export const useMessage = () => {
} }
const searchChatMode = async ( const searchChatMode = async (
webSearch: boolean,
iodSearch,
message: string, message: string,
image: string, image: string,
isRegenerate: boolean, isRegenerate: boolean,
messages: Message[], messages: Message[],
history: ChatHistory, history: ChatHistory,
signal: AbortSignal, signal: AbortSignal
) => { ) => {
const url = await getOllamaURL() const url = await getOllamaURL()
setStreaming(true) setStreaming(true)
@@ -1202,7 +1188,7 @@ export const useMessage = () => {
isBot: false, isBot: false,
name: "You", name: "You",
message, message,
webSources: [], sources: [],
iodSources: [], iodSources: [],
images: [image] images: [image]
}, },
@@ -1210,7 +1196,7 @@ export const useMessage = () => {
isBot: true, isBot: true,
name: selectedModel, name: selectedModel,
message: "▋", message: "▋",
webSources: [], sources: [],
iodSources: [], iodSources: [],
id: generateMessageId id: generateMessageId
} }
@@ -1222,7 +1208,7 @@ export const useMessage = () => {
isBot: true, isBot: true,
name: selectedModel, name: selectedModel,
message: "▋", message: "▋",
webSources: [], sources: [],
iodSources: [], iodSources: [],
id: generateMessageId id: generateMessageId
} }
@@ -1300,10 +1286,10 @@ export const useMessage = () => {
query = removeReasoning(query) query = removeReasoning(query)
} }
const { prompt, webSources, iodSources } = const { prompt, source, iodSource } = await getSystemPromptForWeb(query, selectedQuickPrompt)
await getSystemPromptForWeb(query, [], webSearch, iodSearch)
setIsSearchingInternet(false) setIsSearchingInternet(false)
console.log("iodSource:")
console.log(iodSource)
// message = message.trim().replaceAll("\n", " ") // message = message.trim().replaceAll("\n", " ")
let humanMessage = await humanMessageFormatter({ let humanMessage = await humanMessageFormatter({
@@ -1424,8 +1410,8 @@ export const useMessage = () => {
return { return {
...message, ...message,
message: fullText, message: fullText,
webSources, sources: source,
iodSources, iodSources: iodSource,
generationInfo, generationInfo,
reasoning_time_taken: timetaken reasoning_time_taken: timetaken
} }
@@ -1455,8 +1441,8 @@ export const useMessage = () => {
message, message,
image, image,
fullText, fullText,
webSources, source,
iodSources, iodSource,
generationInfo, generationInfo,
reasoning_time_taken: timetaken reasoning_time_taken: timetaken
}) })
@@ -1555,7 +1541,7 @@ export const useMessage = () => {
isBot: false, isBot: false,
name: "You", name: "You",
message, message,
webSources: [], sources: [],
iodSources: [], iodSources: [],
images: [image], images: [image],
messageType: messageType messageType: messageType
@@ -1564,7 +1550,7 @@ export const useMessage = () => {
isBot: true, isBot: true,
name: selectedModel, name: selectedModel,
message: "▋", message: "▋",
webSources: [], sources: [],
iodSources: [], iodSources: [],
id: generateMessageId id: generateMessageId
} }
@@ -1576,7 +1562,7 @@ export const useMessage = () => {
isBot: true, isBot: true,
name: selectedModel, name: selectedModel,
message: "▋", message: "▋",
webSources: [], sources: [],
iodSources: [], iodSources: [],
id: generateMessageId id: generateMessageId
} }
@@ -1723,8 +1709,8 @@ export const useMessage = () => {
message, message,
image, image,
fullText, fullText,
webSources: [], source: [],
iodSources: [], iodSource:[],
message_source: "copilot", message_source: "copilot",
message_type: messageType, message_type: messageType,
generationInfo, generationInfo,
@@ -1802,16 +1788,14 @@ export const useMessage = () => {
) )
} else { } else {
if (chatMode === "normal") { if (chatMode === "normal") {
if (webSearch || iodSearch) { if (webSearch) {
await searchChatMode( await searchChatMode(
webSearch,
iodSearch,
message, message,
image, image,
isRegenerate || false, isRegenerate || false,
messages, messages,
memory || history, memory || history,
signal, signal
) )
} else { } else {
await normalChatMode( await normalChatMode(
@@ -1944,8 +1928,6 @@ export const useMessage = () => {
regenerateLastMessage, regenerateLastMessage,
webSearch, webSearch,
setWebSearch, setWebSearch,
iodSearch,
setIodSearch,
isSearchingInternet, isSearchingInternet,
selectedQuickPrompt, selectedQuickPrompt,
setSelectedQuickPrompt, setSelectedQuickPrompt,

View File

@@ -3,12 +3,11 @@ import { cleanUrl } from "~/libs/clean-url"
import { import {
defaultEmbeddingModelForRag, defaultEmbeddingModelForRag,
geWebSearchFollowUpPrompt, geWebSearchFollowUpPrompt,
geWebSearchKeywordsPrompt,
getOllamaURL, getOllamaURL,
promptForRag, promptForRag,
systemPromptForNonRagOption systemPromptForNonRagOption
} from "~/services/ollama" } from "~/services/ollama"
import { type ChatHistory, ChatMessage, type Message } from "~/store/option" import { type ChatHistory, type Message } from "~/store/option"
import { SystemMessage } from "@langchain/core/messages" import { SystemMessage } from "@langchain/core/messages"
import { useStoreMessageOption } from "~/store/option" import { useStoreMessageOption } from "~/store/option"
import { import {
@@ -55,8 +54,6 @@ export const useMessageOption = () => {
const { const {
history, history,
setHistory, setHistory,
chatMessages,
setChatMessages,
setStreaming, setStreaming,
streaming, streaming,
setIsFirstMessage, setIsFirstMessage,
@@ -70,8 +67,6 @@ export const useMessageOption = () => {
setChatMode, setChatMode,
webSearch, webSearch,
setWebSearch, setWebSearch,
iodSearch,
setIodSearch,
isSearchingInternet, isSearchingInternet,
setIsSearchingInternet, setIsSearchingInternet,
selectedQuickPrompt, selectedQuickPrompt,
@@ -114,30 +109,8 @@ export const useMessageOption = () => {
setWebSearch(true) setWebSearch(true)
} }
} }
// 从最后的结果中解析出 思维链 和 结果
const responseResolver = (msg: string) => {
const thinkStart = msg.indexOf("<think>")
const thinkEnd = msg.indexOf("</think>")
let think = ""
let content = ""
if (thinkStart > -1 && thinkEnd > -1) {
think = msg.substring(thinkStart + 7, thinkEnd)
content = msg.substring(thinkEnd + 8)
} else {
content = msg
}
// 去掉换行符
think = think.replace(/\n/g, "")
content = content.replace(/\n/g, "")
return {
think,
content
}
}
const searchChatMode = async ( const searchChatMode = async (
webSearch: boolean,
iodSearch: boolean,
message: string, message: string,
image: string, image: string,
isRegenerate: boolean, isRegenerate: boolean,
@@ -188,13 +161,9 @@ export const useMessageOption = () => {
useMlock: useMlock:
currentChatModelSettings?.useMlock ?? userDefaultModelSettings?.useMlock currentChatModelSettings?.useMlock ?? userDefaultModelSettings?.useMlock
}) })
let newMessage: Message[] = [] let newMessage: Message[] = []
let generateMessageId = generateID() let generateMessageId = generateID()
const chatMessage: ChatMessage = {
id: generateMessageId,
queryContent: message,
date: new Date()
} as ChatMessage
if (!isRegenerate) { if (!isRegenerate) {
newMessage = [ newMessage = [
@@ -203,7 +172,7 @@ export const useMessageOption = () => {
isBot: false, isBot: false,
name: "You", name: "You",
message, message,
webSources: [], sources: [],
iodSources: [], iodSources: [],
images: [image] images: [image]
}, },
@@ -211,7 +180,7 @@ export const useMessageOption = () => {
isBot: true, isBot: true,
name: selectedModel, name: selectedModel,
message: "▋", message: "▋",
webSources: [], sources: [],
iodSources: [], iodSources: [],
id: generateMessageId id: generateMessageId
} }
@@ -223,7 +192,7 @@ export const useMessageOption = () => {
isBot: true, isBot: true,
name: selectedModel, name: selectedModel,
message: "▋", message: "▋",
webSources: [], sources: [],
iodSources: [], iodSources: [],
id: generateMessageId id: generateMessageId
} }
@@ -238,8 +207,7 @@ export const useMessageOption = () => {
setIsSearchingInternet(true) setIsSearchingInternet(true)
let query = message let query = message
let keywords: string[] = [] /*
if (newMessage.length > 2) { if (newMessage.length > 2) {
let questionPrompt = await geWebSearchFollowUpPrompt() let questionPrompt = await geWebSearchFollowUpPrompt()
const lastTenMessages = newMessage.slice(-10) const lastTenMessages = newMessage.slice(-10)
@@ -302,30 +270,17 @@ export const useMessageOption = () => {
query = response.content.toString() query = response.content.toString()
query = removeReasoning(query) query = removeReasoning(query)
} }
*/
// Currently only IoD search use keywords const quickPrompt = selectedQuickPrompt;
if (iodSearch) { console.log("quick prompt:"+quickPrompt)
// Extract keywords const { prompt, source, iodSource } = await getSystemPromptForWeb(query, quickPrompt)
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, iodData, iodDataTokenCount } =
await getSystemPromptForWeb(query, keywords, webSearch, iodSearch)
console.log("prompt:\n" + prompt)
setIsSearchingInternet(false) setIsSearchingInternet(false)
chatMessage.prompt = prompt console.log("iodSource from useMessageOption:")
chatMessage.iodKeywords = keywords console.log(iodSource)
chatMessage.iodData = iodData console.log("prompt")
chatMessage.iodDataTokenCount = iodDataTokenCount console.log(prompt)
console.log("query")
console.log(query)
// message = message.trim().replaceAll("\n", " ") // message = message.trim().replaceAll("\n", " ")
let humanMessage = await humanMessageFormatter({ let humanMessage = await humanMessageFormatter({
@@ -445,8 +400,8 @@ export const useMessageOption = () => {
return { return {
...message, ...message,
message: fullText, message: fullText,
webSources, sources: source,
iodSources, iodSources:iodSource,
generationInfo, generationInfo,
reasoning_time_taken: timetaken reasoning_time_taken: timetaken
} }
@@ -476,25 +431,14 @@ export const useMessageOption = () => {
message, message,
image, image,
fullText, fullText,
webSources, source,
iodSources, iodSource,
generationInfo, generationInfo,
reasoning_time_taken: timetaken reasoning_time_taken: timetaken
}) })
setIsProcessing(false) setIsProcessing(false)
setStreaming(false) setStreaming(false)
chatMessage.modelInputTokenCount = prompt.length
chatMessage.modelOutputTokenCount = fullText.length
chatMessage.model = ollama.modelName
chatMessage.relatedDataCount = iodData?.length ?? 0
chatMessage.timeTaken = new Date().getTime() - chatMessage.date.getTime()
const { think, content } = responseResolver(fullText)
chatMessage.thinkingChain = think
chatMessage.responseContent = content
chatMessage.modelResponseContent = fullText
setChatMessages([chatMessage, ...chatMessages])
} catch (e) { } catch (e) {
const errorSave = await saveMessageOnError({ const errorSave = await saveMessageOnError({
e, e,
@@ -620,7 +564,7 @@ export const useMessageOption = () => {
isBot: false, isBot: false,
name: "You", name: "You",
message, message,
webSources: [], sources: [],
iodSources: [], iodSources: [],
images: [image] images: [image]
}, },
@@ -628,7 +572,7 @@ export const useMessageOption = () => {
isBot: true, isBot: true,
name: selectedModel, name: selectedModel,
message: "▋", message: "▋",
webSources: [], sources: [],
iodSources: [], iodSources: [],
id: generateMessageId id: generateMessageId
} }
@@ -640,7 +584,7 @@ export const useMessageOption = () => {
isBot: true, isBot: true,
name: selectedModel, name: selectedModel,
message: "▋", message: "▋",
webSources: [], sources: [],
iodSources: [], iodSources: [],
id: generateMessageId id: generateMessageId
} }
@@ -828,6 +772,7 @@ export const useMessageOption = () => {
image, image,
fullText, fullText,
source: [], source: [],
iodSource:[],
generationInfo, generationInfo,
prompt_content: promptContent, prompt_content: promptContent,
prompt_id: promptId, prompt_id: promptId,
@@ -926,7 +871,7 @@ export const useMessageOption = () => {
isBot: false, isBot: false,
name: "You", name: "You",
message, message,
webSources: [], sources: [],
iodSources: [], iodSources: [],
images: [] images: []
}, },
@@ -934,7 +879,7 @@ export const useMessageOption = () => {
isBot: true, isBot: true,
name: selectedModel, name: selectedModel,
message: "▋", message: "▋",
webSources: [], sources: [],
iodSources: [], iodSources: [],
id: generateMessageId id: generateMessageId
} }
@@ -946,7 +891,7 @@ export const useMessageOption = () => {
isBot: true, isBot: true,
name: selectedModel, name: selectedModel,
message: "▋", message: "▋",
webSources: [], sources: [],
iodSources: [], iodSources: [],
id: generateMessageId id: generateMessageId
} }
@@ -1053,7 +998,8 @@ export const useMessageOption = () => {
} }
}) })
// message = message.trim().replaceAll("\n", " ") // message = message.trim().replaceAll("\n", " ")
const iodSource = []
//TODO not support iodSource in RAG
let humanMessage = await humanMessageFormatter({ let humanMessage = await humanMessageFormatter({
content: [ content: [
{ {
@@ -1150,7 +1096,8 @@ export const useMessageOption = () => {
return { return {
...message, ...message,
message: fullText, message: fullText,
webSources: source, sources: source,
iodSources: iodSource,
generationInfo, generationInfo,
reasoning_time_taken: timetaken reasoning_time_taken: timetaken
} }
@@ -1181,6 +1128,7 @@ export const useMessageOption = () => {
image, image,
fullText, fullText,
source, source,
iodSource,
generationInfo, generationInfo,
reasoning_time_taken: timetaken reasoning_time_taken: timetaken
}) })
@@ -1249,10 +1197,8 @@ export const useMessageOption = () => {
signal signal
) )
} else { } else {
if (webSearch || iodSearch) { if (webSearch) {
await searchChatMode( await searchChatMode(
webSearch,
iodSearch,
message, message,
image, image,
isRegenerate, isRegenerate,
@@ -1387,8 +1333,6 @@ export const useMessageOption = () => {
regenerateLastMessage, regenerateLastMessage,
webSearch, webSearch,
setWebSearch, setWebSearch,
iodSearch,
setIodSearch,
isSearchingInternet, isSearchingInternet,
setIsSearchingInternet, setIsSearchingInternet,
selectedQuickPrompt, selectedQuickPrompt,

View File

@@ -72,7 +72,7 @@ export const pageAssistModel = async ({
configuration: { configuration: {
apiKey: providerInfo.apiKey || "temp", apiKey: providerInfo.apiKey || "temp",
baseURL: providerInfo.baseUrl || "" baseURL: providerInfo.baseUrl || ""
}, }
}) as any }) as any
} }
@@ -85,7 +85,7 @@ export const pageAssistModel = async ({
configuration: { configuration: {
apiKey: providerInfo.apiKey || "temp", apiKey: providerInfo.apiKey || "temp",
baseURL: providerInfo.baseUrl || "" baseURL: providerInfo.baseUrl || ""
}, }
}) as any }) as any
} }
return new ChatOllama({ return new ChatOllama({

View File

@@ -12,8 +12,6 @@ import SidepanelSettings from "./sidepanel-settings"
import OptionRagSettings from "./option-rag" import OptionRagSettings from "./option-rag"
import OptionChrome from "./option-settings-chrome" import OptionChrome from "./option-settings-chrome"
import OptionOpenAI from "./option-settings-openai" import OptionOpenAI from "./option-settings-openai"
import OptionMetering from "./option-metering"
import MeteringListDetail from "./metering-list-detail"
export const OptionRoutingChrome = () => { export const OptionRoutingChrome = () => {
return ( return (
@@ -29,8 +27,6 @@ export const OptionRoutingChrome = () => {
<Route path="/settings/knowledge" element={<OptionKnowledgeBase />} /> <Route path="/settings/knowledge" element={<OptionKnowledgeBase />} />
<Route path="/settings/rag" element={<OptionRagSettings />} /> <Route path="/settings/rag" element={<OptionRagSettings />} />
<Route path="/settings/about" element={<OptionAbout />} /> <Route path="/settings/about" element={<OptionAbout />} />
<Route path="/metering" element={<OptionMetering />} />
<Route path="/metering/list/:id" element={<MeteringListDetail />} />
</Routes> </Routes>
) )
} }

View File

@@ -10,8 +10,6 @@ const OptionModal = lazy(() => import("./option-settings-model"))
const OptionPrompt = lazy(() => import("./option-settings-prompt")) const OptionPrompt = lazy(() => import("./option-settings-prompt"))
const OptionOllamaSettings = lazy(() => import("./options-settings-ollama")) const OptionOllamaSettings = lazy(() => import("./options-settings-ollama"))
const OptionSettings = lazy(() => import("./option-settings")) const OptionSettings = lazy(() => import("./option-settings"))
const OptionMetering = lazy(() => import("./option-metering"))
const MeteringListDetail = lazy(() => import("./metering-list-detail"))
const OptionShare = lazy(() => import("./option-settings-share")) const OptionShare = lazy(() => import("./option-settings-share"))
const OptionKnowledgeBase = lazy(() => import("./option-settings-knowledge")) const OptionKnowledgeBase = lazy(() => import("./option-settings-knowledge"))
const OptionAbout = lazy(() => import("./option-settings-about")) const OptionAbout = lazy(() => import("./option-settings-about"))
@@ -31,8 +29,6 @@ export const OptionRoutingFirefox = () => {
<Route path="/settings/knowledge" element={<OptionKnowledgeBase />} /> <Route path="/settings/knowledge" element={<OptionKnowledgeBase />} />
<Route path="/settings/about" element={<OptionAbout />} /> <Route path="/settings/about" element={<OptionAbout />} />
<Route path="/settings/rag" element={<OptionRagSettings />} /> <Route path="/settings/rag" element={<OptionRagSettings />} />
<Route path="/metering" element={<OptionMetering />} />
<Route path="/metering/list/:id" element={<MeteringListDetail />} />
</Routes> </Routes>
) )
} }

View File

@@ -1,12 +0,0 @@
import OptionLayout from "~/components/Layouts/Layout"
import { ListDetail } from "~/components/Option/Metering/listDetail"
const OptionSettings = () => {
return (
<OptionLayout>
<ListDetail />
</OptionLayout>
)
}
export default OptionSettings

View File

@@ -1,12 +0,0 @@
import OptionLayout from "~/components/Layouts/Layout"
import { MeteringDetail } from "~/components/Option/Metering/detail"
const OptionSettings = () => {
return (
<OptionLayout>
<MeteringDetail />
</OptionLayout>
)
}
export default OptionSettings

View File

@@ -21,39 +21,15 @@ const DEFAULT_RAG_QUESTION_PROMPT =
const DEFAUTL_RAG_SYSTEM_PROMPT = `You are a helpful AI assistant. Use the following pieces of context to answer the question at the end. If you don't know the answer, just say you don't know. DO NOT try to make up an answer. If the question is not related to the context, politely respond that you are tuned to only answer questions that are related to the context. {context} Question: {question} Helpful answer:` const DEFAUTL_RAG_SYSTEM_PROMPT = `You are a helpful AI assistant. Use the following pieces of context to answer the question at the end. If you don't know the answer, just say you don't know. DO NOT try to make up an answer. If the question is not related to the context, politely respond that you are tuned to only answer questions that are related to the context. {context} Question: {question} Helpful answer:`
const DEFAULT_WEBSEARCH_PROMPT = `You are an AI model who is expert at searching the web and answering user's queries. const DEFAULT_WEBSEARCH_PROMP = `You are an AI model who is expert at searching the web and answering user's queries.
Generate a response that is informative and relevant to the user's query based on provided search results. the current date and time are {current_date_time}. Generate a response that is informative and relevant to the user's query based on provided search results. the current date and time are {current_date_time}.
\`iod-search-results\` block provides knowledge from the Internet of Data (数联网) search results. Each search result has a format of: \`search-results\` block provides knowledge from the web search results. You can use this information to generate a meaningful response.
\`<result doId="{doId}" name="{name}" url="{url}" id="{id}">{content}</result>\`
Please show the \`doId\` and \`name\` of the search result when you cite the Internet of Data search result, in the following format, in English:
\`[IoD source [id] doId: {doId} "{name}"]({url})\`
Or in Chinese:
\`[数联网引用[id] doId: {doId} "{name}"]({url})\`
For example, in English:
\`[IoD source [1] doId: 10.48550/arXiv.1803.05591v2 "On the insufficiency of existing momentum schemes for Stochastic Optimization"](http://arxiv.org/pdf/1803.05591v2.pdf)\`
Or in Chinese:
\`[数联网引用[1] doId: 10.48550/arXiv.1803.05591v2 "On the insufficiency of existing momentum schemes for Stochastic Optimization"](http://arxiv.org/pdf/1803.05591v2.pdf)\`
\`web-search-results\` block provides knowledge from the World Wide Web (万维网) search results. <search-results>
Please show the \`doId\` and \`name\` of the search result when you cite the search result, in the following format, in English: {search_results}
\`[3W source [id] "{name}"]({url})\` </search-results>
Or in Chinese:
\`[万维网引用[id] "{name}"]({url})\`
For example, in English:
\`[3W source [1] On the insufficiency of existing momentum schemes for Stochastic Optimization](http://arxiv.org/pdf/1803.05591v2.pdf)\`
Or in Chinese:
\`[万维网引用[1] On the insufficiency of existing momentum schemes for Stochastic Optimization](http://arxiv.org/pdf/1803.05591v2.pdf)\`
You can use these information to generate a meaningful response.
<iod-search-results>
{iod_search_results}
</iod-search-results>
<web-search-results>
{web_search_results}
</web-search-results>
` `
const DEFAULT_WEBSEARCH_FOLLOWUP_PROMPT = `You will give a follow-up question. You need to rephrase the follow-up question if needed so it is a standalone question that can be used by the AI model to search the internet. const DEFAULT_WEBSEARCH_FOLLOWUP_PROMPT = `You will give a follow-up question. You need to rephrase the follow-up question if needed so it is a standalone question that can be used by the AI model to search the internet.
@@ -82,30 +58,6 @@ Follow-up question: {question}
Rephrased 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 () => { export const getOllamaURL = async () => {
const ollamaURL = await storage.get("ollamaURL") const ollamaURL = await storage.get("ollamaURL")
if (!ollamaURL || ollamaURL.length === 0) { if (!ollamaURL || ollamaURL.length === 0) {
@@ -433,7 +385,7 @@ export const saveForRag = async (
export const getWebSearchPrompt = async () => { export const getWebSearchPrompt = async () => {
const prompt = await storage.get("webSearchPrompt") const prompt = await storage.get("webSearchPrompt")
if (!prompt || prompt.length === 0) { if (!prompt || prompt.length === 0) {
return DEFAULT_WEBSEARCH_PROMPT return DEFAULT_WEBSEARCH_PROMP
} }
return prompt return prompt
} }
@@ -459,18 +411,6 @@ export const setWebPrompts = async (prompt: string, followUpPrompt: string) => {
await setWebSearchFollowUpPrompt(followUpPrompt) 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 () => { export const getPageShareUrl = async () => {
const pageShareUrl = await storage.get("pageShareUrl") const pageShareUrl = await storage.get("pageShareUrl")
if (!pageShareUrl || pageShareUrl.length === 0) { if (!pageShareUrl || pageShareUrl.length === 0) {

View File

@@ -14,7 +14,7 @@ export type Message = {
isBot: boolean isBot: boolean
name: string name: string
message: string message: string
webSources: any[] sources: any[]
iodSources: any[] iodSources: any[]
images?: string[] images?: string[]
search?: WebSearch search?: WebSearch
@@ -26,53 +26,15 @@ export type Message = {
export type ChatHistory = { export type ChatHistory = {
role: "user" | "assistant" | "system" role: "user" | "assistant" | "system"
content: string content: string
image?: string image?: string,
messageType?: string messageType?: string
}[] }[]
export type ChatMessage = {
id: string
// 问题
queryContent: string
// 提示词全文
prompt: string
// 思维链(只有深度思考时有)
thinkingChain?: string
// 回答
responseContent: string
// 关联数据个数
relatedDataCount: number
// 数联网输入token
iodInputToken: string
// 数联网输出token
iodOutputToken: string
// 大模型输入token数量
modelInputTokenCount: number
// 大模型输出token数量
modelOutputTokenCount: number
// 日期
date: Date
// 耗时
timeTaken: number
// 大模型回答的全部内容
modelResponseContent: string
// iod的全部内容的token数量
iodDataTokenCount: number
// iod返回的数据
iodData: any[]
// iod keywords
iodKeywords: string[]
// 模型
model: string
}
type State = { type State = {
messages: Message[] messages: Message[]
setMessages: (messages: Message[]) => void setMessages: (messages: Message[]) => void
history: ChatHistory history: ChatHistory
setHistory: (history: ChatHistory) => void setHistory: (history: ChatHistory) => void
chatMessages: ChatMessage[]
setChatMessages: (chatMessages: ChatMessage[]) => void
streaming: boolean streaming: boolean
setStreaming: (streaming: boolean) => void setStreaming: (streaming: boolean) => void
isFirstMessage: boolean isFirstMessage: boolean
@@ -91,8 +53,6 @@ type State = {
setIsEmbedding: (isEmbedding: boolean) => void setIsEmbedding: (isEmbedding: boolean) => void
webSearch: boolean webSearch: boolean
setWebSearch: (webSearch: boolean) => void setWebSearch: (webSearch: boolean) => void
iodSearch: boolean
setIodSearch: (iodSearch: boolean) => void
isSearchingInternet: boolean isSearchingInternet: boolean
setIsSearchingInternet: (isSearchingInternet: boolean) => void setIsSearchingInternet: (isSearchingInternet: boolean) => void
@@ -120,8 +80,6 @@ export const useStoreMessageOption = create<State>((set) => ({
setMessages: (messages) => set({ messages }), setMessages: (messages) => set({ messages }),
history: [], history: [],
setHistory: (history) => set({ history }), setHistory: (history) => set({ history }),
chatMessages: [],
setChatMessages: (chatMessages) => set({ chatMessages }),
streaming: false, streaming: false,
setStreaming: (streaming) => set({ streaming }), setStreaming: (streaming) => set({ streaming }),
isFirstMessage: true, isFirstMessage: true,
@@ -143,8 +101,6 @@ export const useStoreMessageOption = create<State>((set) => ({
setIsEmbedding: (isEmbedding) => set({ isEmbedding }), setIsEmbedding: (isEmbedding) => set({ isEmbedding }),
webSearch: false, webSearch: false,
setWebSearch: (webSearch) => set({ webSearch }), setWebSearch: (webSearch) => set({ webSearch }),
iodSearch: false,
setIodSearch: (iodSearch) => set({ iodSearch }),
isSearchingInternet: false, isSearchingInternet: false,
setIsSearchingInternet: (isSearchingInternet) => set({ isSearchingInternet }), setIsSearchingInternet: (isSearchingInternet) => set({ isSearchingInternet }),
selectedSystemPrompt: null, selectedSystemPrompt: null,
@@ -160,5 +116,5 @@ export const useStoreMessageOption = create<State>((set) => ({
setTemporaryChat: (temporaryChat) => set({ temporaryChat }), setTemporaryChat: (temporaryChat) => set({ temporaryChat }),
useOCR: false, useOCR: false,
setUseOCR: (useOCR) => set({ useOCR }) setUseOCR: (useOCR) => set({ useOCR }),
})) }))

View File

@@ -1,9 +0,0 @@
export type IodRegistryEntry = {
doId: string
name: string
url?: string
pdf_url?: string
description: string
content?: string
data_space?: string
}

View File

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

View File

@@ -1,5 +0,0 @@
export type WebSearchResult = {
url: string
name: string
content: string
}

View File

@@ -1,22 +0,0 @@
export function formatDate(date) {
// 获取年份
const year = date.getFullYear()
// 获取月份注意月份是从0开始计数的所以需要加1并且确保月份是两位数
const month = String(date.getMonth() + 1).padStart(2, "0")
// 获取日期,确保日期是两位数
const day = String(date.getDate()).padStart(2, "0")
// 获取小时24小时制并确保小时是两位数
const hours = String(date.getHours()).padStart(2, "0")
// 获取分钟,并确保分钟是两位数
const minutes = String(date.getMinutes()).padStart(2, "0")
// 组合成所需的格式
return `${year}-${month}-${day} ${hours}:${minutes}`
}
// 示例使用
const now = new Date()

View File

@@ -1,211 +0,0 @@
import { cleanUrl } from "@/libs/clean-url"
import { PageAssistHtmlLoader } from "@/loader/html"
import { PageAssistPDFUrlLoader } from "@/loader/pdf-url"
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"
import type { IodRegistryEntry } from "~/types/iod"
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 async function localIodSearch(
query: string,
keywords: string[]
): Promise<IodRegistryEntry[]> {
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: IodRegistryEntry[] =
body.data?.results?.filter((r) => r.url || r.pdf_url) || []
for (const r of results) {
r.url = r.url || r.pdf_url
}
return results
})
.catch((e) => {
console.log(e)
return []
})
})
)
).flat()
// results 根据 doId 去重
const map = new Map<string, IodRegistryEntry>()
for (const r of results) {
map.set(r.doId, r)
}
return Array.from(map.values())
}
const ARXIV_URL_PATTERN = /^https?:\/\/arxiv\.org\//
const ARXIV_NO_HTM = "No HTML for"
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>>[] = []
const resMap = new Map<string, IodRegistryEntry>()
for (const result of searchResults) {
const url = result.url
if (!url) continue
let htmlUrl = ""
if (ARXIV_URL_PATTERN.test(url)) {
htmlUrl = url.replace("/pdf/", "/html/").replace(".pdf", "")
}
let noHtml = htmlUrl === ""
if (!noHtml) {
const loader = new PageAssistHtmlLoader({
html: "",
url: htmlUrl
})
try {
const documents = await loader.loadByURL()
for (const doc of documents) {
if (doc.pageContent.includes(ARXIV_NO_HTM)) {
noHtml = true
return
}
docs.push(doc)
}
} catch (e) {
console.log(e)
noHtml = true
}
}
if (noHtml) {
if (url.endsWith(".pdf")) {
const loader = new PageAssistPDFUrlLoader({
name: result.name,
url
})
try {
const documents = await loader.load()
for (const doc of documents) {
docs.push(doc)
}
} catch (e) {
console.log(e)
}
} else {
const loader = new PageAssistHtmlLoader({
html: "",
url
})
try {
const documents = await loader.loadByURL()
for (const doc of documents) {
docs.push(doc)
}
} catch (e) {
console.log(e)
}
}
}
}
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) => {
// `source` for PDF type
const key = result.metadata.url || result.metadata.source
if (!key) return null
const fullRes = resMap[key]
return {
...fullRes,
content: result.pageContent
}
}).filter((r) => r)
return searchResult
}

View File

@@ -8,9 +8,7 @@ import { getWebsiteFromQuery, processSingleWebsite } from "./website"
import { searxngSearch } from "./search-engines/searxng" import { searxngSearch } from "./search-engines/searxng"
import { braveAPISearch } from "./search-engines/brave-api" import { braveAPISearch } from "./search-engines/brave-api"
import { webBaiduSearch } from "./search-engines/baidu" import { webBaiduSearch } from "./search-engines/baidu"
import { searchIod } from "./iod" import { LucideToggleRight } from "lucide-react"
import type { WebSearchResult } from "~/types/web"
import type { IodRegistryEntry } from "~/types/iod"
const getHostName = (url: string) => { const getHostName = (url: string) => {
try { try {
@@ -21,134 +19,110 @@ const getHostName = (url: string) => {
} }
} }
async function searchWeb( const searchWeb = (provider: string, query: string) => {
provider: string,
query: string
): Promise<WebSearchResult[]> {
let results = []
switch (provider) { switch (provider) {
case "duckduckgo": case "duckduckgo":
results = await webDuckDuckGoSearch(query) return webDuckDuckGoSearch(query)
break
case "sogou": case "sogou":
results = await webSogouSearch(query) return webSogouSearch(query)
break
case "brave": case "brave":
results = await webBraveSearch(query) return webBraveSearch(query)
break
case "searxng": case "searxng":
results = await searxngSearch(query) return searxngSearch(query)
break
case "brave-api": case "brave-api":
results = await braveAPISearch(query) return braveAPISearch(query)
break
case "baidu": case "baidu":
results = await webBaiduSearch(query) return webBaiduSearch(query)
break
default: default:
results = await webGoogleSearch(query) return webGoogleSearch(query)
break
} }
return results.map((r) => ({ ...r, name: getHostName(r.url) }))
} }
export const getSystemPromptForWeb = async ( export const getSystemPromptForWeb = async (query: string, promptMode) => {
query: string,
keywords: string[] = [],
webSearch = true,
iodSearch = false
) => {
try { try {
const websiteVisit = getWebsiteFromQuery(query) if (!promptMode){
let webSearchResults: WebSearchResult[] = [] return {
// let search_results_web = "" prompt: "",
source: [],
if (webSearch) { iodSource:[]
const isVisitSpecificWebsite = await getIsVisitSpecificWebsite()
if (isVisitSpecificWebsite && websiteVisit.hasUrl) {
const url = websiteVisit.url
const queryWithoutUrl = websiteVisit.queryWithouUrls
webSearchResults = await processSingleWebsite(url, queryWithoutUrl)
} else {
const searchProvider = await getSearchProvider()
webSearchResults = await searchWeb(searchProvider, query)
} }
}
// search_results_web = webSearchResults let iodsearch = []
// .map( if (promptMode.indexOf("iod_search_results")!=-1){
// (result, idx) => iodsearch = [
// `<result source="${result.url}" id="${idx}">${result.content}</result>` {
// ) url:"http://bdware.cn/resolve?id=CSTR:432421111.1233.53323",
// .join("\n") content:"数联网Internet Of Data):数据作为互联网上可独立管理的资源,在“物理/机器”互联网之上形成一个“虚拟/数据”网络,实现全网一体化的数据互联互通互操作。",
id:"CSTR:432421111.1233.53323,数联网定义"
}, {
url:"http://bdware.cn/resolve?id=CSTR:1121311.3423.7754",
content:"数据空间:面向具体的领域和业务场景,按照数据所对应的物理实体的结构、关系来对数据进行管理和组织,构成物理世界的数字孪生。",
id:"CSTR:1121311.3423.7754,数据空间定义"
}
]
} }
let iodSearchResults: IodRegistryEntry[] = [] const websiteVisit = getWebsiteFromQuery(query)
// let search_results_iod = "" let search: {
url: any;
content: string;
}[] = []
if (iodSearch) { const isVisitSpecificWebsite = await getIsVisitSpecificWebsite()
iodSearchResults = await searchIod(query, keywords) if (isVisitSpecificWebsite && websiteVisit.hasUrl) {
// search_results_iod = iodSearchResults const url = websiteVisit.url
// .map( const queryWithoutUrl = websiteVisit.queryWithouUrls
// (result, idx) => search = await processSingleWebsite(url, queryWithoutUrl)
// `<result source="${result.url}" id="${idx}">${result.content}</result>` } else if (promptMode.indexOf("web_search_results")!=-1) {
// ) const searchProvider = await getSearchProvider()
// .join("\n") search = await searchWeb(searchProvider, query)
} }
const _iodSearchResults = iodSearchResults
.map((res) => ({
doId: res.doId,
name: res.name,
url: res.url,
data_space: res.data_space,
tokenCount: (res.content || res.description)?.length ?? 0,
content: res.content || res.description
}))
const iod_search_results = _iodSearchResults const search_results = search
.map( .map(
(result, idx) => (result, idx) =>
`<result doId="${result.doId}" name="${result.name}" source="${result.url}" id="${idx + 1}">${result.content}</result>` `<result source="${result.url}" id="${idx+1}">${result.content}</result>`
) )
.join("\n") .join("\n")
console.log("iod_search_result: " + iod_search_results)
const web_search_results = webSearchResults
.map(
(result, idx) =>
`<result source="${result.url}" name="${result.name}" id="${idx + 1}">${result.content}</result>`
)
.join("\n")
console.log("web_search_result: " + web_search_results)
const current_date_time = new Date().toLocaleString() const current_date_time = new Date().toLocaleString()
const system = await getWebSearchPrompt() const system = promptMode
const iod_search_results= iodsearch.map(
(result, idx) =>
`<result source="${result.url}" id="${idx+1}">${result.content}</result>`
)
.join("\n")
console.log("iod_search_xml in web.ts")
console.log(iod_search_results)
const prompt = system const prompt = system
.replace("{current_date_time}", current_date_time) .replace("{current_date_time}", current_date_time)
.replace("{iod_search_results}", iod_search_results) .replace("{web_search_results}", search_results)
.replace("{web_search_results}", web_search_results) .replace("{iod_search_results}",iod_search_results)
return { return {
prompt, prompt,
webSources: webSearchResults.map((result) => { source: search.map((result) => {
return { return {
url: result.url, url: result.url,
name: result.name, name: getHostName(result.url),
type: "url" type: "url"
} }
}), }),
iodSources: iodSearchResults, iodSource: iodsearch.map((result) => {
iodData: _iodSearchResults, return {
iodDataTokenCount: _iodSearchResults.reduce((acc, cur) => (acc + cur.content.length), 0) url: result.url,
name: result.id,
type: "url"
}
})
} }
} catch (e) { } catch (e) {
console.error(e) console.error(e)
return { return {
prompt: "", prompt: "",
webSources: [], source: [],
iodSources: [] iodSource:[]
} }
} }
} }