Skip to content

Commit cd85ae0

Browse files
authored
Merge pull request #6381 from menloresearch/enhancement/dialog-modal-responsive
enhancement: responsive dialog modals
2 parents fcd285c + 9b13b14 commit cd85ae0

22 files changed

+1049
-597
lines changed

web-app/src/containers/LeftPanel.tsx

Lines changed: 21 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
import { Link, useNavigate, useRouterState } from '@tanstack/react-router'
1+
import { Link, useRouterState } from '@tanstack/react-router'
22
import { useLeftPanel } from '@/hooks/useLeftPanel'
33
import { cn } from '@/lib/utils'
44
import {
55
IconLayoutSidebar,
66
IconDots,
77
IconCirclePlusFilled,
88
IconSettingsFilled,
9-
IconTrash,
109
IconStar,
1110
IconMessageFilled,
1211
IconAppsFilled,
@@ -27,24 +26,14 @@ import { useThreads } from '@/hooks/useThreads'
2726

2827
import { useTranslation } from '@/i18n/react-i18next-compat'
2928
import { useMemo, useState, useEffect, useRef } from 'react'
30-
import {
31-
Dialog,
32-
DialogClose,
33-
DialogContent,
34-
DialogDescription,
35-
DialogFooter,
36-
DialogHeader,
37-
DialogTitle,
38-
DialogTrigger,
39-
} from '@/components/ui/dialog'
40-
import { Button } from '@/components/ui/button'
4129
import { toast } from 'sonner'
4230
import { DownloadManagement } from '@/containers/DownloadManegement'
4331
import { useSmallScreen } from '@/hooks/useMediaQuery'
4432
import { useClickOutside } from '@/hooks/useClickOutside'
4533
import { useDownloadStore } from '@/hooks/useDownloadStore'
4634
import { PlatformFeatures } from '@/lib/platform/const'
4735
import { PlatformFeature } from '@/lib/platform/types'
36+
import { DeleteAllThreadsDialog } from '@/containers/dialogs'
4837

4938
const mainMenus = [
5039
{
@@ -76,7 +65,6 @@ const mainMenus = [
7665
const LeftPanel = () => {
7766
const { open, setLeftPanel } = useLeftPanel()
7867
const { t } = useTranslation()
79-
const navigate = useNavigate()
8068
const [searchTerm, setSearchTerm] = useState('')
8169

8270
const isSmallScreen = useSmallScreen()
@@ -362,80 +350,25 @@ const LeftPanel = () => {
362350
{t('common:recents')}
363351
</span>
364352
<div className="relative">
365-
<Dialog>
366-
<DropdownMenu>
367-
<DropdownMenuTrigger asChild>
368-
<button
369-
className="size-6 flex cursor-pointer items-center justify-center rounded hover:bg-left-panel-fg/10 transition-all duration-200 ease-in-out data-[state=open]:bg-left-panel-fg/10"
370-
onClick={(e) => {
371-
e.preventDefault()
372-
e.stopPropagation()
373-
}}
374-
>
375-
<IconDots
376-
size={18}
377-
className="text-left-panel-fg/60"
378-
/>
379-
</button>
380-
</DropdownMenuTrigger>
381-
<DropdownMenuContent side="bottom" align="end">
382-
<DialogTrigger asChild>
383-
<DropdownMenuItem
384-
onSelect={(e) => e.preventDefault()}
385-
>
386-
<IconTrash size={16} />
387-
<span>{t('common:deleteAll')}</span>
388-
</DropdownMenuItem>
389-
</DialogTrigger>
390-
<DialogContent>
391-
<DialogHeader>
392-
<DialogTitle>
393-
{t('common:dialogs.deleteAllThreads.title')}
394-
</DialogTitle>
395-
<DialogDescription>
396-
{t(
397-
'common:dialogs.deleteAllThreads.description'
398-
)}
399-
</DialogDescription>
400-
<DialogFooter className="mt-2">
401-
<DialogClose asChild>
402-
<Button
403-
variant="link"
404-
size="sm"
405-
className="hover:no-underline"
406-
>
407-
{t('common:cancel')}
408-
</Button>
409-
</DialogClose>
410-
<Button
411-
variant="destructive"
412-
size="sm"
413-
onClick={() => {
414-
deleteAllThreads()
415-
toast.success(
416-
t(
417-
'common:toast.deleteAllThreads.title'
418-
),
419-
{
420-
id: 'delete-all-thread',
421-
description: t(
422-
'common:toast.deleteAllThreads.description'
423-
),
424-
}
425-
)
426-
setTimeout(() => {
427-
navigate({ to: route.home })
428-
}, 0)
429-
}}
430-
>
431-
{t('common:deleteAll')}
432-
</Button>
433-
</DialogFooter>
434-
</DialogHeader>
435-
</DialogContent>
436-
</DropdownMenuContent>
437-
</DropdownMenu>
438-
</Dialog>
353+
<DropdownMenu>
354+
<DropdownMenuTrigger asChild>
355+
<button
356+
className="size-6 flex cursor-pointer items-center justify-center rounded hover:bg-left-panel-fg/10 transition-all duration-200 ease-in-out data-[state=open]:bg-left-panel-fg/10"
357+
onClick={(e) => {
358+
e.preventDefault()
359+
e.stopPropagation()
360+
}}
361+
>
362+
<IconDots
363+
size={18}
364+
className="text-left-panel-fg/60"
365+
/>
366+
</button>
367+
</DropdownMenuTrigger>
368+
<DropdownMenuContent side="bottom" align="end">
369+
<DeleteAllThreadsDialog onDeleteAll={deleteAllThreads} />
370+
</DropdownMenuContent>
371+
</DropdownMenu>
439372
</div>
440373
</div>
441374
)}

web-app/src/containers/ThreadContent.tsx

Lines changed: 15 additions & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,18 @@
22
import { ThreadMessage } from '@janhq/core'
33
import { RenderMarkdown } from './RenderMarkdown'
44
import React, { Fragment, memo, useCallback, useMemo, useState } from 'react'
5-
import {
6-
IconCopy,
7-
IconCopyCheck,
8-
IconRefresh,
9-
IconTrash,
10-
IconPencil,
11-
IconInfoCircle,
12-
} from '@tabler/icons-react'
5+
import { IconCopy, IconCopyCheck, IconRefresh } from '@tabler/icons-react'
136
import { useAppState } from '@/hooks/useAppState'
147
import { cn } from '@/lib/utils'
158
import { useMessages } from '@/hooks/useMessages'
169
import ThinkingBlock from '@/containers/ThinkingBlock'
1710
import ToolCallBlock from '@/containers/ToolCallBlock'
1811
import { useChat } from '@/hooks/useChat'
1912
import {
20-
Dialog,
21-
DialogClose,
22-
DialogContent,
23-
DialogFooter,
24-
DialogHeader,
25-
DialogTitle,
26-
DialogTrigger,
27-
} from '@/components/ui/dialog'
28-
import { Button } from '@/components/ui/button'
29-
import { Textarea } from '@/components/ui/textarea'
13+
EditMessageDialog,
14+
MessageMetadataDialog,
15+
DeleteMessageDialog,
16+
} from '@/containers/dialogs'
3017
import {
3118
Tooltip,
3219
TooltipContent,
@@ -37,8 +24,6 @@ import { AvatarEmoji } from '@/containers/AvatarEmoji'
3724

3825
import TokenSpeedIndicator from '@/containers/TokenSpeedIndicator'
3926

40-
import CodeEditor from '@uiw/react-textarea-code-editor'
41-
import '@uiw/react-textarea-code-editor/dist.css'
4227
import { useTranslation } from '@/i18n/react-i18next-compat'
4328
import { useModelProvider } from '@/hooks/useModelProvider'
4429

@@ -76,69 +61,6 @@ const CopyButton = ({ text }: { text: string }) => {
7661
)
7762
}
7863

79-
const EditDialog = ({
80-
message,
81-
setMessage,
82-
}: {
83-
message: string
84-
setMessage: (message: string) => void
85-
}) => {
86-
const { t } = useTranslation()
87-
const [draft, setDraft] = useState(message)
88-
89-
const handleSave = () => {
90-
if (draft !== message) {
91-
setMessage(draft)
92-
}
93-
}
94-
95-
return (
96-
<Dialog>
97-
<DialogTrigger>
98-
<Tooltip>
99-
<TooltipTrigger asChild>
100-
<div className="flex outline-0 items-center gap-1 hover:text-accent transition-colors cursor-pointer group relative">
101-
<IconPencil size={16} />
102-
</div>
103-
</TooltipTrigger>
104-
<TooltipContent>
105-
<p>{t('edit')}</p>
106-
</TooltipContent>
107-
</Tooltip>
108-
</DialogTrigger>
109-
<DialogContent className="w-3/4">
110-
<DialogHeader>
111-
<DialogTitle>{t('common:dialogs.editMessage.title')}</DialogTitle>
112-
<Textarea
113-
value={draft}
114-
onChange={(e) => setDraft(e.target.value)}
115-
className="mt-2 resize-none w-full"
116-
onKeyDown={(e) => {
117-
// Prevent key from being captured by parent components
118-
e.stopPropagation()
119-
}}
120-
/>
121-
<DialogFooter className="mt-2 flex items-center">
122-
<DialogClose asChild>
123-
<Button variant="link" size="sm" className="hover:no-underline">
124-
Cancel
125-
</Button>
126-
</DialogClose>
127-
<DialogClose asChild>
128-
<Button
129-
disabled={draft === message || !draft}
130-
onClick={handleSave}
131-
>
132-
Save
133-
</Button>
134-
</DialogClose>
135-
</DialogFooter>
136-
</DialogHeader>
137-
</DialogContent>
138-
</Dialog>
139-
)
140-
}
141-
14264
// Use memo to prevent unnecessary re-renders, but allow re-renders when props change
14365
export const ThreadContent = memo(
14466
(
@@ -349,32 +271,20 @@ export const ThreadContent = memo(
349271
)}
350272

351273
<div className="flex items-center justify-end gap-2 text-main-view-fg/60 text-xs mt-2">
352-
<EditDialog
274+
<EditMessageDialog
353275
message={
354276
item.content?.find((c) => c.type === 'text')?.text?.value ||
355277
''
356278
}
357-
setMessage={(message) => {
279+
onSave={(message) => {
358280
if (item.updateMessage) {
359281
item.updateMessage(item, message)
360282
}
361283
}}
362284
/>
363-
<Tooltip>
364-
<TooltipTrigger asChild>
365-
<button
366-
className="flex items-center gap-1 hover:text-accent transition-colors cursor-pointer group relative"
367-
onClick={() => {
368-
deleteMessage(item.thread_id, item.id)
369-
}}
370-
>
371-
<IconTrash size={16} />
372-
</button>
373-
</TooltipTrigger>
374-
<TooltipContent>
375-
<p>{t('delete')}</p>
376-
</TooltipContent>
377-
</Tooltip>
285+
<DeleteMessageDialog
286+
onDelete={() => deleteMessage(item.thread_id, item.id)}
287+
/>
378288
</div>
379289
</div>
380290
)}
@@ -456,68 +366,15 @@ export const ThreadContent = memo(
456366
'hidden'
457367
)}
458368
>
459-
<EditDialog
460-
message={item.content?.[0]?.text.value}
461-
setMessage={(message) =>
369+
<EditMessageDialog
370+
message={item.content?.[0]?.text.value || ''}
371+
onSave={(message) =>
462372
item.updateMessage && item.updateMessage(item, message)
463373
}
464374
/>
465375
<CopyButton text={item.content?.[0]?.text.value || ''} />
466-
<Tooltip>
467-
<TooltipTrigger asChild>
468-
<button
469-
className="flex items-center gap-1 hover:text-accent transition-colors cursor-pointer group relative"
470-
onClick={() => {
471-
removeMessage()
472-
}}
473-
>
474-
<IconTrash size={16} />
475-
</button>
476-
</TooltipTrigger>
477-
<TooltipContent>
478-
<p>{t('delete')}</p>
479-
</TooltipContent>
480-
</Tooltip>
481-
<Dialog>
482-
<DialogTrigger>
483-
<Tooltip>
484-
<TooltipTrigger asChild>
485-
<div className="outline-0 focus:outline-0 flex items-center gap-1 hover:text-accent transition-colors cursor-pointer group relative">
486-
<IconInfoCircle size={16} />
487-
</div>
488-
</TooltipTrigger>
489-
<TooltipContent>
490-
<p>{t('metadata')}</p>
491-
</TooltipContent>
492-
</Tooltip>
493-
</DialogTrigger>
494-
<DialogContent>
495-
<DialogHeader>
496-
<DialogTitle>
497-
{t('common:dialogs.messageMetadata.title')}
498-
</DialogTitle>
499-
<div className="space-y-2">
500-
<div className="border border-main-view-fg/10 rounded-md overflow-hidden">
501-
<CodeEditor
502-
value={JSON.stringify(
503-
item.metadata || {},
504-
null,
505-
2
506-
)}
507-
language="json"
508-
readOnly
509-
style={{
510-
fontFamily: 'ui-monospace',
511-
backgroundColor: 'transparent',
512-
height: '100%',
513-
}}
514-
className="w-full h-full !text-sm"
515-
/>
516-
</div>
517-
</div>
518-
</DialogHeader>
519-
</DialogContent>
520-
</Dialog>
376+
<DeleteMessageDialog onDelete={removeMessage} />
377+
<MessageMetadataDialog metadata={item.metadata} />
521378

522379
{item.isLastMessage && selectedModel && (
523380
<Tooltip>

0 commit comments

Comments
 (0)