diff --git a/package.json b/package.json
index 06feb95..926fa77 100644
--- a/package.json
+++ b/package.json
@@ -24,6 +24,7 @@
"axios": "^1.6.7",
"html-to-text": "^9.0.5",
"langchain": "^0.1.9",
+ "lucide-react": "^0.323.0",
"plasmo": "0.84.1",
"property-information": "^6.4.1",
"react": "18.2.0",
@@ -78,7 +79,8 @@
"scripting",
"declarativeNetRequest",
"declarativeNetRequestFeedback",
- "action"
+ "action",
+ "unlimitedStorage"
]
}
}
diff --git a/src/background.ts b/src/background.ts
index e67032f..172269e 100644
--- a/src/background.ts
+++ b/src/background.ts
@@ -13,9 +13,4 @@ chrome.runtime.onMessage.addListener(async (message) => {
chrome.action.onClicked.addListener((tab) => {
chrome.tabs.create({url: chrome.runtime.getURL("options.html")});
-});
-
-// listen to commadns
-chrome.commands.onCommand.addListener((command) => {
- console.log('Command', command)
-})
\ No newline at end of file
+});
\ No newline at end of file
diff --git a/src/components/Common/Playground/Message.tsx b/src/components/Common/Playground/Message.tsx
index d946b45..a469ae6 100644
--- a/src/components/Common/Playground/Message.tsx
+++ b/src/components/Common/Playground/Message.tsx
@@ -55,43 +55,45 @@ export const PlaygroundMessage = (props: Props) => {
{/* source if aviable */}
-
+ {props.images && (
+
+ {props.images
+ .filter((image) => image.length > 0)
+ .map((image, index) => (
+
+

+
+ ))}
+
+ )}
- {props.isBot && (
-
- {!props.hideCopy && (
-
- )}
-
- )}
+ {props.isBot && (
+
+ {!props.hideCopy && (
+
+ )}
+
+ )}
+
- {props.images && (
-
- {props.images
- .filter((image) => image.length > 0)
- .map((image, index) => (
-
-

-
- ))}
-
- )}
)
}
diff --git a/src/components/Option/Layout.tsx b/src/components/Option/Layout.tsx
new file mode 100644
index 0000000..99b9422
--- /dev/null
+++ b/src/components/Option/Layout.tsx
@@ -0,0 +1,113 @@
+import React, { Fragment, useState } from "react"
+import { Dialog, Menu, Transition } from "@headlessui/react"
+import {
+ Bars3BottomLeftIcon,
+ XMarkIcon,
+ TagIcon,
+ CircleStackIcon,
+ CogIcon,
+ ChatBubbleLeftIcon,
+ Bars3Icon,
+ Bars4Icon,
+ ArrowPathIcon
+} from "@heroicons/react/24/outline"
+import logoImage from "data-base64:~assets/icon.png"
+
+import { Link, useParams, useLocation, useNavigate } from "react-router-dom"
+import { Sidebar } from "./Sidebar"
+import { Drawer, Layout, Select } from "antd"
+import { useQuery } from "@tanstack/react-query"
+import { fetchModels } from "~services/ollama"
+import { useMessageOption } from "~hooks/useMessageOption"
+import { PanelLeftIcon, Settings2 } from "lucide-react"
+
+const navigation = [
+ { name: "Embed", href: "/bot/:id", icon: TagIcon },
+ {
+ name: "Preview",
+ href: "/bot/:id/preview",
+ icon: ChatBubbleLeftIcon
+ },
+ {
+ name: "Data Sources",
+ href: "/bot/:id/data-sources",
+ icon: CircleStackIcon
+ },
+ {
+ name: "Settings",
+ href: "/bot/:id/settings",
+ icon: CogIcon
+ }
+]
+
+//@ts-ignore -
+function classNames(...classes) {
+ return classes.filter(Boolean).join(" ")
+}
+
+export default function OptionLayout({
+ children
+}: {
+ children: React.ReactNode
+}) {
+ const [sidebarOpen, setSidebarOpen] = useState(false)
+ const params = useParams<{ id: string }>()
+ const location = useLocation()
+
+ const {
+ data: models,
+ isLoading: isModelsLoading,
+ refetch: refetchModels,
+ isFetching: isModelsFetching
+ } = useQuery({
+ queryKey: ["fetchModel"],
+ queryFn: fetchModels
+ })
+
+ const { selectedModel, setSelectedModel } = useMessageOption()
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ {children}
+
+ setSidebarOpen(false)}
+ open={sidebarOpen}
+ >
+
+
+
+ )
+}
diff --git a/src/components/Option/Playground/Playground.tsx b/src/components/Option/Playground/Playground.tsx
new file mode 100644
index 0000000..30e642b
--- /dev/null
+++ b/src/components/Option/Playground/Playground.tsx
@@ -0,0 +1,89 @@
+import React from "react"
+import { PlaygroundForm } from "./PlaygroundForm"
+import { PlaygroundChat } from "./PlaygroundChat"
+
+export const Playground = () => {
+ const drop = React.useRef(null)
+ const [dropedFile, setDropedFile] = React.useState()
+ const [dropState, setDropState] = React.useState<
+ "idle" | "dragging" | "error"
+ >("idle")
+ React.useEffect(() => {
+ if (!drop.current) {
+ return
+ }
+ const handleDragOver = (e: DragEvent) => {
+ e.preventDefault()
+ e.stopPropagation()
+ }
+
+ const handleDrop = (e: DragEvent) => {
+ e.preventDefault()
+ e.stopPropagation()
+
+ setDropState("idle")
+
+ const files = Array.from(e.dataTransfer?.files || [])
+
+ const isImage = files.every((file) => file.type.startsWith("image/"))
+
+ if (!isImage) {
+ setDropState("error")
+ return
+ }
+
+ const newFiles = Array.from(e.dataTransfer?.files || []).slice(0, 1)
+ if (newFiles.length > 0) {
+ setDropedFile(newFiles[0])
+ }
+ }
+
+ const handleDragEnter = (e: DragEvent) => {
+ e.preventDefault()
+ e.stopPropagation()
+ setDropState("dragging")
+ }
+
+ const handleDragLeave = (e: DragEvent) => {
+ e.preventDefault()
+ e.stopPropagation()
+ setDropState("idle")
+ }
+
+ drop.current.addEventListener("dragover", handleDragOver)
+ drop.current.addEventListener("drop", handleDrop)
+ drop.current.addEventListener("dragenter", handleDragEnter)
+ drop.current.addEventListener("dragleave", handleDragLeave)
+
+ return () => {
+ if (drop.current) {
+ drop.current.removeEventListener("dragover", handleDragOver)
+ drop.current.removeEventListener("drop", handleDrop)
+ drop.current.removeEventListener("dragenter", handleDragEnter)
+ drop.current.removeEventListener("dragleave", handleDragLeave)
+ }
+ }
+ }, [])
+ return (
+
+ )
+}
diff --git a/src/components/Option/Playground/PlaygroundChat.tsx b/src/components/Option/Playground/PlaygroundChat.tsx
new file mode 100644
index 0000000..4eacd62
--- /dev/null
+++ b/src/components/Option/Playground/PlaygroundChat.tsx
@@ -0,0 +1,33 @@
+import React from "react"
+import { useMessage } from "~hooks/useMessage"
+import { useMessageOption } from "~hooks/useMessageOption"
+import { PlaygroundMessage } from "./PlaygroundMessage"
+
+export const PlaygroundChat = () => {
+ const { messages } = useMessageOption()
+ const divRef = React.useRef(null)
+ React.useEffect(() => {
+ if (divRef.current) {
+ divRef.current.scrollIntoView({ behavior: "smooth" })
+ }
+ })
+ return (
+
+ {/* {messages.length === 0 &&
no message
} */}
+ {messages.length > 0 &&
}
+ {messages.map((message, index) => (
+
+ ))}
+ {messages.length > 0 && (
+
+ )}
+
+
+ )
+}
diff --git a/src/components/Option/Playground/PlaygroundForm.tsx b/src/components/Option/Playground/PlaygroundForm.tsx
new file mode 100644
index 0000000..faff95f
--- /dev/null
+++ b/src/components/Option/Playground/PlaygroundForm.tsx
@@ -0,0 +1,181 @@
+import { useForm } from "@mantine/form"
+import { useMutation } from "@tanstack/react-query"
+import React from "react"
+import useDynamicTextareaSize from "~hooks/useDynamicTextareaSize"
+import PhotoIcon from "@heroicons/react/24/outline/PhotoIcon"
+import XMarkIcon from "@heroicons/react/24/outline/XMarkIcon"
+import { toBase64 } from "~libs/to-base64"
+import { useMessageOption } from "~hooks/useMessageOption"
+import { ArrowPathIcon } from "@heroicons/react/24/outline"
+import { Tooltip } from "antd"
+
+type Props = {
+ dropedFile: File | undefined
+}
+
+export const PlaygroundForm = ({ dropedFile }: Props) => {
+ const textareaRef = React.useRef(null)
+ const inputRef = React.useRef(null)
+
+ const resetHeight = () => {
+ const textarea = textareaRef.current
+ if (textarea) {
+ textarea.style.height = "auto"
+ }
+ }
+ const form = useForm({
+ initialValues: {
+ message: "",
+ image: ""
+ }
+ })
+
+ const onInputChange = async (
+ e: React.ChangeEvent | File
+ ) => {
+ if (e instanceof File) {
+ const base64 = await toBase64(e)
+ form.setFieldValue("image", base64)
+ } else {
+ if (e.target.files) {
+ const base64 = await toBase64(e.target.files[0])
+ form.setFieldValue("image", base64)
+ }
+ }
+ }
+
+ React.useEffect(() => {
+ if (dropedFile) {
+ onInputChange(dropedFile)
+ }
+ }, [dropedFile])
+
+ useDynamicTextareaSize(textareaRef, form.values.message, 120)
+
+ const { onSubmit, selectedModel, chatMode } = useMessageOption()
+
+ const { mutateAsync: sendMessage, isPending: isSending } = useMutation({
+ mutationFn: onSubmit
+ })
+
+ return (
+
+
+
+
+

+
+
+
+
+ {form.errors.message && (
+
+ {form.errors.message}
+
+ )}
+
+
+ )
+}
diff --git a/src/components/Option/Playground/PlaygroundMessage.tsx b/src/components/Option/Playground/PlaygroundMessage.tsx
new file mode 100644
index 0000000..364e1eb
--- /dev/null
+++ b/src/components/Option/Playground/PlaygroundMessage.tsx
@@ -0,0 +1,98 @@
+import { CheckIcon, ClipboardIcon } from "@heroicons/react/24/outline"
+import Markdown from "../../Common/Markdown"
+import React from "react"
+
+type Props = {
+ message: string
+ hideCopy?: boolean
+ botAvatar?: JSX.Element
+ userAvatar?: JSX.Element
+ isBot: boolean
+ name: string
+ images?: string[]
+}
+
+export const PlaygroundMessage = (props: Props) => {
+ const [isBtnPressed, setIsBtnPressed] = React.useState(false)
+
+ React.useEffect(() => {
+ if (isBtnPressed) {
+ setTimeout(() => {
+ setIsBtnPressed(false)
+ }, 4000)
+ }
+ }, [isBtnPressed])
+
+ return (
+
+
+
+
+
+ {props.isBot ? (
+ !props.botAvatar ? (
+
+ ) : (
+ props.botAvatar
+ )
+ ) : !props.userAvatar ? (
+
+ ) : (
+ props.userAvatar
+ )}
+
+
+
+ {props.isBot && (
+
+ {props.name}
+
+ )}
+
+
+
+ {/* source if aviable */}
+ {props.images && (
+
+ {props.images
+ .filter((image) => image.length > 0)
+ .map((image, index) => (
+
+

+
+ ))}
+
+ )}
+
+
+
+ {props.isBot && (
+
+ {!props.hideCopy && (
+
+ )}
+
+ )}
+
+
+
+ )
+}
diff --git a/src/components/Option/Playground/PlaygroundNewChat.tsx b/src/components/Option/Playground/PlaygroundNewChat.tsx
new file mode 100644
index 0000000..03d6d86
--- /dev/null
+++ b/src/components/Option/Playground/PlaygroundNewChat.tsx
@@ -0,0 +1,25 @@
+import { PencilSquareIcon } from "@heroicons/react/24/outline"
+import { useMessage } from "../../../hooks/useMessage"
+
+export const PlaygroundNewChat = () => {
+ const { setHistory, setMessages, setHistoryId } = useMessage()
+
+
+ const handleClick = () => {
+ setHistoryId(null)
+ setMessages([])
+ setHistory([])
+ // navigate(`/bot/${params.id}`);
+ }
+
+ return (
+
+ )
+}
diff --git a/src/components/Option/Playground/PlaygroundSettings.tsx b/src/components/Option/Playground/PlaygroundSettings.tsx
new file mode 100644
index 0000000..8cc1354
--- /dev/null
+++ b/src/components/Option/Playground/PlaygroundSettings.tsx
@@ -0,0 +1,42 @@
+import { Modal } from "antd"
+import { useState } from "react"
+
+export const PlaygroundSettings = () => {
+ const [open, setOpen] = useState(false)
+ return (
+
+
+
+
+
+
setOpen(false)}>
+ Nothing to see here
+
+
+ )
+}
diff --git a/src/components/Option/Sidebar.tsx b/src/components/Option/Sidebar.tsx
new file mode 100644
index 0000000..9d74808
--- /dev/null
+++ b/src/components/Option/Sidebar.tsx
@@ -0,0 +1,47 @@
+import { useQuery } from "@tanstack/react-query"
+import { PageAssitDatabase } from "~libs/db"
+import { Empty, Skeleton } from "antd"
+
+type Props = {}
+
+export const Sidebar = ({}: Props) => {
+ const { data: chatHistories, status } = useQuery({
+ queryKey: ["fetchChatHistory"],
+ queryFn: async () => {
+ const db = new PageAssitDatabase()
+ const history = await db.getChatHistories()
+ return history
+ }
+ })
+
+ return (
+
+ {status === "success" && chatHistories.length === 0 && (
+
+
+
+ )}
+ {status === "pending" && (
+
+
+
+ )}
+ {status === "error" && (
+
+ Error loading history
+
+ )}
+ {status === "success" && chatHistories.length > 0 && (
+
+ {chatHistories.map((chat, index) => (
+
+ {chat.title}
+
+ ))}
+
+ )}
+
+ )
+}
diff --git a/src/components/Sidepanel/Chat/form.tsx b/src/components/Sidepanel/Chat/form.tsx
index e935589..94bf2b8 100644
--- a/src/components/Sidepanel/Chat/form.tsx
+++ b/src/components/Sidepanel/Chat/form.tsx
@@ -57,7 +57,7 @@ export const SidepanelForm = ({ dropedFile }: Props) => {
})
return (
-
+
{chatMode === "normal" && form.values.image && (
diff --git a/src/css/tailwind.css b/src/css/tailwind.css
index 40b6434..8ace08c 100644
--- a/src/css/tailwind.css
+++ b/src/css/tailwind.css
@@ -2,7 +2,7 @@
font-family: 'font';
src: url('font.ttf') format('truetype');
}
-body {
+* {
font-family: 'font' !important;
}
diff --git a/src/hooks/useMessageOption.tsx b/src/hooks/useMessageOption.tsx
new file mode 100644
index 0000000..30cd4bd
--- /dev/null
+++ b/src/hooks/useMessageOption.tsx
@@ -0,0 +1,306 @@
+import React from "react"
+import { cleanUrl } from "~libs/clean-url"
+import { getOllamaURL, systemPromptForNonRag } from "~services/ollama"
+import { type ChatHistory, type Message } from "~store/option"
+import { ChatOllama } from "@langchain/community/chat_models/ollama"
+import {
+ HumanMessage,
+ AIMessage,
+ type MessageContent,
+ SystemMessage
+} from "@langchain/core/messages"
+import { useStoreMessageOption } from "~store/option"
+import { saveHistory, saveMessage } from "~libs/db"
+
+export type BotResponse = {
+ bot: {
+ text: string
+ sourceDocuments: any[]
+ }
+ history: ChatHistory
+ history_id: string
+}
+
+const generateHistory = (
+ messages: {
+ role: "user" | "assistant" | "system"
+ content: string
+ image?: string
+ }[]
+) => {
+ let history = []
+ for (const message of messages) {
+ if (message.role === "user") {
+ let content: MessageContent = [
+ {
+ type: "text",
+ text: message.content
+ }
+ ]
+
+ if (message.image) {
+ content = [
+ {
+ type: "image_url",
+ image_url: message.image
+ },
+ {
+ type: "text",
+ text: message.content
+ }
+ ]
+ }
+ history.push(
+ new HumanMessage({
+ content: content
+ })
+ )
+ } else if (message.role === "assistant") {
+ history.push(
+ new AIMessage({
+ content: [
+ {
+ type: "text",
+ text: message.content
+ }
+ ]
+ })
+ )
+ }
+ }
+ return history
+}
+
+export const useMessageOption = () => {
+ const {
+ history,
+ messages,
+ setHistory,
+ setMessages,
+ setStreaming,
+ streaming,
+ setIsFirstMessage,
+ historyId,
+ setHistoryId,
+ isLoading,
+ setIsLoading,
+ isProcessing,
+ setIsProcessing,
+ selectedModel,
+ setSelectedModel,
+ chatMode,
+ setChatMode
+ } = useStoreMessageOption()
+
+ const abortControllerRef = React.useRef
(null)
+
+ const clearChat = () => {
+ stopStreamingRequest()
+ setMessages([])
+ setHistory([])
+ setHistoryId(null)
+ setIsFirstMessage(true)
+ setIsLoading(false)
+ setIsProcessing(false)
+ setStreaming(false)
+ }
+
+ const normalChatMode = async (message: string, image: string) => {
+ 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
+ setMessages(newMessage)
+
+ try {
+ const prompt = await systemPromptForNonRag()
+
+ 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)
+
+ 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)
+ }
+
+ setIsProcessing(false)
+ } catch (e) {
+ setIsProcessing(false)
+ setStreaming(false)
+
+ setMessages([
+ ...messages,
+ {
+ isBot: true,
+ name: selectedModel,
+ message: `Something went wrong. Check out the following logs:
+ \`\`\`
+ ${e?.message}
+ \`\`\`
+ `,
+ sources: []
+ }
+ ])
+ }
+ }
+
+ const onSubmit = async ({
+ message,
+ image
+ }: {
+ message: string
+ image: string
+ }) => {
+ await normalChatMode(message, image)
+ }
+
+ const stopStreamingRequest = () => {
+ if (abortControllerRef.current) {
+ abortControllerRef.current.abort()
+ abortControllerRef.current = null
+ }
+ }
+
+ return {
+ messages,
+ setMessages,
+ onSubmit,
+ setStreaming,
+ streaming,
+ setHistory,
+ historyId,
+ setHistoryId,
+ setIsFirstMessage,
+ isLoading,
+ setIsLoading,
+ isProcessing,
+ stopStreamingRequest,
+ clearChat,
+ selectedModel,
+ setSelectedModel,
+ chatMode,
+ setChatMode
+ }
+}
diff --git a/src/libs/class-name.tsx b/src/libs/class-name.tsx
new file mode 100644
index 0000000..4a9cf0f
--- /dev/null
+++ b/src/libs/class-name.tsx
@@ -0,0 +1,3 @@
+export const classNames = (...classes: string[]) => {
+ return classes.filter(Boolean).join(" ")
+}
diff --git a/src/libs/db.ts b/src/libs/db.ts
new file mode 100644
index 0000000..d51829a
--- /dev/null
+++ b/src/libs/db.ts
@@ -0,0 +1,111 @@
+im
+
+type HistoryInfo = {
+ id: string
+ title: string
+ createdAt: number
+}
+
+type Message = {
+ id: string
+ history_id: string
+ name: string
+ role: string
+ content: string
+ images?: string[]
+ createdAt: number
+}
+
+type MessageHistory = Message[]
+
+type ChatHistory = HistoryInfo[]
+
+export class PageAssitDatabase {
+ db: chrome.storage.StorageArea
+
+ constructor() {
+ this.db = chrome.storage.local
+ }
+
+ async getChatHistory(id: string): Promise {
+ return new Promise((resolve, reject) => {
+ this.db.get(id, (result) => {
+ resolve(result[id] || [])
+ })
+ })
+ }
+
+ async getChatHistories(): Promise {
+ return new Promise((resolve, reject) => {
+ this.db.get("chatHistories", (result) => {
+ resolve(result.chatHistories || [])
+ })
+ })
+ }
+
+ async addChatHistory(history: HistoryInfo) {
+ const chatHistories = await this.getChatHistories()
+ const newChatHistories = [history, ...chatHistories]
+ this.db.set({ chatHistories: newChatHistories })
+ }
+
+ async addMessage(message: Message) {
+ const history_id = message.history_id
+ const chatHistory = await this.getChatHistory(history_id)
+ const newChatHistory = [message, ...chatHistory]
+ this.db.set({ [history_id]: newChatHistory })
+ }
+
+ async removeChatHistory(id: string) {
+ const chatHistories = await this.getChatHistories()
+ const newChatHistories = chatHistories.filter(
+ (history) => history.id !== id
+ )
+ this.db.set({ chatHistories: newChatHistories })
+ }
+
+ async removeMessage(history_id: string, message_id: string) {
+ const chatHistory = await this.getChatHistory(history_id)
+ const newChatHistory = chatHistory.filter(
+ (message) => message.id !== message_id
+ )
+ this.db.set({ [history_id]: newChatHistory })
+ }
+
+ async clear() {
+ this.db.clear()
+ }
+}
+
+const generateID = () => {
+ return "pa_xxxx-xxxx-xxx-xxxx".replace(/[x]/g, () => {
+ const r = Math.floor(Math.random() * 16)
+ return r.toString(16)
+ })
+}
+
+
+
+export const saveHistory = async (title: string) => {
+ const id = generateID()
+ const createdAt = Date.now()
+ const history = { id, title, createdAt }
+ const db = new PageAssitDatabase()
+ await db.addChatHistory(history)
+ return history
+}
+
+export const saveMessage = async (
+ history_id: string,
+ name: string,
+ role: string,
+ content: string,
+ images: string[]
+) => {
+ const id = generateID()
+ const createdAt = Date.now()
+ const message = { id, history_id, name, role, content, images, createdAt }
+ const db = new PageAssitDatabase()
+ await db.addMessage(message)
+ return message
+}
diff --git a/src/options.tsx b/src/options.tsx
index b33f3f9..320cf78 100644
--- a/src/options.tsx
+++ b/src/options.tsx
@@ -7,6 +7,7 @@ import "./css/tailwind.css"
import { ConfigProvider, theme } from "antd"
import { StyleProvider } from "@ant-design/cssinjs"
import { useDarkMode } from "~hooks/useDarkmode"
+import { OptionRouting } from "~routes"
function IndexOption() {
const { mode } = useDarkMode()
return (
@@ -18,6 +19,7 @@ function IndexOption() {
}}>
+
diff --git a/src/routes/index.tsx b/src/routes/index.tsx
index 5a55597..8010b08 100644
--- a/src/routes/index.tsx
+++ b/src/routes/index.tsx
@@ -4,11 +4,17 @@ import { useDarkMode } from "~hooks/useDarkmode"
import { SidepanelSettings } from "./sidepanel-settings"
import { OptionIndex } from "./option-index"
-export const OptionRouting = () => (
-
- } />
-
-)
+export const OptionRouting = () => {
+ const { mode } = useDarkMode()
+
+ return (
+
+
+ } />
+
+
+ )
+}
export const SidepanelRouting = () => {
const { mode } = useDarkMode()
diff --git a/src/routes/option-index.tsx b/src/routes/option-index.tsx
index 3cb3271..a22046b 100644
--- a/src/routes/option-index.tsx
+++ b/src/routes/option-index.tsx
@@ -1,10 +1,12 @@
+import OptionLayout from "~components/Option/Layout"
+import { Playground } from "~components/Option/Playground/Playground"
import { SettingsBody } from "~components/Sidepanel/Settings/body"
import { SidepanelSettingsHeader } from "~components/Sidepanel/Settings/header"
export const OptionIndex = () => {
return (
-
- hey
-
+
+
+
)
}
diff --git a/src/store/option.tsx b/src/store/option.tsx
new file mode 100644
index 0000000..4e67ce6
--- /dev/null
+++ b/src/store/option.tsx
@@ -0,0 +1,62 @@
+import { create } from "zustand"
+
+export type Message = {
+ isBot: boolean
+ name: string
+ message: string
+ sources: any[]
+ images?: string[]
+}
+
+export type ChatHistory = {
+ role: "user" | "assistant" | "system"
+ content: string,
+ image?: string
+}[]
+
+type State = {
+ messages: Message[]
+ setMessages: (messages: Message[]) => void
+ history: ChatHistory
+ setHistory: (history: ChatHistory) => void
+ streaming: boolean
+ setStreaming: (streaming: boolean) => void
+ isFirstMessage: boolean
+ setIsFirstMessage: (isFirstMessage: boolean) => void
+ historyId: string | null
+ setHistoryId: (history_id: string | null) => void
+ isLoading: boolean
+ setIsLoading: (isLoading: boolean) => void
+ isProcessing: boolean
+ setIsProcessing: (isProcessing: boolean) => void
+ selectedModel: string | null
+ setSelectedModel: (selectedModel: string) => void
+ chatMode: "normal" | "rag"
+ setChatMode: (chatMode: "normal" | "rag") => void
+ isEmbedding: boolean
+ setIsEmbedding: (isEmbedding: boolean) => void
+}
+
+export const useStoreMessageOption = create((set) => ({
+ messages: [],
+ setMessages: (messages) => set({ messages }),
+ history: [],
+ setHistory: (history) => set({ history }),
+ streaming: true,
+ setStreaming: (streaming) => set({ streaming }),
+ isFirstMessage: true,
+ setIsFirstMessage: (isFirstMessage) => set({ isFirstMessage }),
+ historyId: null,
+ setHistoryId: (historyId) => set({ historyId }),
+ isLoading: false,
+ setIsLoading: (isLoading) => set({ isLoading }),
+ isProcessing: false,
+ setIsProcessing: (isProcessing) => set({ isProcessing }),
+ defaultSpeechToTextLanguage: "en-US",
+ selectedModel: null,
+ setSelectedModel: (selectedModel) => set({ selectedModel }),
+ chatMode: "normal",
+ setChatMode: (chatMode) => set({ chatMode }),
+ isEmbedding: false,
+ setIsEmbedding: (isEmbedding) => set({ isEmbedding }),
+}))
diff --git a/yarn.lock b/yarn.lock
index 68350d4..77bf920 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4962,6 +4962,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.323.0:
+ version "0.323.0"
+ resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.323.0.tgz#d1dfae7b212a29bbc513b9d7fd0ce5e8f93e6b13"
+ integrity sha512-rTXZFILl2Y4d1SG9p1Mdcf17AcPvPvpc/egFIzUrp7IUy60MUQo3Oi1mu8LGYXUVwuRZYsSMt3csHRW5mAovJg==
+
magic-string@^0.30.0:
version "0.30.6"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.6.tgz#996e21b42f944e45591a68f0905d6a740a12506c"