diff --git a/.github/workflows/jan-server-web-ci.yml b/.github/workflows/jan-server-web-ci.yml
index a0ec7e29c..552399930 100644
--- a/.github/workflows/jan-server-web-ci.yml
+++ b/.github/workflows/jan-server-web-ci.yml
@@ -8,7 +8,6 @@ on:
- '.github/workflows/jan-server-web-ci.yml'
- 'core/**'
- 'web-app/**'
- - 'extensions/**'
- 'extensions-web/**'
- 'Makefile'
- 'package.json'
@@ -20,7 +19,6 @@ on:
- '.github/workflows/jan-server-web-ci.yml'
- 'core/**'
- 'web-app/**'
- - 'extensions/**'
- 'extensions-web/**'
- 'Makefile'
- 'package.json'
diff --git a/Dockerfile b/Dockerfile
index d05a1f372..b06262ec5 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -19,7 +19,6 @@ RUN yarn --version
WORKDIR /app
# Copy source code
-COPY ./extensions ./extensions
COPY ./extensions-web ./extensions-web
COPY ./web-app ./web-app
COPY ./Makefile ./Makefile
diff --git a/web-app/src/containers/LeftPanel.tsx b/web-app/src/containers/LeftPanel.tsx
index b019d318e..2f4ce9ecc 100644
--- a/web-app/src/containers/LeftPanel.tsx
+++ b/web-app/src/containers/LeftPanel.tsx
@@ -1,4 +1,4 @@
-import { Link, useNavigate, useRouterState } from '@tanstack/react-router'
+import { Link, useRouterState } from '@tanstack/react-router'
import { useLeftPanel } from '@/hooks/useLeftPanel'
import { cn } from '@/lib/utils'
import {
@@ -6,7 +6,6 @@ import {
IconDots,
IconCirclePlusFilled,
IconSettingsFilled,
- IconTrash,
IconStar,
IconMessageFilled,
IconAppsFilled,
@@ -27,17 +26,6 @@ import { useThreads } from '@/hooks/useThreads'
import { useTranslation } from '@/i18n/react-i18next-compat'
import { useMemo, useState, useEffect, useRef } from 'react'
-import {
- Dialog,
- DialogClose,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
-} from '@/components/ui/dialog'
-import { Button } from '@/components/ui/button'
import { toast } from 'sonner'
import { DownloadManagement } from '@/containers/DownloadManegement'
import { useSmallScreen } from '@/hooks/useMediaQuery'
@@ -45,6 +33,7 @@ import { useClickOutside } from '@/hooks/useClickOutside'
import { useDownloadStore } from '@/hooks/useDownloadStore'
import { PlatformFeatures } from '@/lib/platform/const'
import { PlatformFeature } from '@/lib/platform/types'
+import { DeleteAllThreadsDialog } from '@/containers/dialogs'
const mainMenus = [
{
@@ -76,7 +65,6 @@ const mainMenus = [
const LeftPanel = () => {
const { open, setLeftPanel } = useLeftPanel()
const { t } = useTranslation()
- const navigate = useNavigate()
const [searchTerm, setSearchTerm] = useState('')
const isSmallScreen = useSmallScreen()
@@ -362,80 +350,25 @@ const LeftPanel = () => {
{t('common:recents')}
-
+
+
+
+
+
+
+
+
)}
diff --git a/web-app/src/containers/ThreadContent.tsx b/web-app/src/containers/ThreadContent.tsx
index fc7306142..0316ee764 100644
--- a/web-app/src/containers/ThreadContent.tsx
+++ b/web-app/src/containers/ThreadContent.tsx
@@ -2,14 +2,7 @@
import { ThreadMessage } from '@janhq/core'
import { RenderMarkdown } from './RenderMarkdown'
import React, { Fragment, memo, useCallback, useMemo, useState } from 'react'
-import {
- IconCopy,
- IconCopyCheck,
- IconRefresh,
- IconTrash,
- IconPencil,
- IconInfoCircle,
-} from '@tabler/icons-react'
+import { IconCopy, IconCopyCheck, IconRefresh } from '@tabler/icons-react'
import { useAppState } from '@/hooks/useAppState'
import { cn } from '@/lib/utils'
import { useMessages } from '@/hooks/useMessages'
@@ -17,16 +10,10 @@ import ThinkingBlock from '@/containers/ThinkingBlock'
import ToolCallBlock from '@/containers/ToolCallBlock'
import { useChat } from '@/hooks/useChat'
import {
- Dialog,
- DialogClose,
- DialogContent,
- DialogFooter,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
-} from '@/components/ui/dialog'
-import { Button } from '@/components/ui/button'
-import { Textarea } from '@/components/ui/textarea'
+ EditMessageDialog,
+ MessageMetadataDialog,
+ DeleteMessageDialog,
+} from '@/containers/dialogs'
import {
Tooltip,
TooltipContent,
@@ -37,8 +24,6 @@ import { AvatarEmoji } from '@/containers/AvatarEmoji'
import TokenSpeedIndicator from '@/containers/TokenSpeedIndicator'
-import CodeEditor from '@uiw/react-textarea-code-editor'
-import '@uiw/react-textarea-code-editor/dist.css'
import { useTranslation } from '@/i18n/react-i18next-compat'
import { useModelProvider } from '@/hooks/useModelProvider'
@@ -76,69 +61,6 @@ const CopyButton = ({ text }: { text: string }) => {
)
}
-const EditDialog = ({
- message,
- setMessage,
-}: {
- message: string
- setMessage: (message: string) => void
-}) => {
- const { t } = useTranslation()
- const [draft, setDraft] = useState(message)
-
- const handleSave = () => {
- if (draft !== message) {
- setMessage(draft)
- }
- }
-
- return (
-
- )
-}
-
// Use memo to prevent unnecessary re-renders, but allow re-renders when props change
export const ThreadContent = memo(
(
@@ -349,32 +271,20 @@ export const ThreadContent = memo(
)}
-
c.type === 'text')?.text?.value ||
''
}
- setMessage={(message) => {
+ onSave={(message) => {
if (item.updateMessage) {
item.updateMessage(item, message)
}
}}
/>
-
-
-
-
-
- {t('delete')}
-
-
+ deleteMessage(item.thread_id, item.id)}
+ />
)}
@@ -456,68 +366,15 @@ export const ThreadContent = memo(
'hidden'
)}
>
-
+
item.updateMessage && item.updateMessage(item, message)
}
/>
-
-
-
-
-
- {t('delete')}
-
-
-
+
+
{item.isLastMessage && selectedModel && (
diff --git a/web-app/src/containers/ThreadList.tsx b/web-app/src/containers/ThreadList.tsx
index 106a7dfc6..112f41b2d 100644
--- a/web-app/src/containers/ThreadList.tsx
+++ b/web-app/src/containers/ThreadList.tsx
@@ -15,14 +15,11 @@ import { CSS } from '@dnd-kit/utilities'
import {
IconDots,
IconStarFilled,
- IconTrash,
- IconEdit,
IconStar,
} from '@tabler/icons-react'
import { useThreads } from '@/hooks/useThreads'
import { useLeftPanel } from '@/hooks/useLeftPanel'
import { cn } from '@/lib/utils'
-import { route } from '@/constants/routes'
import { useSmallScreen } from '@/hooks/useMediaQuery'
import {
@@ -33,19 +30,10 @@ import {
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import { useTranslation } from '@/i18n/react-i18next-compat'
-import { DialogClose, DialogFooter, DialogHeader } from '@/components/ui/dialog'
-import {
- Dialog,
- DialogTrigger,
- DialogContent,
- DialogTitle,
- DialogDescription,
-} from '@/components/ui/dialog'
-import { Button } from '@/components/ui/button'
import { memo, useMemo, useState } from 'react'
import { useNavigate, useMatches } from '@tanstack/react-router'
-import { toast } from 'sonner'
-import { Input } from '@/components/ui/input'
+import { RenameThreadDialog, DeleteThreadDialog } from '@/containers/dialogs'
+import { route } from '@/constants/routes'
const SortableItem = memo(({ thread }: { thread: Thread }) => {
const {
@@ -94,9 +82,6 @@ const SortableItem = memo(({ thread }: { thread: Thread }) => {
return (thread.title || '').replace(/]*>|<\/span>/g, '')
}, [thread.title])
- const [title, setTitle] = useState(
- plainTitleForRename || t('common:newThread')
- )
return (
{
{t('common:star')}
)}
-
+ setOpenDropdown(false)}
+ />
-
+ setOpenDropdown(false)}
+ />
diff --git a/web-app/src/containers/ToolCallBlock.tsx b/web-app/src/containers/ToolCallBlock.tsx
index 16c32d04d..4442702da 100644
--- a/web-app/src/containers/ToolCallBlock.tsx
+++ b/web-app/src/containers/ToolCallBlock.tsx
@@ -1,16 +1,11 @@
import { ChevronDown, ChevronUp, Loader } from 'lucide-react'
import { cn } from '@/lib/utils'
import { create } from 'zustand'
-import { RenderMarkdown } from './RenderMarkdown'
+import { RenderMarkdown } from '@/containers/RenderMarkdown'
import { useMemo, useState } from 'react'
-import {
- Dialog,
- DialogContent,
- DialogHeader,
- DialogTitle,
-} from '@/components/ui/dialog'
import { twMerge } from 'tailwind-merge'
import { useTranslation } from '@/i18n/react-i18next-compat'
+import ImageModal from '@/containers/dialogs/ImageModal'
interface Props {
result: string
@@ -108,14 +103,6 @@ const ContentItemRenderer = ({
)
}
- // if (item.type === 'text' && item.text) {
- // return (
- //
- //
- //
- // )
- // }
-
// For any other types, render as JSON
return (
@@ -243,29 +230,7 @@ const ToolCallBlock = ({ id, name, result, loading, args }: Props) => {
- {/* Image Modal */}
-
+
)
}
diff --git a/web-app/src/containers/TrustedHostsInput.tsx b/web-app/src/containers/TrustedHostsInput.tsx
index e697f69c1..ce835e0a0 100644
--- a/web-app/src/containers/TrustedHostsInput.tsx
+++ b/web-app/src/containers/TrustedHostsInput.tsx
@@ -45,7 +45,7 @@ export function TrustedHostsInput({
onBlur={handleBlur}
placeholder={t('common:enterTrustedHosts')}
className={cn(
- 'w-24 h-8 text-sm',
+ 'h-8 text-sm',
isServerRunning && 'opacity-50 pointer-events-none'
)}
/>
diff --git a/web-app/src/containers/dialogs/AddProviderDialog.tsx b/web-app/src/containers/dialogs/AddProviderDialog.tsx
new file mode 100644
index 000000000..6b12b87f5
--- /dev/null
+++ b/web-app/src/containers/dialogs/AddProviderDialog.tsx
@@ -0,0 +1,105 @@
+import { useState, useRef } from 'react'
+import { useTranslation } from '@/i18n/react-i18next-compat'
+import {
+ Dialog,
+ DialogTrigger,
+ DialogContent,
+ DialogTitle,
+ DialogClose,
+ DialogFooter,
+ DialogHeader,
+} from '@/components/ui/dialog'
+import { Button } from '@/components/ui/button'
+import { Input } from '@/components/ui/input'
+
+interface AddProviderDialogProps {
+ onCreateProvider: (name: string) => void
+ children: React.ReactNode
+}
+
+export function AddProviderDialog({
+ onCreateProvider,
+ children,
+}: AddProviderDialogProps) {
+ const { t } = useTranslation()
+ const [name, setName] = useState('')
+ const [isOpen, setIsOpen] = useState(false)
+ const createButtonRef = useRef(null)
+
+ const handleCreate = () => {
+ if (name.trim()) {
+ onCreateProvider(name.trim())
+ setName('')
+ setIsOpen(false)
+ }
+ }
+
+ const handleCancel = () => {
+ setName('')
+ setIsOpen(false)
+ }
+
+ const handleKeyDown = (e: React.KeyboardEvent) => {
+ if (e.key === 'Enter' && name.trim()) {
+ e.preventDefault()
+ handleCreate()
+ }
+ // Prevent key from being captured by parent components
+ e.stopPropagation()
+ }
+
+ const handleOpenChange = (open: boolean) => {
+ setIsOpen(open)
+ if (!open) {
+ setName('')
+ }
+ }
+
+ return (
+
+ )
+}
\ No newline at end of file
diff --git a/web-app/src/containers/dialogs/ChangeDataFolderLocation.tsx b/web-app/src/containers/dialogs/ChangeDataFolderLocation.tsx
index e30c95c01..9232c6e9c 100644
--- a/web-app/src/containers/dialogs/ChangeDataFolderLocation.tsx
+++ b/web-app/src/containers/dialogs/ChangeDataFolderLocation.tsx
@@ -33,7 +33,7 @@ export default function ChangeDataFolderLocation({
return (