309 lines
9.8 KiB
TypeScript
309 lines
9.8 KiB
TypeScript
import { ChatbotUIContext } from "@/context/context"
|
|
import { getAssistantCollectionsByAssistantId } from "@/db/assistant-collections"
|
|
import { getAssistantFilesByAssistantId } from "@/db/assistant-files"
|
|
import { getAssistantToolsByAssistantId } from "@/db/assistant-tools"
|
|
import { getCollectionFilesByCollectionId } from "@/db/collection-files"
|
|
import useHotkey from "@/lib/hooks/use-hotkey"
|
|
import { LLM_LIST } from "@/lib/models/llm/llm-list"
|
|
import { Tables } from "@/supabase/types"
|
|
import { LLMID } from "@/types"
|
|
import { IconChevronDown, IconRobotFace } from "@tabler/icons-react"
|
|
import Image from "next/image"
|
|
import { FC, useContext, useEffect, useRef, useState } from "react"
|
|
import { useTranslation } from "react-i18next"
|
|
import { ModelIcon } from "../models/model-icon"
|
|
import { Button } from "../ui/button"
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuTrigger
|
|
} from "../ui/dropdown-menu"
|
|
import { Input } from "../ui/input"
|
|
import { QuickSettingOption } from "./quick-setting-option"
|
|
import { set } from "date-fns"
|
|
|
|
interface QuickSettingsProps {}
|
|
|
|
export const QuickSettings: FC<QuickSettingsProps> = ({}) => {
|
|
const { t } = useTranslation()
|
|
|
|
useHotkey("p", () => setIsOpen(prevState => !prevState))
|
|
|
|
const {
|
|
presets,
|
|
assistants,
|
|
selectedAssistant,
|
|
selectedPreset,
|
|
chatSettings,
|
|
setSelectedPreset,
|
|
setSelectedAssistant,
|
|
setChatSettings,
|
|
assistantImages,
|
|
setChatFiles,
|
|
setSelectedTools,
|
|
setShowFilesDisplay,
|
|
selectedWorkspace
|
|
} = useContext(ChatbotUIContext)
|
|
|
|
const inputRef = useRef<HTMLInputElement>(null)
|
|
|
|
const [isOpen, setIsOpen] = useState(false)
|
|
const [search, setSearch] = useState("")
|
|
const [loading, setLoading] = useState(false)
|
|
|
|
useEffect(() => {
|
|
if (isOpen) {
|
|
setTimeout(() => {
|
|
inputRef.current?.focus()
|
|
}, 100) // FIX: hacky
|
|
}
|
|
}, [isOpen])
|
|
|
|
const handleSelectQuickSetting = async (
|
|
item: Tables<"presets"> | Tables<"assistants"> | null,
|
|
contentType: "presets" | "assistants" | "remove"
|
|
) => {
|
|
console.log({ item, contentType })
|
|
if (contentType === "assistants" && item) {
|
|
setSelectedAssistant(item as Tables<"assistants">)
|
|
setLoading(true)
|
|
let allFiles = []
|
|
const assistantFiles = (await getAssistantFilesByAssistantId(item.id))
|
|
.files
|
|
allFiles = [...assistantFiles]
|
|
const assistantCollections = (
|
|
await getAssistantCollectionsByAssistantId(item.id)
|
|
).collections
|
|
for (const collection of assistantCollections) {
|
|
const collectionFiles = (
|
|
await getCollectionFilesByCollectionId(collection.id)
|
|
).files
|
|
allFiles = [...allFiles, ...collectionFiles]
|
|
}
|
|
const assistantTools = (await getAssistantToolsByAssistantId(item.id))
|
|
.tools
|
|
setSelectedTools(assistantTools)
|
|
setChatFiles(
|
|
allFiles.map(file => ({
|
|
id: file.id,
|
|
name: file.name,
|
|
type: file.type,
|
|
file: null
|
|
}))
|
|
)
|
|
if (allFiles.length > 0) setShowFilesDisplay(true)
|
|
setLoading(false)
|
|
setSelectedPreset(null)
|
|
} else if (contentType === "presets" && item) {
|
|
setSelectedPreset(item as Tables<"presets">)
|
|
setSelectedAssistant(null)
|
|
setChatFiles([])
|
|
setSelectedTools([])
|
|
} else {
|
|
setSelectedPreset(null)
|
|
setSelectedAssistant(null)
|
|
setChatFiles([])
|
|
setSelectedTools([])
|
|
if (selectedWorkspace) {
|
|
setChatSettings({
|
|
model: selectedWorkspace.default_model as LLMID,
|
|
prompt: selectedWorkspace.default_prompt,
|
|
temperature: selectedWorkspace.default_temperature,
|
|
contextLength: selectedWorkspace.default_context_length,
|
|
includeProfileContext: selectedWorkspace.include_profile_context,
|
|
includeWorkspaceInstructions:
|
|
selectedWorkspace.include_workspace_instructions,
|
|
embeddingsProvider: selectedWorkspace.embeddings_provider as
|
|
| "openai"
|
|
| "local"
|
|
})
|
|
}
|
|
return
|
|
}
|
|
|
|
setChatSettings({
|
|
model: item.model as LLMID,
|
|
prompt: item.prompt,
|
|
temperature: item.temperature,
|
|
contextLength: item.context_length,
|
|
includeProfileContext: item.include_profile_context,
|
|
includeWorkspaceInstructions: item.include_workspace_instructions,
|
|
embeddingsProvider: item.embeddings_provider as "openai" | "local"
|
|
})
|
|
}
|
|
|
|
const checkIfModified = () => {
|
|
if (!chatSettings) return false
|
|
|
|
if (selectedPreset) {
|
|
return (
|
|
selectedPreset.include_profile_context !==
|
|
chatSettings?.includeProfileContext ||
|
|
selectedPreset.include_workspace_instructions !==
|
|
chatSettings.includeWorkspaceInstructions ||
|
|
selectedPreset.context_length !== chatSettings.contextLength ||
|
|
selectedPreset.model !== chatSettings.model ||
|
|
selectedPreset.prompt !== chatSettings.prompt ||
|
|
selectedPreset.temperature !== chatSettings.temperature
|
|
)
|
|
} else if (selectedAssistant) {
|
|
return (
|
|
selectedAssistant.include_profile_context !==
|
|
chatSettings.includeProfileContext ||
|
|
selectedAssistant.include_workspace_instructions !==
|
|
chatSettings.includeWorkspaceInstructions ||
|
|
selectedAssistant.context_length !== chatSettings.contextLength ||
|
|
selectedAssistant.model !== chatSettings.model ||
|
|
selectedAssistant.prompt !== chatSettings.prompt ||
|
|
selectedAssistant.temperature !== chatSettings.temperature
|
|
)
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
const isModified = checkIfModified()
|
|
|
|
const items = [
|
|
...presets.map(preset => ({ ...preset, contentType: "presets" })),
|
|
...assistants.map(assistant => ({
|
|
...assistant,
|
|
contentType: "assistants"
|
|
}))
|
|
]
|
|
|
|
const selectedAssistantImage = selectedPreset
|
|
? ""
|
|
: assistantImages.find(
|
|
image => image.path === selectedAssistant?.image_path
|
|
)?.base64 || ""
|
|
|
|
const modelDetails = LLM_LIST.find(
|
|
model => model.modelId === selectedPreset?.model
|
|
)
|
|
|
|
return (
|
|
<DropdownMenu
|
|
open={isOpen}
|
|
onOpenChange={isOpen => {
|
|
setIsOpen(isOpen)
|
|
setSearch("")
|
|
}}
|
|
>
|
|
<DropdownMenuTrigger asChild className="max-w-[400px]" disabled={loading}>
|
|
<Button variant="ghost" className="flex space-x-3 text-lg">
|
|
{selectedPreset && (
|
|
<ModelIcon
|
|
provider={modelDetails?.provider || "custom"}
|
|
width={32}
|
|
height={32}
|
|
/>
|
|
)}
|
|
|
|
{selectedAssistant &&
|
|
(selectedAssistantImage ? (
|
|
<Image
|
|
className="rounded"
|
|
src={selectedAssistantImage}
|
|
alt="Assistant"
|
|
width={28}
|
|
height={28}
|
|
/>
|
|
) : (
|
|
<IconRobotFace
|
|
className="bg-primary text-secondary border-primary rounded border-DEFAULT p-1"
|
|
size={28}
|
|
/>
|
|
))}
|
|
|
|
{loading ? (
|
|
<div className="animate-pulse">{t("chat.loadingAssistant")}</div>
|
|
) : (
|
|
<>
|
|
<div className="overflow-hidden text-ellipsis">
|
|
{isModified &&
|
|
(selectedPreset || selectedAssistant) &&
|
|
"Modified "}
|
|
|
|
{selectedPreset?.name ||
|
|
selectedAssistant?.name ||
|
|
t("chat.quickSettingsLabel")}
|
|
</div>
|
|
|
|
<IconChevronDown className="ml-1" />
|
|
</>
|
|
)}
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
|
|
<DropdownMenuContent
|
|
className="min-w-[300px] max-w-[500px] space-y-4"
|
|
align="start"
|
|
>
|
|
{presets.length === 0 && assistants.length === 0 ? (
|
|
<div className="p-8 text-center">{t("chat.noItemsFound")}</div>
|
|
) : (
|
|
<>
|
|
<Input
|
|
ref={inputRef}
|
|
className="w-full"
|
|
placeholder="Search..."
|
|
value={search}
|
|
onChange={e => setSearch(e.target.value)}
|
|
onKeyDown={e => e.stopPropagation()}
|
|
/>
|
|
|
|
{!!(selectedPreset || selectedAssistant) && (
|
|
<QuickSettingOption
|
|
contentType={selectedPreset ? "presets" : "assistants"}
|
|
isSelected={true}
|
|
item={
|
|
selectedPreset ||
|
|
(selectedAssistant as
|
|
| Tables<"presets">
|
|
| Tables<"assistants">)
|
|
}
|
|
onSelect={() => {
|
|
handleSelectQuickSetting(null, "remove")
|
|
}}
|
|
image={selectedPreset ? "" : selectedAssistantImage}
|
|
/>
|
|
)}
|
|
|
|
{items
|
|
.filter(
|
|
item =>
|
|
item.name.toLowerCase().includes(search.toLowerCase()) &&
|
|
item.id !== selectedPreset?.id &&
|
|
item.id !== selectedAssistant?.id
|
|
)
|
|
.map(({ contentType, ...item }) => (
|
|
<QuickSettingOption
|
|
key={item.id}
|
|
contentType={contentType as "presets" | "assistants"}
|
|
isSelected={false}
|
|
item={item}
|
|
onSelect={() =>
|
|
handleSelectQuickSetting(
|
|
item,
|
|
contentType as "presets" | "assistants"
|
|
)
|
|
}
|
|
image={
|
|
contentType === "assistants"
|
|
? assistantImages.find(
|
|
image =>
|
|
image.path ===
|
|
(item as Tables<"assistants">).image_path
|
|
)?.base64 || ""
|
|
: ""
|
|
}
|
|
/>
|
|
))}
|
|
</>
|
|
)}
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
)
|
|
}
|