mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-01-02 14:22:28 +08:00
fix: move History and Download buttons to Settings dialog for cleaner chat interface
This commit is contained in:
@@ -1,22 +1,15 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import {
|
import { Image as ImageIcon, Loader2, Send, Trash2 } from "lucide-react"
|
||||||
Download,
|
|
||||||
History,
|
|
||||||
Image as ImageIcon,
|
|
||||||
Loader2,
|
|
||||||
Send,
|
|
||||||
Trash2,
|
|
||||||
} from "lucide-react"
|
|
||||||
import type React from "react"
|
import type React from "react"
|
||||||
import { useCallback, useEffect, useRef, useState } from "react"
|
import { useCallback, useEffect, useRef, useState } from "react"
|
||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
import { ButtonWithTooltip } from "@/components/button-with-tooltip"
|
import { ButtonWithTooltip } from "@/components/button-with-tooltip"
|
||||||
import { ErrorToast } from "@/components/error-toast"
|
import { ErrorToast } from "@/components/error-toast"
|
||||||
import { HistoryDialog } from "@/components/history-dialog"
|
|
||||||
import { ModelSelector } from "@/components/model-selector"
|
import { ModelSelector } from "@/components/model-selector"
|
||||||
import { ResetWarningModal } from "@/components/reset-warning-modal"
|
import { ResetWarningModal } from "@/components/reset-warning-modal"
|
||||||
import { SaveDialog } from "@/components/save-dialog"
|
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { Switch } from "@/components/ui/switch"
|
import { Switch } from "@/components/ui/switch"
|
||||||
import { Textarea } from "@/components/ui/textarea"
|
import { Textarea } from "@/components/ui/textarea"
|
||||||
@@ -25,7 +18,7 @@ import {
|
|||||||
TooltipContent,
|
TooltipContent,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip"
|
} from "@/components/ui/tooltip"
|
||||||
import { useDiagram } from "@/contexts/diagram-context"
|
|
||||||
import { useDictionary } from "@/hooks/use-dictionary"
|
import { useDictionary } from "@/hooks/use-dictionary"
|
||||||
import { formatMessage } from "@/lib/i18n/utils"
|
import { formatMessage } from "@/lib/i18n/utils"
|
||||||
import { isPdfFile, isTextFile } from "@/lib/pdf-utils"
|
import { isPdfFile, isTextFile } from "@/lib/pdf-utils"
|
||||||
@@ -152,8 +145,7 @@ interface ChatInputProps {
|
|||||||
File,
|
File,
|
||||||
{ text: string; charCount: number; isExtracting: boolean }
|
{ text: string; charCount: number; isExtracting: boolean }
|
||||||
>
|
>
|
||||||
showHistory?: boolean
|
|
||||||
onToggleHistory?: (show: boolean) => void
|
|
||||||
sessionId?: string
|
sessionId?: string
|
||||||
error?: Error | null
|
error?: Error | null
|
||||||
minimalStyle?: boolean
|
minimalStyle?: boolean
|
||||||
@@ -175,9 +167,7 @@ export function ChatInput({
|
|||||||
files = [],
|
files = [],
|
||||||
onFileChange = () => {},
|
onFileChange = () => {},
|
||||||
pdfData = new Map(),
|
pdfData = new Map(),
|
||||||
showHistory = false,
|
|
||||||
onToggleHistory = () => {},
|
|
||||||
sessionId,
|
|
||||||
error = null,
|
error = null,
|
||||||
minimalStyle = false,
|
minimalStyle = false,
|
||||||
onMinimalStyleChange = () => {},
|
onMinimalStyleChange = () => {},
|
||||||
@@ -188,12 +178,7 @@ export function ChatInput({
|
|||||||
onConfigureModels = () => {},
|
onConfigureModels = () => {},
|
||||||
}: ChatInputProps) {
|
}: ChatInputProps) {
|
||||||
const dict = useDictionary()
|
const dict = useDictionary()
|
||||||
const {
|
|
||||||
diagramHistory,
|
|
||||||
saveDiagramToFile,
|
|
||||||
showSaveDialog,
|
|
||||||
setShowSaveDialog,
|
|
||||||
} = useDiagram()
|
|
||||||
const textareaRef = useRef<HTMLTextAreaElement>(null)
|
const textareaRef = useRef<HTMLTextAreaElement>(null)
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||||
const [isDragging, setIsDragging] = useState(false)
|
const [isDragging, setIsDragging] = useState(false)
|
||||||
@@ -386,11 +371,6 @@ export function ChatInput({
|
|||||||
onClear={handleClear}
|
onClear={handleClear}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<HistoryDialog
|
|
||||||
showHistory={showHistory}
|
|
||||||
onToggleHistory={onToggleHistory}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
@@ -421,41 +401,7 @@ export function ChatInput({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-1 overflow-hidden justify-end">
|
<div className="flex items-center gap-1 overflow-hidden justify-end">
|
||||||
<ButtonWithTooltip
|
<div className="flex items-center gap-1 overflow-x-hidden">
|
||||||
type="button"
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => onToggleHistory(true)}
|
|
||||||
disabled={isDisabled || diagramHistory.length === 0}
|
|
||||||
tooltipContent={dict.chat.diagramHistory}
|
|
||||||
className="h-8 w-8 p-0 text-muted-foreground hover:text-foreground"
|
|
||||||
>
|
|
||||||
<History className="h-4 w-4" />
|
|
||||||
</ButtonWithTooltip>
|
|
||||||
|
|
||||||
<ButtonWithTooltip
|
|
||||||
type="button"
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => setShowSaveDialog(true)}
|
|
||||||
disabled={isDisabled}
|
|
||||||
tooltipContent={dict.chat.saveDiagram}
|
|
||||||
className="h-8 w-8 p-0 text-muted-foreground hover:text-foreground"
|
|
||||||
>
|
|
||||||
<Download className="h-4 w-4" />
|
|
||||||
</ButtonWithTooltip>
|
|
||||||
|
|
||||||
<SaveDialog
|
|
||||||
open={showSaveDialog}
|
|
||||||
onOpenChange={setShowSaveDialog}
|
|
||||||
onSave={(filename, format) =>
|
|
||||||
saveDiagramToFile(filename, format, sessionId)
|
|
||||||
}
|
|
||||||
defaultFilename={`diagram-${new Date()
|
|
||||||
.toISOString()
|
|
||||||
.slice(0, 10)}`}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ButtonWithTooltip
|
<ButtonWithTooltip
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@@ -477,7 +423,7 @@ export function ChatInput({
|
|||||||
multiple
|
multiple
|
||||||
disabled={isDisabled}
|
disabled={isDisabled}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
<ModelSelector
|
<ModelSelector
|
||||||
models={models}
|
models={models}
|
||||||
selectedModelId={selectedModelId}
|
selectedModelId={selectedModelId}
|
||||||
@@ -486,9 +432,7 @@ export function ChatInput({
|
|||||||
disabled={isDisabled}
|
disabled={isDisabled}
|
||||||
showUnvalidatedModels={showUnvalidatedModels}
|
showUnvalidatedModels={showUnvalidatedModels}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="w-px h-5 bg-border mx-1" />
|
<div className="w-px h-5 bg-border mx-1" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={isDisabled || !input.trim()}
|
disabled={isDisabled || !input.trim()}
|
||||||
|
|||||||
@@ -154,7 +154,6 @@ export default function ChatPanel({
|
|||||||
// File processing using extracted hook
|
// File processing using extracted hook
|
||||||
const { files, pdfData, handleFileChange, setFiles } = useFileProcessor()
|
const { files, pdfData, handleFileChange, setFiles } = useFileProcessor()
|
||||||
|
|
||||||
const [showHistory, setShowHistory] = useState(false)
|
|
||||||
const [showSettingsDialog, setShowSettingsDialog] = useState(false)
|
const [showSettingsDialog, setShowSettingsDialog] = useState(false)
|
||||||
const [showModelConfigDialog, setShowModelConfigDialog] = useState(false)
|
const [showModelConfigDialog, setShowModelConfigDialog] = useState(false)
|
||||||
|
|
||||||
@@ -1066,8 +1065,6 @@ export default function ChatPanel({
|
|||||||
files={files}
|
files={files}
|
||||||
onFileChange={handleFileChange}
|
onFileChange={handleFileChange}
|
||||||
pdfData={pdfData}
|
pdfData={pdfData}
|
||||||
showHistory={showHistory}
|
|
||||||
onToggleHistory={setShowHistory}
|
|
||||||
sessionId={sessionId}
|
sessionId={sessionId}
|
||||||
error={error}
|
error={error}
|
||||||
minimalStyle={minimalStyle}
|
minimalStyle={minimalStyle}
|
||||||
@@ -1088,6 +1085,7 @@ export default function ChatPanel({
|
|||||||
onToggleDrawioUi={onToggleDrawioUi}
|
onToggleDrawioUi={onToggleDrawioUi}
|
||||||
darkMode={darkMode}
|
darkMode={darkMode}
|
||||||
onToggleDarkMode={onToggleDarkMode}
|
onToggleDarkMode={onToggleDarkMode}
|
||||||
|
sessionId={sessionId}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ModelConfigDialog
|
<ModelConfigDialog
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { Moon, Sun } from "lucide-react"
|
import { Download, History, Moon, Sun } from "lucide-react"
|
||||||
import { usePathname, useRouter, useSearchParams } from "next/navigation"
|
import { usePathname, useRouter, useSearchParams } from "next/navigation"
|
||||||
import { Suspense, useEffect, useState } from "react"
|
import { Suspense, useEffect, useState } from "react"
|
||||||
|
import { HistoryDialog } from "@/components/history-dialog"
|
||||||
|
import { type ExportFormat, SaveDialog } from "@/components/save-dialog"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -21,6 +23,7 @@ import {
|
|||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select"
|
} from "@/components/ui/select"
|
||||||
import { Switch } from "@/components/ui/switch"
|
import { Switch } from "@/components/ui/switch"
|
||||||
|
import { useDiagram } from "@/contexts/diagram-context"
|
||||||
import { useDictionary } from "@/hooks/use-dictionary"
|
import { useDictionary } from "@/hooks/use-dictionary"
|
||||||
import { getApiEndpoint } from "@/lib/base-path"
|
import { getApiEndpoint } from "@/lib/base-path"
|
||||||
import { i18n, type Locale } from "@/lib/i18n/config"
|
import { i18n, type Locale } from "@/lib/i18n/config"
|
||||||
@@ -65,6 +68,7 @@ interface SettingsDialogProps {
|
|||||||
onToggleDrawioUi: () => void
|
onToggleDrawioUi: () => void
|
||||||
darkMode: boolean
|
darkMode: boolean
|
||||||
onToggleDarkMode: () => void
|
onToggleDarkMode: () => void
|
||||||
|
sessionId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const STORAGE_ACCESS_CODE_KEY = "next-ai-draw-io-access-code"
|
export const STORAGE_ACCESS_CODE_KEY = "next-ai-draw-io-access-code"
|
||||||
@@ -86,6 +90,7 @@ function SettingsContent({
|
|||||||
onToggleDrawioUi,
|
onToggleDrawioUi,
|
||||||
darkMode,
|
darkMode,
|
||||||
onToggleDarkMode,
|
onToggleDarkMode,
|
||||||
|
sessionId,
|
||||||
}: SettingsDialogProps) {
|
}: SettingsDialogProps) {
|
||||||
const dict = useDictionary()
|
const dict = useDictionary()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -95,11 +100,21 @@ function SettingsContent({
|
|||||||
const [closeProtection, setCloseProtection] = useState(true)
|
const [closeProtection, setCloseProtection] = useState(true)
|
||||||
const [isVerifying, setIsVerifying] = useState(false)
|
const [isVerifying, setIsVerifying] = useState(false)
|
||||||
const [error, setError] = useState("")
|
const [error, setError] = useState("")
|
||||||
|
const [showHistory, setShowHistory] = useState(false)
|
||||||
|
const [showSaveDialog, setShowSaveDialog] = useState(false)
|
||||||
const [accessCodeRequired, setAccessCodeRequired] = useState(
|
const [accessCodeRequired, setAccessCodeRequired] = useState(
|
||||||
() => getStoredAccessCodeRequired() ?? false,
|
() => getStoredAccessCodeRequired() ?? false,
|
||||||
)
|
)
|
||||||
const [currentLang, setCurrentLang] = useState("en")
|
const [currentLang, setCurrentLang] = useState("en")
|
||||||
|
|
||||||
|
// Get diagram context
|
||||||
|
const { diagramHistory, saveDiagramToFile } = useDiagram()
|
||||||
|
|
||||||
|
// Handler for saving diagram (RENAMED to avoid conflict)
|
||||||
|
const handleDiagramSave = (filename: string, format: ExportFormat) => {
|
||||||
|
saveDiagramToFile(filename, format, sessionId)
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Only fetch if not cached in localStorage
|
// Only fetch if not cached in localStorage
|
||||||
if (getStoredAccessCodeRequired() !== null) return
|
if (getStoredAccessCodeRequired() !== null) return
|
||||||
@@ -206,6 +221,7 @@ function SettingsContent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<DialogContent className="sm:max-w-lg p-0 gap-0">
|
<DialogContent className="sm:max-w-lg p-0 gap-0">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<DialogHeader className="px-6 pt-6 pb-4">
|
<DialogHeader className="px-6 pt-6 pb-4">
|
||||||
@@ -249,7 +265,9 @@ function SettingsContent({
|
|||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={isVerifying || !accessCode.trim()}
|
disabled={
|
||||||
|
isVerifying || !accessCode.trim()
|
||||||
|
}
|
||||||
className="h-9 px-4 rounded-xl"
|
className="h-9 px-4 rounded-xl"
|
||||||
>
|
>
|
||||||
{isVerifying ? "..." : dict.common.save}
|
{isVerifying ? "..." : dict.common.save}
|
||||||
@@ -333,7 +351,9 @@ function SettingsContent({
|
|||||||
{/* Close Protection */}
|
{/* Close Protection */}
|
||||||
<SettingItem
|
<SettingItem
|
||||||
label={dict.settings.closeProtection}
|
label={dict.settings.closeProtection}
|
||||||
description={dict.settings.closeProtectionDescription}
|
description={
|
||||||
|
dict.settings.closeProtectionDescription
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Switch
|
<Switch
|
||||||
id="close-protection"
|
id="close-protection"
|
||||||
@@ -348,6 +368,38 @@ function SettingsContent({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</SettingItem>
|
</SettingItem>
|
||||||
|
|
||||||
|
{/* Diagram Actions */}
|
||||||
|
<SettingItem
|
||||||
|
label={dict.settings.diagramActions}
|
||||||
|
description={
|
||||||
|
dict.settings.diagramActionsDescription
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button
|
||||||
|
id="history-button"
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setShowHistory(true)}
|
||||||
|
disabled={diagramHistory.length === 0}
|
||||||
|
className="h-9 rounded-xl border-border-subtle hover:bg-interactive-hover"
|
||||||
|
>
|
||||||
|
<History className="h-4 w-4 mr-1.5" />
|
||||||
|
{dict.settings.history}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
id="download-button"
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setShowSaveDialog(true)}
|
||||||
|
className="h-9 rounded-xl border-border-subtle hover:bg-interactive-hover"
|
||||||
|
>
|
||||||
|
<Download className="h-4 w-4 mr-1.5" />
|
||||||
|
{dict.settings.download}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</SettingItem>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -358,6 +410,22 @@ function SettingsContent({
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
;(
|
||||||
|
<HistoryDialog
|
||||||
|
showHistory={showHistory}
|
||||||
|
onToggleHistory={setShowHistory}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
<SaveDialog
|
||||||
|
open={showSaveDialog}
|
||||||
|
onOpenChange={setShowSaveDialog}
|
||||||
|
onSave={handleDiagramSave}
|
||||||
|
defaultFilename={`diagram-${new Date()
|
||||||
|
.toISOString()
|
||||||
|
.slice(0, 10)}
|
||||||
|
`}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -98,7 +98,11 @@
|
|||||||
"minimal": "Minimal",
|
"minimal": "Minimal",
|
||||||
"sketch": "Sketch",
|
"sketch": "Sketch",
|
||||||
"closeProtection": "Close Protection",
|
"closeProtection": "Close Protection",
|
||||||
"closeProtectionDescription": "Show confirmation when leaving the page."
|
"closeProtectionDescription": "Show confirmation when leaving the page.",
|
||||||
|
"diagramActions": "Diagram Actions",
|
||||||
|
"diagramActionsDescription": "Manage diagram history and exports",
|
||||||
|
"history": "History",
|
||||||
|
"download": "Download"
|
||||||
},
|
},
|
||||||
"save": {
|
"save": {
|
||||||
"title": "Save Diagram",
|
"title": "Save Diagram",
|
||||||
|
|||||||
@@ -98,7 +98,11 @@
|
|||||||
"minimal": "ミニマル",
|
"minimal": "ミニマル",
|
||||||
"sketch": "スケッチ",
|
"sketch": "スケッチ",
|
||||||
"closeProtection": "ページ離脱確認",
|
"closeProtection": "ページ離脱確認",
|
||||||
"closeProtectionDescription": "ページを離れる際に確認を表示します。"
|
"closeProtectionDescription": "ページを離れる際に確認を表示します。",
|
||||||
|
"diagramActions": "ダイアグラム操作",
|
||||||
|
"diagramActionsDescription": "ダイアグラムの履歴とエクスポートを管理",
|
||||||
|
"history": "履歴",
|
||||||
|
"download": "ダウンロード"
|
||||||
},
|
},
|
||||||
"save": {
|
"save": {
|
||||||
"title": "ダイアグラムを保存",
|
"title": "ダイアグラムを保存",
|
||||||
|
|||||||
@@ -98,7 +98,11 @@
|
|||||||
"minimal": "简约",
|
"minimal": "简约",
|
||||||
"sketch": "草图",
|
"sketch": "草图",
|
||||||
"closeProtection": "关闭确认",
|
"closeProtection": "关闭确认",
|
||||||
"closeProtectionDescription": "离开页面时显示确认。"
|
"closeProtectionDescription": "离开页面时显示确认。",
|
||||||
|
"diagramActions": "图表操作",
|
||||||
|
"diagramActionsDescription": "管理图表历史记录和导出",
|
||||||
|
"history": "历史记录",
|
||||||
|
"download": "下载"
|
||||||
},
|
},
|
||||||
"save": {
|
"save": {
|
||||||
"title": "保存图表",
|
"title": "保存图表",
|
||||||
|
|||||||
Reference in New Issue
Block a user