chore: remove lock

This commit is contained in:
Faisal Amir 2025-05-19 13:00:28 +07:00
parent 3fedc9231c
commit 1c81e5a95e
16 changed files with 524 additions and 26 deletions

View File

@ -25,7 +25,7 @@
"@radix-ui/react-slot": "^1.2.0",
"@radix-ui/react-switch": "^1.2.2",
"@radix-ui/react-tooltip": "^1.2.4",
"@tabler/icons-react": "^3.31.0",
"@tabler/icons-react": "^3.33.0",
"@tailwindcss/vite": "^4.1.4",
"@tanstack/react-router": "^1.116.0",
"@tanstack/react-router-devtools": "^1.116.0",

View File

@ -150,7 +150,10 @@ function DropdownMenuLabel({
<DropdownMenuPrimitive.Label
data-slot="dropdown-menu-label"
data-inset={inset}
className={cn('px-2 py-1.5 text-sm data-[inset]:pl-8', className)}
className={cn(
'px-2 py-1.5 text-xs text-main-view-fg/50 data-[inset]:pl-8',
className
)}
{...props}
/>
)

View File

@ -7,7 +7,7 @@ function Textarea({ className, ...props }: React.ComponentProps<'textarea'>) {
<textarea
data-slot="textarea"
className={cn(
'border-input placeholder:text-main-view-fg/40 border-main-view-fg/10 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 shadow-xs transition-[color,box-shadow] outline-none disabled:cursor-not-allowed disabled:opacity-50 ',
'border-input placeholder:text-main-view-fg/40 border-main-view-fg/10 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 shadow-xs transition-[color,box-shadow] outline-none disabled:cursor-not-allowed disabled:opacity-50 text-sm',
'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[1px] ring-main-view-fg/10',
className
)}

View File

@ -2,6 +2,7 @@ export const localStoregeKey = {
LeftPanel: 'left-panel',
threads: 'threads',
messages: 'messages',
assistant: 'assistant',
theme: 'theme',
modelProvider: 'model-provider',
settingAppearance: 'setting-appearance',

View File

@ -1,6 +1,7 @@
export const route = {
// home as new chat or thread
home: '/',
assistant: '/assistant',
settings: {
index: '/settings',
providers: '/settings/providers/$providerName',

View File

@ -28,13 +28,19 @@ import { listen } from '@tauri-apps/api/event'
import { SystemEvent } from '@/types/events'
import { getTools } from '@/services/mcp'
import { useChat } from '@/hooks/useChat'
import DropdownModelProvider from '@/containers/DropdownModelProvider'
type ChatInputProps = {
className?: string
showSpeedToken?: boolean
model?: ThreadModel
}
const ChatInput = ({ className, showSpeedToken = true }: ChatInputProps) => {
const ChatInput = ({
model,
className,
showSpeedToken = true,
}: ChatInputProps) => {
const textareaRef = useRef<HTMLTextAreaElement>(null)
const [isFocused, setIsFocused] = useState(false)
const [rows, setRows] = useState(1)
@ -160,6 +166,8 @@ const ChatInput = ({ className, showSpeedToken = true }: ChatInputProps) => {
streamingContent && 'opacity-50 pointer-events-none'
)}
>
<DropdownModelProvider model={model} />
{/* File attachment - always available */}
<div className="h-6 p-1 flex items-center justify-center rounded-sm hover:bg-main-view-fg/10 transition-all duration-200 ease-in-out gap-1">
<IconPaperclip size={18} className="text-main-view-fg/50" />

View File

@ -0,0 +1,67 @@
import { useState } from 'react'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import { useAssistant } from '@/hooks/useAssistant'
import AddEditAssistant from './dialogs/AddEditAssistant'
import { IconCirclePlus } from '@tabler/icons-react'
import type { Assistant } from '@/hooks/useAssistant'
const DropdownAssistant = () => {
const { assistants, addAssistant, updateAssistant } = useAssistant()
const [open, setOpen] = useState(false)
const [editingKey, setEditingKey] = useState<string | null>(null)
const handleSave = (assistant: Assistant) => {
addAssistant(assistant)
setOpen(false)
}
return (
<>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button className="rounded font-medium cursor-pointer flex items-center gap-1.5 relative z-20">
<span className="text-main-view-fg/80">
{assistants[0]?.name || 'Jan'}
</span>
</button>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-44 max-h-[320px]"
side="bottom"
sideOffset={10}
align="start"
>
{assistants.map((assistant) => (
<DropdownMenuItem key={assistant.id}>
<span className="truncate text-main-view-fg/70">
{assistant.name}
</span>
</DropdownMenuItem>
))}
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => setOpen(true)}>
<IconCirclePlus />
<span className="truncate text-main-view-fg/70">
Create Assistant
</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<AddEditAssistant
open={open}
onOpenChange={setOpen}
editingKey={editingKey}
onSave={handleSave}
/>
</>
)
}
export default DropdownAssistant

View File

@ -16,10 +16,7 @@ import { route } from '@/constants/routes'
import { useThreads } from '@/hooks/useThreads'
type DropdownModelProviderProps = {
model?: {
id: string
provider: string
}
model?: ThreadModel
}
const DropdownModelProvider = ({ model }: DropdownModelProviderProps) => {
@ -54,13 +51,23 @@ const DropdownModelProvider = ({ model }: DropdownModelProviderProps) => {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button className="bg-main-view-fg/5 hover:bg-main-view-fg/8 px-2 py-1 rounded font-medium cursor-pointer flex items-center gap-1.5 relative z-20">
<button
title={displayModel}
className="bg-main-view-fg/5 hover:bg-main-view-fg/8 px-2 py-1 rounded font-medium cursor-pointer flex items-center gap-1.5 relative z-20 max-w-40"
>
<img
src={getProviderLogo(selectedProvider as string)}
alt={`${selectedProvider} - Logo`}
className="size-4"
/>
<span className="text-main-view-fg/80">{displayModel}</span>
<span
className={cn(
'text-main-view-fg/80 truncate leading-normal',
!selectedModel?.id && 'text-main-view-fg/50'
)}
>
{displayModel}
</span>
</button>
</DropdownMenuTrigger>
<DropdownMenuContent
@ -115,6 +122,7 @@ const DropdownModelProvider = ({ model }: DropdownModelProviderProps) => {
!provider.api_key?.length &&
'hidden'
)}
title={model.id}
key={`model-${modelIndex}`}
onClick={() => {
selectModelProvider(provider.provider, model.id)

View File

@ -18,7 +18,7 @@ const HeaderPage = ({ children }: HeaderPageProps) => {
platformName === 'macos' && !open ? 'pl-18' : 'pl-4'
)}
>
<div className="flex items-center gap-2">
<div className="flex items-center w-full gap-2">
{!open && (
<button
className="size-5 cursor-pointer flex items-center justify-center rounded hover:bg-main-view-fg/10 transition-all duration-200 ease-in-out data-[state=open]:bg-main-view-fg/10"

View File

@ -12,6 +12,7 @@ import {
IconAppsFilled,
IconX,
IconSearch,
IconClipboardSmileFilled,
} from '@tabler/icons-react'
import { route } from '@/constants/routes'
import ThreadList from './ThreadList'
@ -46,6 +47,11 @@ const mainMenus = [
icon: IconCirclePlusFilled,
route: route.home,
},
{
title: 'Assistant',
icon: IconClipboardSmileFilled,
route: route.assistant,
},
{
title: 'common.hub',
icon: IconAppsFilled,
@ -87,8 +93,6 @@ const LeftPanel = () => {
const [openDropdown, setOpenDropdown] = useState(false)
console.log(threads)
return (
<aside
className={cn(

View File

@ -0,0 +1,217 @@
import { useState, useEffect } from 'react'
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter,
} from '@/components/ui/dialog'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { IconPlus, IconTrash } from '@tabler/icons-react'
import { Assistant } from '@/hooks/useAssistant'
import { Textarea } from '@/components/ui/textarea'
interface AddEditAssistantProps {
open: boolean
onOpenChange: (open: boolean) => void
editingKey: string | null
initialData?: Assistant
onSave: (assistant: Assistant) => void
}
export default function AddEditAssistant({
open,
onOpenChange,
editingKey,
initialData,
onSave,
}: AddEditAssistantProps) {
const [avatar, setAvatar] = useState<string | undefined>(initialData?.avatar)
const [name, setName] = useState(initialData?.name || '')
const [description, setDescription] = useState<string | undefined>(
initialData?.description
)
const [instructions, setInstructions] = useState(
initialData?.instructions || ''
)
const [paramsKeys, setParamsKeys] = useState<string[]>([''])
const [paramsValues, setParamsValues] = useState<string[]>([''])
// Reset form when modal opens/closes or editing key changes
useEffect(() => {
if (open && editingKey && initialData) {
setAvatar(initialData.avatar)
setName(initialData.name)
setDescription(initialData.description)
setInstructions(initialData.instructions)
// Convert parameters object to arrays of keys and values
const keys = Object.keys(initialData.parameters || {})
const values = Object.values(initialData.parameters || {})
setParamsKeys(keys.length > 0 ? keys : [''])
setParamsValues(values.length > 0 ? values : [''])
} else if (open) {
// Add mode - reset form
resetForm()
}
}, [open, editingKey, initialData])
const resetForm = () => {
setAvatar(undefined)
setName('')
setDescription(undefined)
setInstructions('')
setParamsKeys([''])
setParamsValues([''])
}
const handleParameterChange = (
index: number,
value: string,
isKey: boolean
) => {
if (isKey) {
const newKeys = [...paramsKeys]
newKeys[index] = value
setParamsKeys(newKeys)
} else {
const newValues = [...paramsValues]
newValues[index] = value
setParamsValues(newValues)
}
}
const handleAddParameter = () => {
setParamsKeys([...paramsKeys, ''])
setParamsValues([...paramsValues, ''])
}
const handleRemoveParameter = (index: number) => {
const newKeys = [...paramsKeys]
const newValues = [...paramsValues]
newKeys.splice(index, 1)
newValues.splice(index, 1)
setParamsKeys(newKeys.length > 0 ? newKeys : [''])
setParamsValues(newValues.length > 0 ? newValues : [''])
}
const handleSave = () => {
// Convert parameters arrays to object
const parameters: Record<string, string> = {}
paramsKeys.forEach((key, index) => {
parameters[key] = paramsValues[index] || ''
})
const assistant: Assistant = {
avatar,
id: initialData?.id || Math.random().toString(36).substring(7),
name,
created_at: initialData?.created_at || Date.now(),
description,
instructions,
parameters: parameters || {},
}
onSave(assistant)
onOpenChange(false)
resetForm()
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent>
<DialogHeader>
<DialogTitle>
{editingKey ? 'Edit Assistant' : 'Add Assistant'}
</DialogTitle>
</DialogHeader>
<div className="space-y-4">
<div className="space-y-2">
<label className="text-sm mb-2 inline-block">Name</label>
<Input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Enter name"
autoFocus
/>
</div>
<div className="space-y-2">
<label className="text-sm mb-2 inline-block">
Avatar (optional)
</label>
<Input
value={avatar || ''}
onChange={(e) => setAvatar(e.target.value)}
placeholder="Enter avatar URL"
/>
</div>
<div className="space-y-2">
<label className="text-sm mb-2 inline-block">
Description (optional)
</label>
<Input
value={description || ''}
onChange={(e) => setDescription(e.target.value)}
placeholder="Enter description"
/>
</div>
<div className="space-y-2">
<label className="text-sm mb-2 inline-block">Instructions</label>
<Textarea
value={instructions}
onChange={(e) => setInstructions(e.target.value)}
placeholder="Enter instructions"
/>
</div>
<div className="space-y-2">
<div className="flex items-center justify-between">
<label className="text-sm">Parameters</label>
<div
className="size-6 cursor-pointer flex items-center justify-center rounded hover:bg-main-view-fg/10 transition-all duration-200 ease-in-out"
onClick={handleAddParameter}
>
<IconPlus size={18} className="text-main-view-fg/60" />
</div>
</div>
{paramsKeys.map((key, index) => (
<div key={index} className="flex items-center gap-2">
<Input
value={key}
onChange={(e) =>
handleParameterChange(index, e.target.value, true)
}
placeholder="Key"
className="flex-1"
/>
<Input
value={paramsValues[index] || ''}
onChange={(e) =>
handleParameterChange(index, e.target.value, false)
}
placeholder="Value"
className="flex-1"
/>
<div
className="size-6 cursor-pointer flex items-center justify-center rounded hover:bg-main-view-fg/10 transition-all duration-200 ease-in-out"
onClick={() => handleRemoveParameter(index)}
>
<IconTrash size={18} className="text-destructive" />
</div>
</div>
))}
</div>
</div>
<DialogFooter>
<Button onClick={handleSave}>Save</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}

View File

@ -0,0 +1,51 @@
import { localStoregeKey } from '@/constants/localStorage'
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
export type Assistant = {
avatar?: string
id: string
name: string
created_at: number
description?: string
instructions: string
parameters: Record<string, string>
}
interface AssistantState {
assistants: Assistant[]
addAssistant: (assistant: Assistant) => void
updateAssistant: (assistant: Assistant) => void
deleteAssistant: (id: string) => void
}
const defaultAssistant: Assistant = {
avatar: '',
id: 'jan',
name: 'Jan',
created_at: 1747029866.542,
description: 'A default assistant that can use all downloaded models.',
instructions: '',
parameters: {},
}
export const useAssistant = create<AssistantState>()(
persist(
(set, get) => ({
assistants: [defaultAssistant],
addAssistant: (assistant) =>
set({ assistants: [...get().assistants, assistant] }),
updateAssistant: (assistant) =>
set({
assistants: get().assistants.map((a) =>
a.id === assistant.id ? assistant : a
),
}),
deleteAssistant: (id) =>
set({ assistants: get().assistants.filter((a) => a.id !== id) }),
}),
{
name: localStoregeKey.assistant,
}
)
)

View File

@ -12,6 +12,7 @@
import { Route as rootRoute } from './routes/__root'
import { Route as HubImport } from './routes/hub'
import { Route as AssistantImport } from './routes/assistant'
import { Route as IndexImport } from './routes/index'
import { Route as ThreadsThreadIdImport } from './routes/threads/$threadId'
import { Route as SettingsShortcutsImport } from './routes/settings/shortcuts'
@ -33,6 +34,12 @@ const HubRoute = HubImport.update({
getParentRoute: () => rootRoute,
} as any)
const AssistantRoute = AssistantImport.update({
id: '/assistant',
path: '/assistant',
getParentRoute: () => rootRoute,
} as any)
const IndexRoute = IndexImport.update({
id: '/',
path: '/',
@ -117,6 +124,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof IndexImport
parentRoute: typeof rootRoute
}
'/assistant': {
id: '/assistant'
path: '/assistant'
fullPath: '/assistant'
preLoaderRoute: typeof AssistantImport
parentRoute: typeof rootRoute
}
'/hub': {
id: '/hub'
path: '/hub'
@ -208,6 +222,7 @@ declare module '@tanstack/react-router' {
export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/assistant': typeof AssistantRoute
'/hub': typeof HubRoute
'/local-api-server/logs': typeof LocalApiServerLogsRoute
'/settings/appearance': typeof SettingsAppearanceRoute
@ -224,6 +239,7 @@ export interface FileRoutesByFullPath {
export interface FileRoutesByTo {
'/': typeof IndexRoute
'/assistant': typeof AssistantRoute
'/hub': typeof HubRoute
'/local-api-server/logs': typeof LocalApiServerLogsRoute
'/settings/appearance': typeof SettingsAppearanceRoute
@ -241,6 +257,7 @@ export interface FileRoutesByTo {
export interface FileRoutesById {
__root__: typeof rootRoute
'/': typeof IndexRoute
'/assistant': typeof AssistantRoute
'/hub': typeof HubRoute
'/local-api-server/logs': typeof LocalApiServerLogsRoute
'/settings/appearance': typeof SettingsAppearanceRoute
@ -259,6 +276,7 @@ export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths:
| '/'
| '/assistant'
| '/hub'
| '/local-api-server/logs'
| '/settings/appearance'
@ -274,6 +292,7 @@ export interface FileRouteTypes {
fileRoutesByTo: FileRoutesByTo
to:
| '/'
| '/assistant'
| '/hub'
| '/local-api-server/logs'
| '/settings/appearance'
@ -289,6 +308,7 @@ export interface FileRouteTypes {
id:
| '__root__'
| '/'
| '/assistant'
| '/hub'
| '/local-api-server/logs'
| '/settings/appearance'
@ -306,6 +326,7 @@ export interface FileRouteTypes {
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
AssistantRoute: typeof AssistantRoute
HubRoute: typeof HubRoute
LocalApiServerLogsRoute: typeof LocalApiServerLogsRoute
SettingsAppearanceRoute: typeof SettingsAppearanceRoute
@ -322,6 +343,7 @@ export interface RootRouteChildren {
const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
AssistantRoute: AssistantRoute,
HubRoute: HubRoute,
LocalApiServerLogsRoute: LocalApiServerLogsRoute,
SettingsAppearanceRoute: SettingsAppearanceRoute,
@ -347,6 +369,7 @@ export const routeTree = rootRoute
"filePath": "__root.tsx",
"children": [
"/",
"/assistant",
"/hub",
"/local-api-server/logs",
"/settings/appearance",
@ -364,6 +387,9 @@ export const routeTree = rootRoute
"/": {
"filePath": "index.tsx"
},
"/assistant": {
"filePath": "assistant.tsx"
},
"/hub": {
"filePath": "hub.tsx"
},

View File

@ -0,0 +1,109 @@
import { createFileRoute } from '@tanstack/react-router'
import { route } from '@/constants/routes'
import { useState } from 'react'
import { useAssistant } from '@/hooks/useAssistant'
import type { Assistant } from '@/hooks/useAssistant'
import HeaderPage from '@/containers/HeaderPage'
import {
IconCirclePlus,
IconCodeCircle,
IconPencil,
IconTrash,
} from '@tabler/icons-react'
import AddEditAssistant from '@/containers/dialogs/AddEditAssistant'
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const Route = createFileRoute(route.assistant as any)({
component: Assistant,
})
function Assistant() {
const { assistants, addAssistant, updateAssistant } = useAssistant()
const [open, setOpen] = useState(false)
const [editingKey, setEditingKey] = useState<string | null>(null)
const handleSave = (assistant: Assistant) => {
if (editingKey) {
updateAssistant(assistant)
} else {
addAssistant(assistant)
}
setOpen(false)
setEditingKey(null)
}
return (
<div className="flex h-full flex-col flex-justify-center">
<HeaderPage>
<span>Assistant</span>
</HeaderPage>
<div className="h-full p-4 overflow-y-auto">
<div className="grid grid-cols-3 gap-4">
{assistants.map((assistant) => (
<div
className="bg-main-view-fg/3 p-3 rounded-md"
key={assistant.id}
>
<div className="flex items-center justify-between gap-2">
<h3 className="text-base font-medium text-main-view-fg/80">
{assistant.name}
</h3>
<div className="flex items-center gap-0.5">
<div
className="size-6 cursor-pointer flex items-center justify-center rounded hover:bg-main-view-fg/10 transition-all duration-200 ease-in-out"
title="Edit Assistant in JSON"
>
<IconCodeCircle
size={18}
className="text-main-view-fg/50"
/>
</div>
<div
className="size-6 cursor-pointer flex items-center justify-center rounded hover:bg-main-view-fg/10 transition-all duration-200 ease-in-out"
title="Edit Assistant"
onClick={() => {
setEditingKey(assistant.id)
setOpen(true)
}}
>
<IconPencil size={18} className="text-main-view-fg/50" />
</div>
<div
className="size-6 cursor-pointer flex items-center justify-center rounded hover:bg-main-view-fg/10 transition-all duration-200 ease-in-out"
title="Delete Assistant"
>
<IconTrash size={18} className="text-main-view-fg/50" />
</div>
</div>
</div>
<p className="text-main-view-fg/50 mt-1">
{assistant.description}
</p>
</div>
))}
<div
className="bg-main-view p-3 rounded-md border border-dashed border-main-view-fg/10 flex items-center justify-center cursor-pointer hover:bg-main-view-fg/1 transition-all duration-200 ease-in-out"
key="new-assistant"
onClick={() => {
setEditingKey(null)
setOpen(true)
}}
>
<IconCirclePlus className="text-main-view-fg/50" />
</div>
</div>
<AddEditAssistant
open={open}
onOpenChange={setOpen}
editingKey={editingKey}
initialData={
editingKey ? assistants.find((a) => a.id === editingKey) : undefined
}
onSave={handleSave}
/>
</div>
</div>
)
}

View File

@ -3,7 +3,7 @@ import { createFileRoute, useSearch } from '@tanstack/react-router'
import ChatInput from '@/containers/ChatInput'
import HeaderPage from '@/containers/HeaderPage'
import { useTranslation } from 'react-i18next'
import DropdownModelProvider from '@/containers/DropdownModelProvider'
import { useModelProvider } from '@/hooks/useModelProvider'
import SetupScreen from '@/containers/SetupScreen'
import { route } from '@/constants/routes'
@ -14,6 +14,7 @@ type SearchParams = {
provider: string
}
}
import DropdownAssistant from '@/containers/DropdownAssistant'
export const Route = createFileRoute(route.home as any)({
component: Index,
@ -43,7 +44,7 @@ function Index() {
return (
<div className="flex h-full flex-col flex-justify-center">
<HeaderPage>
<DropdownModelProvider model={selectedModel} />
<DropdownAssistant />
</HeaderPage>
<div className="h-full px-8 overflow-y-auto flex flex-col gap-2 justify-center">
<div className="w-4/6 mx-auto">
@ -56,7 +57,7 @@ function Index() {
</p>
</div>
<div className="flex-1 shrink-0">
<ChatInput showSpeedToken={false} />
<ChatInput showSpeedToken={false} model={selectedModel} />
</div>
</div>
</div>

View File

@ -1,21 +1,21 @@
import { useEffect, useMemo, useRef, useState } from 'react'
import { createFileRoute, useParams } from '@tanstack/react-router'
import { UIEventHandler } from 'react'
import HeaderPage from '@/containers/HeaderPage'
import { useThreads } from '@/hooks/useThreads'
import ChatInput from '@/containers/ChatInput'
import DropdownModelProvider from '@/containers/DropdownModelProvider'
import { useShallow } from 'zustand/react/shallow'
import { ThreadContent } from '@/containers/ThreadContent'
import { StreamingContent } from '@/containers/StreamingContent'
import debounce from 'lodash.debounce'
import { cn } from '@/lib/utils'
import { ArrowDown } from 'lucide-react'
import HeaderPage from '@/containers/HeaderPage'
import { useThreads } from '@/hooks/useThreads'
import ChatInput from '@/containers/ChatInput'
import { useShallow } from 'zustand/react/shallow'
import { ThreadContent } from '@/containers/ThreadContent'
import { StreamingContent } from '@/containers/StreamingContent'
import { ModelLoader } from '@/containers/loaders/ModelLoader'
import { useMessages } from '@/hooks/useMessages'
import { fetchMessages } from '@/services/messages'
import { useAppState } from '@/hooks/useAppState'
import DropdownAssistant from '@/containers/DropdownAssistant'
// as route.threadsDetail
export const Route = createFileRoute('/threads/$threadId')({
@ -159,7 +159,9 @@ function ThreadDetail() {
return (
<div className="flex flex-col h-full">
<HeaderPage>
<DropdownModelProvider model={threadModel} />
<div className="flex items-center justify-between w-full pr-2">
<DropdownAssistant />
</div>
{thread?.model?.provider === 'llama.cpp' && loadingModel && (
<ModelLoader />
)}
@ -213,7 +215,7 @@ function ThreadDetail() {
<ArrowDown size={12} />
</div>
</div>
<ChatInput />
<ChatInput model={threadModel} />
</div>
</div>
</div>