Add lucide-react package and remove unused icons
This commit is contained in:
parent
06b32176a9
commit
43f3727369
@ -26,6 +26,7 @@
|
||||
"dayjs": "^1.11.10",
|
||||
"html-to-text": "^9.0.5",
|
||||
"langchain": "^0.1.9",
|
||||
"lucide-react": "^0.340.0",
|
||||
"plasmo": "0.84.1",
|
||||
"property-information": "^6.4.1",
|
||||
"react": "18.2.0",
|
||||
|
@ -1,9 +1,8 @@
|
||||
import Markdown from "../../Common/Markdown"
|
||||
import React from "react"
|
||||
import { Image, Tooltip } from "antd"
|
||||
import { ClipboardIcon } from "~icons/ClipboardIcon"
|
||||
import { CheckIcon } from "~icons/CheckIcon"
|
||||
import { ArrowPathIcon } from "~icons/ArrowPathIcon"
|
||||
import { WebSearch } from "./WebSearch"
|
||||
import { CheckIcon, ClipboardIcon } from "lucide-react"
|
||||
|
||||
type Props = {
|
||||
message: string
|
||||
@ -17,9 +16,8 @@ type Props = {
|
||||
totalMessages: number
|
||||
onRengerate: () => void
|
||||
isProcessing: boolean
|
||||
webSearch?: {
|
||||
|
||||
}
|
||||
webSearch?: {}
|
||||
isSearchingInternet?: boolean
|
||||
}
|
||||
|
||||
export const PlaygroundMessage = (props: Props) => {
|
||||
@ -49,6 +47,12 @@ export const PlaygroundMessage = (props: Props) => {
|
||||
{props.isBot ? props.name : "You"}
|
||||
</span>
|
||||
|
||||
{props.isBot &&
|
||||
props.isSearchingInternet &&
|
||||
props.currentMessageIndex === props.totalMessages - 1 ? (
|
||||
<WebSearch />
|
||||
) : null}
|
||||
|
||||
<div className="flex flex-grow flex-col">
|
||||
<Markdown message={props.message} />
|
||||
</div>
|
||||
|
25
src/components/Common/Playground/WebSearch.tsx
Normal file
25
src/components/Common/Playground/WebSearch.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import { useWebSearch } from "~store/web"
|
||||
|
||||
export const WebSearch = () => {
|
||||
const {} = useWebSearch()
|
||||
return (
|
||||
<div className="gradient-border mt-4 flex w-56 items-center gap-4 rounded-lg bg-neutral-100 p-1ccc text-slate-900 dark:bg-neutral-800 dark:text-slate-50">
|
||||
<div className="rounded p-1">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className="w-6 h-6">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M12 21a9.004 9.004 0 0 0 8.716-6.747M12 21a9.004 9.004 0 0 1-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 0 1 7.843 4.582M12 3a8.997 8.997 0 0 0-7.843 4.582m15.686 0A11.953 11.953 0 0 1 12 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0 1 21 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0 1 12 16.5c-3.162 0-6.133-.815-8.716-2.247m0 0A9.015 9.015 0 0 1 3 12c0-1.605.42-3.113 1.157-4.418"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="text-sm font-semibold">Searching Web</div>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -4,7 +4,7 @@ import { PlaygroundEmpty } from "./PlaygroundEmpty"
|
||||
import { PlaygroundMessage } from "~components/Common/Playground/Message"
|
||||
|
||||
export const PlaygroundChat = () => {
|
||||
const { messages, streaming, regenerateLastMessage } = useMessageOption()
|
||||
const { messages, streaming, regenerateLastMessage, isSearchingInternet } = useMessageOption()
|
||||
const divRef = React.useRef<HTMLDivElement>(null)
|
||||
React.useEffect(() => {
|
||||
if (divRef.current) {
|
||||
@ -30,6 +30,7 @@ export const PlaygroundChat = () => {
|
||||
totalMessages={messages.length}
|
||||
onRengerate={regenerateLastMessage}
|
||||
isProcessing={streaming}
|
||||
isSearchingInternet={isSearchingInternet}
|
||||
/>
|
||||
))}
|
||||
{messages.length > 0 && (
|
||||
|
@ -4,14 +4,12 @@ import React from "react"
|
||||
import useDynamicTextareaSize from "~hooks/useDynamicTextareaSize"
|
||||
import { toBase64 } from "~libs/to-base64"
|
||||
import { useMessageOption } from "~hooks/useMessageOption"
|
||||
import { Checkbox, Dropdown, Tooltip } from "antd"
|
||||
import { Checkbox, Dropdown, Switch, Tooltip } from "antd"
|
||||
import { Image } from "antd"
|
||||
import { useSpeechRecognition } from "~hooks/useSpeechRecognition"
|
||||
import { MicIcon } from "~icons/MicIcon"
|
||||
import { StopCircleIcon } from "~icons/StopCircleIcon"
|
||||
import { PhotoIcon } from "~icons/PhotoIcon"
|
||||
import { XMarkIcon } from "~icons/XMarkIcon"
|
||||
import { useWebUI } from "~store/webui"
|
||||
import { defaultEmbeddingModelForRag } from "~services/ollama"
|
||||
import { ImageIcon, MicIcon, StopCircleIcon, X } from "lucide-react"
|
||||
|
||||
type Props = {
|
||||
dropedFile: File | undefined
|
||||
@ -68,7 +66,9 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
|
||||
chatMode,
|
||||
speechToTextLanguage,
|
||||
stopStreamingRequest,
|
||||
streaming: isSending
|
||||
streaming: isSending,
|
||||
webSearch,
|
||||
setWebSearch
|
||||
} = useMessageOption()
|
||||
|
||||
const { isListening, start, stop, transcript } = useSpeechRecognition()
|
||||
@ -110,19 +110,10 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
|
||||
form.setFieldValue("image", "")
|
||||
}}
|
||||
className="flex items-center justify-center absolute top-0 m-2 bg-white dark:bg-[#262626] p-1 rounded-full hover:bg-gray-100 dark:hover:bg-gray-600 text-black dark:text-gray-100">
|
||||
<XMarkIcon className="h-5 w-5" />
|
||||
<X className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/* <div className="flex gap-3 justify-end">
|
||||
<Tooltip title="New Chat">
|
||||
<button
|
||||
onClick={clearChat}
|
||||
className="text-gray-500 dark:text-gray-100 mr-3">
|
||||
<ArrowPathIcon className="h-5 w-5" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div> */}
|
||||
<div>
|
||||
<div className="flex">
|
||||
<form
|
||||
@ -131,6 +122,16 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
|
||||
form.setFieldError("message", "Please select a model")
|
||||
return
|
||||
}
|
||||
if (webSearch) {
|
||||
const defaultEM = await defaultEmbeddingModelForRag()
|
||||
if (!defaultEM) {
|
||||
form.setFieldError(
|
||||
"message",
|
||||
"Please set an embedding model on the Settings > Ollama page"
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
form.reset()
|
||||
resetHeight()
|
||||
await sendMessage({
|
||||
@ -167,6 +168,16 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
|
||||
form.setFieldError("message", "Please select a model")
|
||||
return
|
||||
}
|
||||
if (webSearch) {
|
||||
const defaultEM = await defaultEmbeddingModelForRag()
|
||||
if (!defaultEM) {
|
||||
form.setFieldError(
|
||||
"message",
|
||||
"Please set an embedding model on the Settings > Ollama page"
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
form.reset()
|
||||
resetHeight()
|
||||
await sendMessage({
|
||||
@ -177,7 +188,7 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
|
||||
}
|
||||
}}
|
||||
ref={textareaRef}
|
||||
className="px-2 py-2 w-full resize-none bg-transparent focus-within:outline-none sm:text-sm focus:ring-0 focus-visible:ring-0 ring-0 dark:ring-0 border-0 dark:text-gray-100"
|
||||
className="px-2 py-2 w-full resize-none bg-transparent focus-within:outline-none focus:ring-0 focus-visible:ring-0 ring-0 dark:ring-0 border-0 dark:text-gray-100"
|
||||
required
|
||||
rows={1}
|
||||
style={{ minHeight: "60px" }}
|
||||
@ -185,7 +196,33 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
|
||||
placeholder="Type a message..."
|
||||
{...form.getInputProps("message")}
|
||||
/>
|
||||
<div className="flex mt-4 !justify-end gap-3">
|
||||
<div className="mt-4 flex justify-between items-center">
|
||||
<div className="flex">
|
||||
<Tooltip title="Search Internet">
|
||||
<div className="inline-flex items-center gap-2">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className="w-5 h-5 dark:text-gray-300">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M12 21a9.004 9.004 0 0 0 8.716-6.747M12 21a9.004 9.004 0 0 1-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 0 1 7.843 4.582M12 3a8.997 8.997 0 0 0-7.843 4.582m15.686 0A11.953 11.953 0 0 1 12 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0 1 21 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0 1 12 16.5c-3.162 0-6.133-.815-8.716-2.247m0 0A9.015 9.015 0 0 1 3 12c0-1.605.42-3.113 1.157-4.418"
|
||||
/>
|
||||
</svg>
|
||||
<Switch
|
||||
value={webSearch}
|
||||
onChange={(e) => setWebSearch(e)}
|
||||
checkedChildren="On"
|
||||
unCheckedChildren="Off"
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="flex !justify-end gap-3">
|
||||
<Tooltip title="Voice Message">
|
||||
<button
|
||||
type="button"
|
||||
@ -219,7 +256,7 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
|
||||
className={`flex items-center justify-center dark:text-gray-300 ${
|
||||
chatMode === "rag" ? "hidden" : "block"
|
||||
}`}>
|
||||
<PhotoIcon className="h-5 w-5" />
|
||||
<ImageIcon className="h-5 w-5" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
{!isSending ? (
|
||||
@ -250,6 +287,7 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
|
||||
key: 1,
|
||||
label: (
|
||||
<Checkbox
|
||||
value={sendWhenEnter}
|
||||
onChange={(e) =>
|
||||
setSendWhenEnter(e.target.checked)
|
||||
}>
|
||||
@ -289,6 +327,7 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{form.errors.message && (
|
||||
|
@ -6,11 +6,9 @@ import { useMessage } from "~hooks/useMessage"
|
||||
import { toBase64 } from "~libs/to-base64"
|
||||
import { Checkbox, Dropdown, Image, Tooltip } from "antd"
|
||||
import { useSpeechRecognition } from "~hooks/useSpeechRecognition"
|
||||
import { MicIcon } from "~icons/MicIcon"
|
||||
import { PhotoIcon } from "~icons/PhotoIcon"
|
||||
import { XMarkIcon } from "~icons/XMarkIcon"
|
||||
import { useWebUI } from "~store/webui"
|
||||
import { defaultEmbeddingModelForRag } from "~services/ollama"
|
||||
import { ImageIcon, MicIcon, X } from "lucide-react"
|
||||
|
||||
type Props = {
|
||||
dropedFile: File | undefined
|
||||
@ -88,7 +86,7 @@ export const SidepanelForm = ({ dropedFile }: Props) => {
|
||||
form.setFieldValue("image", "")
|
||||
}}
|
||||
className="flex items-center justify-center absolute top-0 m-2 bg-white dark:bg-[#262626] p-1 rounded-full hover:bg-gray-100 dark:hover:bg-gray-600 text-black dark:text-gray-100">
|
||||
<XMarkIcon className="h-5 w-5" />
|
||||
<X className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -166,7 +164,7 @@ export const SidepanelForm = ({ dropedFile }: Props) => {
|
||||
}
|
||||
}}
|
||||
ref={textareaRef}
|
||||
className="px-2 py-2 w-full resize-none bg-transparent focus-within:outline-none sm:text-sm focus:ring-0 focus-visible:ring-0 ring-0 dark:ring-0 border-0 dark:text-gray-100"
|
||||
className="px-2 py-2 w-full resize-none bg-transparent focus-within:outline-none focus:ring-0 focus-visible:ring-0 ring-0 dark:ring-0 border-0 dark:text-gray-100"
|
||||
required
|
||||
rows={1}
|
||||
style={{ minHeight: "60px" }}
|
||||
@ -208,7 +206,7 @@ export const SidepanelForm = ({ dropedFile }: Props) => {
|
||||
className={`flex items-center justify-center dark:text-gray-300 ${
|
||||
chatMode === "rag" ? "hidden" : "block"
|
||||
}`}>
|
||||
<PhotoIcon className="h-5 w-5" />
|
||||
<ImageIcon className="h-5 w-5" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Dropdown.Button
|
||||
@ -238,6 +236,7 @@ export const SidepanelForm = ({ dropedFile }: Props) => {
|
||||
key: 1,
|
||||
label: (
|
||||
<Checkbox
|
||||
value={sendWhenEnter}
|
||||
onChange={(e) =>
|
||||
setSendWhenEnter(e.target.checked)
|
||||
}>
|
||||
|
@ -1,12 +1,11 @@
|
||||
@font-face {
|
||||
font-family: 'font';
|
||||
src: url('font.ttf') format('truetype');
|
||||
font-family: "font";
|
||||
src: url("font.ttf") format("truetype");
|
||||
}
|
||||
* {
|
||||
font-family: 'font' !important;
|
||||
font-family: "font" !important;
|
||||
}
|
||||
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@ -15,3 +14,44 @@
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.gradient-border {
|
||||
--borderWidth: 3px;
|
||||
position: relative;
|
||||
border-radius: var(--borderWidth);
|
||||
}
|
||||
.gradient-border:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: calc(-1 * var(--borderWidth));
|
||||
left: calc(-1 * var(--borderWidth));
|
||||
height: calc(100% + var(--borderWidth) * 2);
|
||||
width: calc(100% + var(--borderWidth) * 2);
|
||||
background: linear-gradient(
|
||||
60deg,
|
||||
#f79533,
|
||||
#f37055,
|
||||
#ef4e7b,
|
||||
#a166ab,
|
||||
#5073b8,
|
||||
#1098ad,
|
||||
#07b39b,
|
||||
#6fba82
|
||||
);
|
||||
border-radius: calc(2 * var(--borderWidth));
|
||||
z-index: -1;
|
||||
animation: animatedgradient 3s ease alternate infinite;
|
||||
background-size: 300% 300%;
|
||||
}
|
||||
|
||||
@keyframes animatedgradient {
|
||||
0% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
50% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
100% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import { useStoreMessageOption } from "~store/option"
|
||||
import { removeMessageUsingHistoryId, saveHistory, saveMessage } from "~libs/db"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import { notification } from "antd"
|
||||
import { getSystemPromptForWeb } from "~web/web"
|
||||
|
||||
export type BotResponse = {
|
||||
bot: {
|
||||
@ -93,15 +94,19 @@ export const useMessageOption = () => {
|
||||
chatMode,
|
||||
setChatMode,
|
||||
speechToTextLanguage,
|
||||
setSpeechToTextLanguage
|
||||
setSpeechToTextLanguage,
|
||||
webSearch,
|
||||
setWebSearch,
|
||||
isSearchingInternet,
|
||||
setIsSearchingInternet
|
||||
} = useStoreMessageOption()
|
||||
|
||||
|
||||
const navigate = useNavigate()
|
||||
|
||||
const abortControllerRef = React.useRef<AbortController | null>(null)
|
||||
|
||||
const clearChat = () => {
|
||||
// stopStreamingRequest()
|
||||
setMessages([])
|
||||
setHistory([])
|
||||
setHistoryId(null)
|
||||
@ -112,6 +117,224 @@ export const useMessageOption = () => {
|
||||
navigate("/")
|
||||
}
|
||||
|
||||
const searchChatMode = async (
|
||||
message: string,
|
||||
image: string,
|
||||
isRegenerate: boolean
|
||||
) => {
|
||||
const url = await getOllamaURL()
|
||||
|
||||
if (image.length > 0) {
|
||||
image = `data:image/jpeg;base64,${image.split(",")[1]}`
|
||||
}
|
||||
abortControllerRef.current = new AbortController()
|
||||
|
||||
const ollama = new ChatOllama({
|
||||
model: selectedModel,
|
||||
baseUrl: cleanUrl(url)
|
||||
})
|
||||
|
||||
let newMessage: Message[] = [
|
||||
...messages,
|
||||
{
|
||||
isBot: false,
|
||||
name: "You",
|
||||
message,
|
||||
sources: [],
|
||||
images: [image]
|
||||
},
|
||||
{
|
||||
isBot: true,
|
||||
name: selectedModel,
|
||||
message: "▋",
|
||||
sources: []
|
||||
}
|
||||
]
|
||||
|
||||
const appendingIndex = newMessage.length - 1
|
||||
if (!isRegenerate) {
|
||||
setMessages(newMessage)
|
||||
}
|
||||
|
||||
try {
|
||||
setIsSearchingInternet(true)
|
||||
const prompt = await getSystemPromptForWeb(message)
|
||||
setIsSearchingInternet(false)
|
||||
|
||||
message = message.trim().replaceAll("\n", " ")
|
||||
|
||||
let humanMessage = new HumanMessage({
|
||||
content: [
|
||||
{
|
||||
text: message,
|
||||
type: "text"
|
||||
}
|
||||
]
|
||||
})
|
||||
if (image.length > 0) {
|
||||
humanMessage = new HumanMessage({
|
||||
content: [
|
||||
{
|
||||
text: message,
|
||||
type: "text"
|
||||
},
|
||||
{
|
||||
image_url: image,
|
||||
type: "image_url"
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
const applicationChatHistory = generateHistory(history)
|
||||
|
||||
if (prompt) {
|
||||
applicationChatHistory.unshift(
|
||||
new SystemMessage({
|
||||
content: [
|
||||
{
|
||||
text: prompt,
|
||||
type: "text"
|
||||
}
|
||||
]
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const chunks = await ollama.stream(
|
||||
[...applicationChatHistory, humanMessage],
|
||||
{
|
||||
signal: abortControllerRef.current.signal
|
||||
}
|
||||
)
|
||||
let count = 0
|
||||
for await (const chunk of chunks) {
|
||||
if (count === 0) {
|
||||
setIsProcessing(true)
|
||||
newMessage[appendingIndex].message = chunk.content + "▋"
|
||||
setMessages(newMessage)
|
||||
} else {
|
||||
newMessage[appendingIndex].message =
|
||||
newMessage[appendingIndex].message.slice(0, -1) +
|
||||
chunk.content +
|
||||
"▋"
|
||||
setMessages(newMessage)
|
||||
}
|
||||
|
||||
count++
|
||||
}
|
||||
|
||||
newMessage[appendingIndex].message = newMessage[
|
||||
appendingIndex
|
||||
].message.slice(0, -1)
|
||||
|
||||
if (!isRegenerate) {
|
||||
setHistory([
|
||||
...history,
|
||||
{
|
||||
role: "user",
|
||||
content: message,
|
||||
image
|
||||
},
|
||||
{
|
||||
role: "assistant",
|
||||
content: newMessage[appendingIndex].message
|
||||
}
|
||||
])
|
||||
} else {
|
||||
setHistory([
|
||||
...history,
|
||||
{
|
||||
role: "assistant",
|
||||
content: newMessage[appendingIndex].message
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
if (historyId) {
|
||||
if (!isRegenerate) {
|
||||
await saveMessage(historyId, selectedModel, "user", message, [image])
|
||||
}
|
||||
await saveMessage(
|
||||
historyId,
|
||||
selectedModel,
|
||||
"assistant",
|
||||
newMessage[appendingIndex].message,
|
||||
[]
|
||||
)
|
||||
} else {
|
||||
const newHistoryId = await saveHistory(message)
|
||||
await saveMessage(newHistoryId.id, selectedModel, "user", message, [
|
||||
image
|
||||
])
|
||||
await saveMessage(
|
||||
newHistoryId.id,
|
||||
selectedModel,
|
||||
"assistant",
|
||||
newMessage[appendingIndex].message,
|
||||
[]
|
||||
)
|
||||
setHistoryId(newHistoryId.id)
|
||||
}
|
||||
|
||||
setIsProcessing(false)
|
||||
setStreaming(false)
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
|
||||
if (e?.name === "AbortError") {
|
||||
newMessage[appendingIndex].message = newMessage[
|
||||
appendingIndex
|
||||
].message.slice(0, -1)
|
||||
|
||||
setHistory([
|
||||
...history,
|
||||
{
|
||||
role: "user",
|
||||
content: message,
|
||||
image
|
||||
},
|
||||
{
|
||||
role: "assistant",
|
||||
content: newMessage[appendingIndex].message
|
||||
}
|
||||
])
|
||||
|
||||
if (historyId) {
|
||||
await saveMessage(historyId, selectedModel, "user", message, [image])
|
||||
await saveMessage(
|
||||
historyId,
|
||||
selectedModel,
|
||||
"assistant",
|
||||
newMessage[appendingIndex].message,
|
||||
[]
|
||||
)
|
||||
} else {
|
||||
const newHistoryId = await saveHistory(message)
|
||||
await saveMessage(newHistoryId.id, selectedModel, "user", message, [
|
||||
image
|
||||
])
|
||||
await saveMessage(
|
||||
newHistoryId.id,
|
||||
selectedModel,
|
||||
"assistant",
|
||||
newMessage[appendingIndex].message,
|
||||
[]
|
||||
)
|
||||
setHistoryId(newHistoryId.id)
|
||||
}
|
||||
} else {
|
||||
notification.error({
|
||||
message: "Error",
|
||||
description: e?.message || "Something went wrong"
|
||||
})
|
||||
}
|
||||
|
||||
setIsProcessing(false)
|
||||
setStreaming(false)
|
||||
}
|
||||
}
|
||||
|
||||
const normalChatMode = async (
|
||||
message: string,
|
||||
image: string,
|
||||
@ -338,10 +561,12 @@ export const useMessageOption = () => {
|
||||
isRegenerate?: boolean
|
||||
}) => {
|
||||
setStreaming(true)
|
||||
// const web = await localGoogleSearch(message)
|
||||
// console.log(web)
|
||||
if (webSearch) {
|
||||
await searchChatMode(message, image, isRegenerate)
|
||||
} else {
|
||||
await normalChatMode(message, image, isRegenerate)
|
||||
}
|
||||
}
|
||||
|
||||
const regenerateLastMessage = async () => {
|
||||
if (history.length > 0) {
|
||||
@ -387,6 +612,9 @@ export const useMessageOption = () => {
|
||||
setChatMode,
|
||||
speechToTextLanguage,
|
||||
setSpeechToTextLanguage,
|
||||
regenerateLastMessage
|
||||
regenerateLastMessage,
|
||||
webSearch,
|
||||
setWebSearch,
|
||||
isSearchingInternet,
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +0,0 @@
|
||||
type Props = {
|
||||
className: string
|
||||
}
|
||||
|
||||
export const ArrowPathIcon: React.FC<Props> = ({ className }) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
viewBox="0 0 24 24"
|
||||
className={className}>
|
||||
<path d="M3 12a9 9 0 019-9 9.75 9.75 0 016.74 2.74L21 8"></path>
|
||||
<path d="M21 3v5h-5M21 12a9 9 0 01-9 9 9.75 9.75 0 01-6.74-2.74L3 16"></path>
|
||||
<path d="M8 16H3v5"></path>
|
||||
</svg>
|
||||
)
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
type Props = {
|
||||
className: string
|
||||
}
|
||||
|
||||
export const BoxesIcon: React.FC<Props> = ({ className }) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
viewBox="0 0 24 24"
|
||||
className={className}>
|
||||
<path d="M2.97 12.92A2 2 0 002 14.63v3.24a2 2 0 00.97 1.71l3 1.8a2 2 0 002.06 0L12 19v-5.5l-5-3-4.03 2.42zM7 16.5l-4.74-2.85M7 16.5l5-3M7 16.5v5.17M12 13.5V19l3.97 2.38a2 2 0 002.06 0l3-1.8a2 2 0 00.97-1.71v-3.24a2 2 0 00-.97-1.71L17 10.5l-5 3zM17 16.5l-5-3M17 16.5l4.74-2.85M17 16.5v5.17"></path>
|
||||
<path d="M7.97 4.42A2 2 0 007 6.13v4.37l5 3 5-3V6.13a2 2 0 00-.97-1.71l-3-1.8a2 2 0 00-2.06 0l-3 1.8zM12 8L7.26 5.15M12 8l4.74-2.85M12 13.5V8"></path>
|
||||
</svg>
|
||||
)
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
type Props = {
|
||||
className: string
|
||||
}
|
||||
|
||||
export const BrainCircuit: React.FC<Props> = ({ className }) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
className={className}
|
||||
viewBox="0 0 24 24">
|
||||
<path d="M12 4.5a2.5 2.5 0 00-4.96-.46 2.5 2.5 0 00-1.98 3 2.5 2.5 0 00-1.32 4.24 3 3 0 00.34 5.58 2.5 2.5 0 002.96 3.08 2.5 2.5 0 004.91.05L12 20V4.5zM16 8V5c0-1.1.9-2 2-2M12 13h4"></path>
|
||||
<path d="M12 18h6a2 2 0 012 2v1M12 8h8M20.5 8a.5.5 0 11-1 0 .5.5 0 011 0zM16.5 13a.5.5 0 11-1 0 .5.5 0 011 0z"></path>
|
||||
<path d="M20.5 21a.5.5 0 11-1 0 .5.5 0 011 0zM18.5 3a.5.5 0 11-1 0 .5.5 0 011 0z"></path>
|
||||
</svg>
|
||||
)
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
type Props = {
|
||||
className: string
|
||||
}
|
||||
|
||||
export const CheckIcon: React.FC<Props> = ({ className }) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
viewBox="0 0 24 24"
|
||||
className={className}>
|
||||
<path d="M20 6L9 17l-5-5"></path>
|
||||
</svg>
|
||||
)
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
type Props = {
|
||||
className: string
|
||||
}
|
||||
|
||||
export const ChevronLeft: React.FC<Props> = ({ className }) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
viewBox="0 0 24 24"
|
||||
className={className}>
|
||||
<path d="M15 18l-6-6 6-6"></path>
|
||||
</svg>
|
||||
)
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
type Props = {
|
||||
className: string
|
||||
}
|
||||
|
||||
export const ClipboardIcon: React.FC<Props> = ({ className }) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
viewBox="0 0 24 24"
|
||||
className={className}>
|
||||
<rect width="8" height="4" x="8" y="2" rx="1" ry="1"></rect>
|
||||
<path d="M16 4h2a2 2 0 012 2v14a2 2 0 01-2 2H6a2 2 0 01-2-2V6a2 2 0 012-2h2"></path>
|
||||
</svg>
|
||||
)
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
type Props = {
|
||||
className: string
|
||||
}
|
||||
|
||||
export const CogIcon: React.FC<Props> = ({ className }) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
viewBox="0 0 24 24"
|
||||
className={className}>
|
||||
<path d="M12.22 2h-.44a2 2 0 00-2 2v.18a2 2 0 01-1 1.73l-.43.25a2 2 0 01-2 0l-.15-.08a2 2 0 00-2.73.73l-.22.38a2 2 0 00.73 2.73l.15.1a2 2 0 011 1.72v.51a2 2 0 01-1 1.74l-.15.09a2 2 0 00-.73 2.73l.22.38a2 2 0 002.73.73l.15-.08a2 2 0 012 0l.43.25a2 2 0 011 1.73V20a2 2 0 002 2h.44a2 2 0 002-2v-.18a2 2 0 011-1.73l.43-.25a2 2 0 012 0l.15.08a2 2 0 002.73-.73l.22-.39a2 2 0 00-.73-2.73l-.15-.08a2 2 0 01-1-1.74v-.5a2 2 0 011-1.74l.15-.09a2 2 0 00.73-2.73l-.22-.38a2 2 0 00-2.73-.73l-.15.08a2 2 0 01-2 0l-.43-.25a2 2 0 01-1-1.73V4a2 2 0 00-2-2z"></path>
|
||||
<circle cx="12" cy="12" r="3"></circle>
|
||||
</svg>
|
||||
)
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
type Props = {
|
||||
className: string
|
||||
}
|
||||
|
||||
export const Download: React.FC<Props> = ({ className }) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
viewBox="0 0 24 24"
|
||||
className={className}>
|
||||
<path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"></path>
|
||||
<path d="M7 10L12 15 17 10"></path>
|
||||
<path d="M12 15L12 3"></path>
|
||||
</svg>
|
||||
)
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
type Props = {
|
||||
className: string
|
||||
}
|
||||
|
||||
export const EllipsisHorizontalIcon = ({ className }: Props) => {
|
||||
return <svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
viewBox="0 0 24 24"
|
||||
className={className}
|
||||
>
|
||||
<circle cx="12" cy="12" r="1"></circle>
|
||||
<circle cx="12" cy="5" r="1"></circle>
|
||||
<circle cx="12" cy="19" r="1"></circle>
|
||||
</svg>
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
type Props = {
|
||||
className: string
|
||||
}
|
||||
|
||||
export const GithubIcon: React.FC<Props> = ({ className }) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
className={className}
|
||||
viewBox="0 0 24 24">
|
||||
<path d="M15 22v-4a4.8 4.8 0 00-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 004 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4M9 18c-4.51 2-5-2-7-2"></path>
|
||||
</svg>
|
||||
)
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
type Props = {
|
||||
className: string
|
||||
}
|
||||
|
||||
export const MicIcon: React.FC<Props> = ({ className }) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
viewBox="0 0 24 24"
|
||||
className={className}>
|
||||
<path d="M12 2a3 3 0 00-3 3v7a3 3 0 006 0V5a3 3 0 00-3-3z"></path>
|
||||
<path d="M19 10v2a7 7 0 01-14 0v-2"></path>
|
||||
<path d="M12 19L12 22"></path>
|
||||
</svg>
|
||||
)
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
type Props = {
|
||||
className: string
|
||||
}
|
||||
|
||||
export const Moon: React.FC<Props> = ({ className }) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
viewBox="0 0 24 24"
|
||||
className={className}>
|
||||
<path d="M12 3a6 6 0 009 9 9 9 0 11-9-9z"></path>
|
||||
</svg>
|
||||
)
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
type Props = {
|
||||
className: string
|
||||
}
|
||||
|
||||
export const PanelLeftIcon: React.FC<Props> = ({ className }) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
className={className}
|
||||
viewBox="0 0 24 24">
|
||||
<rect width="18" height="18" x="3" y="3" rx="2"></rect>
|
||||
<path d="M9 3v18"></path>
|
||||
</svg>
|
||||
)
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
type Props = {
|
||||
className: string
|
||||
}
|
||||
|
||||
export const PencilIcon = ({ className }: Props) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
viewBox="0 0 24 24"
|
||||
className={className}>
|
||||
<path d="M17 3a2.85 2.83 0 114 4L7.5 20.5 2 22l1.5-5.5zM15 5l4 4"></path>
|
||||
</svg>
|
||||
)
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
type Props = {
|
||||
className: string
|
||||
}
|
||||
|
||||
export const PencilSquareIcon: React.FC<Props> = ({ className }) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
viewBox="0 0 24 24"
|
||||
className={className}>
|
||||
<path d="M12 3H5a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"></path>
|
||||
<path d="M18.375 2.625a2.121 2.121 0 113 3L12 15l-4 1 1-4z"></path>
|
||||
</svg>
|
||||
)
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
type Props = {
|
||||
className: string
|
||||
}
|
||||
|
||||
export const PhotoIcon: React.FC<Props> = ({ className }) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
viewBox="0 0 24 24"
|
||||
className={className}>
|
||||
<rect width="18" height="18" x="3" y="3" rx="2" ry="2"></rect>
|
||||
<circle cx="9" cy="9" r="2"></circle>
|
||||
<path d="M21 15l-3.086-3.086a2 2 0 00-2.828 0L6 21"></path>
|
||||
</svg>
|
||||
)
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
type Props = {
|
||||
className: string
|
||||
}
|
||||
|
||||
export const RotateCcw: React.FC<Props> = ({ className }) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
viewBox="0 0 24 24"
|
||||
className={className}>
|
||||
<path d="M3 12a9 9 0 109-9 9.75 9.75 0 00-6.74 2.74L3 8"></path>
|
||||
<path d="M3 3v5h5"></path>
|
||||
</svg>
|
||||
)
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
type Props = {
|
||||
className: string
|
||||
}
|
||||
|
||||
export const SquarePen: React.FC<Props> = ({ className }) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
className={className}
|
||||
viewBox="0 0 24 24">
|
||||
<path d="M12 3H5a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"></path>
|
||||
<path d="M18.375 2.625a2.121 2.121 0 113 3L12 15l-4 1 1-4z"></path>
|
||||
</svg>
|
||||
)
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
type Props = {
|
||||
className: string
|
||||
}
|
||||
|
||||
export const StopCircleIcon: React.FC<Props> = ({ className }) => {
|
||||
return <svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
viewBox="0 0 24 24"
|
||||
className={className}
|
||||
>
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<path d="M9 9H15V15H9z"></path>
|
||||
</svg>
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
type Props = {
|
||||
className: string
|
||||
}
|
||||
|
||||
export const Sun: React.FC<Props> = ({ className }) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
viewBox="0 0 24 24"
|
||||
className={className}>
|
||||
<circle cx="12" cy="12" r="4"></circle>
|
||||
<path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41"></path>
|
||||
</svg>
|
||||
)
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
type Props = {
|
||||
className: string
|
||||
}
|
||||
|
||||
export const Trash: React.FC<Props> = ({ className }) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
viewBox="0 0 24 24"
|
||||
className={className}>
|
||||
<path d="M3 6h18M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"></path>
|
||||
<path d="M10 11L10 17"></path>
|
||||
<path d="M14 11L14 17"></path>
|
||||
</svg>
|
||||
)
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
type Props = {
|
||||
className: string
|
||||
}
|
||||
|
||||
export const XMarkIcon: React.FC<Props> = ({ className }) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
className={className}
|
||||
viewBox="0 0 24 24">
|
||||
<path d="M18 6L6 18M6 6l12 12"></path>
|
||||
</svg>
|
||||
)
|
||||
}
|
@ -3,12 +3,6 @@ import { Document } from "@langchain/core/documents"
|
||||
import { compile } from "html-to-text"
|
||||
import { chromeRunTime } from "~libs/runtime"
|
||||
|
||||
const isPDFFetch = async (url: string) => {
|
||||
await chromeRunTime(url)
|
||||
const response = await fetch(url)
|
||||
const blob = await response.blob()
|
||||
return blob.type === "application/pdf"
|
||||
}
|
||||
export interface WebLoaderParams {
|
||||
html: string
|
||||
url: string
|
||||
@ -16,8 +10,7 @@ export interface WebLoaderParams {
|
||||
|
||||
export class PageAssistHtmlLoader
|
||||
extends BaseDocumentLoader
|
||||
implements WebLoaderParams
|
||||
{
|
||||
implements WebLoaderParams {
|
||||
html: string
|
||||
url: string
|
||||
|
||||
@ -35,4 +28,20 @@ export class PageAssistHtmlLoader
|
||||
const metadata = { source: this.url }
|
||||
return [new Document({ pageContent: text, metadata })]
|
||||
}
|
||||
|
||||
async loadByURL(): Promise<Document<Record<string, any>>[]> {
|
||||
await chromeRunTime(this.url)
|
||||
const fetchHTML = await fetch(this.url)
|
||||
const html = await fetchHTML.text()
|
||||
const htmlCompiler = compile({
|
||||
wordwrap: false,
|
||||
selectors: [
|
||||
{ selector: "img", format: "skip" },
|
||||
{ selector: "script", format: "skip" }
|
||||
]
|
||||
})
|
||||
const text = htmlCompiler(html)
|
||||
const metadata = { source: this.url }
|
||||
return [new Document({ pageContent: text, metadata })]
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,13 @@ 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 in markdown:`
|
||||
|
||||
|
||||
const DEFAULT_WEBSEARCH_PROMP = `You are a helpful assistant that can answer any questions. You can use the following search results in case you want to answer questions about anything in real-time. The current date and time are {current_date_time}.
|
||||
|
||||
Search results:
|
||||
|
||||
{search_results}`
|
||||
|
||||
export const getOllamaURL = async () => {
|
||||
const ollamaURL = await storage.get("ollamaURL")
|
||||
if (!ollamaURL || ollamaURL.length === 0) {
|
||||
@ -247,3 +254,16 @@ export const saveForRag = async (
|
||||
await setDefaultEmbeddingChunkSize(chunkSize)
|
||||
await setDefaultEmbeddingChunkOverlap(overlap)
|
||||
}
|
||||
|
||||
|
||||
export const getWebSearchPrompt = async () => {
|
||||
const prompt = await storage.get("webSearchPrompt")
|
||||
if (!prompt || prompt.length === 0) {
|
||||
return DEFAULT_WEBSEARCH_PROMP
|
||||
}
|
||||
return prompt
|
||||
}
|
||||
|
||||
export const setWebSearchPrompt = async (prompt: string) => {
|
||||
await storage.set("webSearchPrompt", prompt)
|
||||
}
|
@ -47,6 +47,10 @@ type State = {
|
||||
setIsEmbedding: (isEmbedding: boolean) => void
|
||||
speechToTextLanguage: string
|
||||
setSpeechToTextLanguage: (language: string) => void
|
||||
webSearch: boolean;
|
||||
setWebSearch: (webSearch: boolean) => void;
|
||||
isSearchingInternet: boolean;
|
||||
setIsSearchingInternet: (isSearchingInternet: boolean) => void;
|
||||
}
|
||||
|
||||
export const useStoreMessageOption = create<State>((set) => ({
|
||||
@ -72,5 +76,9 @@ export const useStoreMessageOption = create<State>((set) => ({
|
||||
chatMode: "normal",
|
||||
setChatMode: (chatMode) => set({ chatMode }),
|
||||
isEmbedding: false,
|
||||
setIsEmbedding: (isEmbedding) => set({ isEmbedding })
|
||||
setIsEmbedding: (isEmbedding) => set({ isEmbedding }),
|
||||
webSearch: false,
|
||||
setWebSearch: (webSearch) => set({ webSearch }),
|
||||
isSearchingInternet: false,
|
||||
setIsSearchingInternet: (isSearchingInternet) => set({ isSearchingInternet }),
|
||||
}))
|
||||
|
15
src/store/web.tsx
Normal file
15
src/store/web.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import { create } from "zustand"
|
||||
|
||||
type State = {
|
||||
state: "searching" | "clicked" | "embeddings" | "done"
|
||||
text: string
|
||||
setText: (text: string) => void
|
||||
setState: (state: "searching" | "clicked" | "embeddings" | "done") => void
|
||||
}
|
||||
|
||||
export const useWebSearch = create<State>((set) => ({
|
||||
state: "searching",
|
||||
text: "Searching Google",
|
||||
setText: (text) => set({ text }),
|
||||
setState: (state) => set({ state })
|
||||
}))
|
@ -1,12 +1,21 @@
|
||||
import { OllamaEmbeddings } from "@langchain/community/embeddings/ollama"
|
||||
import type { Document } from "@langchain/core/documents"
|
||||
import { RecursiveCharacterTextSplitter } from "langchain/text_splitter"
|
||||
import { MemoryVectorStore } from "langchain/vectorstores/memory"
|
||||
import { cleanUrl } from "~libs/clean-url"
|
||||
import { chromeRunTime } from "~libs/runtime"
|
||||
import { PageAssistHtmlLoader } from "~loader/html"
|
||||
import { defaultEmbeddingChunkOverlap, defaultEmbeddingChunkSize, defaultEmbeddingModelForRag, getOllamaURL } from "~services/ollama"
|
||||
|
||||
const BLOCKED_HOSTS = [
|
||||
"google.com",
|
||||
"youtube.com",
|
||||
"twitter.com",
|
||||
"linkedin.com",
|
||||
]
|
||||
|
||||
const TOTAL_SEARCH_RESULTS = 2
|
||||
|
||||
export const localGoogleSearch = async (query: string) => {
|
||||
await chromeRunTime(
|
||||
cleanUrl("https://www.google.com/search?hl=en&q=" + query)
|
||||
@ -43,3 +52,55 @@ export const localGoogleSearch = async (query: string) => {
|
||||
.filter((result) => result.title && result.link)
|
||||
return filteredSearchResults
|
||||
}
|
||||
|
||||
|
||||
export const webSearch = async (query: string) => {
|
||||
const results = await localGoogleSearch(query)
|
||||
const searchResults = results.slice(0, TOTAL_SEARCH_RESULTS)
|
||||
|
||||
const docs: Document<Record<string, any>>[] = [];
|
||||
for (const result of searchResults) {
|
||||
const loader = new PageAssistHtmlLoader({
|
||||
html: "",
|
||||
url: result.link
|
||||
})
|
||||
|
||||
const documents = await loader.loadByURL()
|
||||
|
||||
documents.forEach((doc) => {
|
||||
docs.push(doc)
|
||||
})
|
||||
}
|
||||
const ollamaUrl = await getOllamaURL()
|
||||
|
||||
const embeddingModle = await defaultEmbeddingModelForRag()
|
||||
const ollamaEmbedding = new OllamaEmbeddings({
|
||||
model: embeddingModle || "",
|
||||
baseUrl: cleanUrl(ollamaUrl),
|
||||
})
|
||||
|
||||
const chunkSize = await defaultEmbeddingChunkSize();
|
||||
const chunkOverlap = await defaultEmbeddingChunkOverlap();
|
||||
const textSplitter = new RecursiveCharacterTextSplitter({
|
||||
chunkSize,
|
||||
chunkOverlap,
|
||||
})
|
||||
|
||||
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
|
||||
}
|
20
src/web/web.ts
Normal file
20
src/web/web.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { getWebSearchPrompt } from "~services/ollama"
|
||||
import { webSearch } from "./local-google"
|
||||
|
||||
export const getSystemPromptForWeb = async (query: string) => {
|
||||
try {
|
||||
const search = await webSearch(query)
|
||||
|
||||
const search_results = search.map((result, idx) => `<result source="${result.url}" id="${idx}">${result.content}</result>`).join("\n")
|
||||
|
||||
const current_date_time = new Date().toLocaleString()
|
||||
|
||||
const system = await getWebSearchPrompt();
|
||||
|
||||
const prompt = system.replace("{current_date_time}", current_date_time).replace("{search_results}", search_results)
|
||||
|
||||
return prompt
|
||||
} catch (e) {
|
||||
return ''
|
||||
}
|
||||
}
|
@ -4967,6 +4967,11 @@ lru-cache@^6.0.0:
|
||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.0.tgz#0bd445ca57363465900f4d1f9bd8db343a4d95c3"
|
||||
integrity sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==
|
||||
|
||||
lucide-react@^0.340.0:
|
||||
version "0.340.0"
|
||||
resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.340.0.tgz#67a6fac6a5e257f2036dffae0dd94d6ccb28ce8e"
|
||||
integrity sha512-mWzYhbyy2d+qKuKHh+GWElPwa+kIquTnKbmSLGWOuZy+bjfZCkYD8DQWVFlqI4mQwc4HNxcqcOvtQ7ZS2PwURg==
|
||||
|
||||
magic-string@^0.30.0:
|
||||
version "0.30.6"
|
||||
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.6.tgz#996e21b42f944e45591a68f0905d6a740a12506c"
|
||||
|
Loading…
x
Reference in New Issue
Block a user