feat(home): add drag file to chat input, add settings config API
This commit is contained in:
@@ -51,4 +51,13 @@ router.post(
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/config',
|
||||||
|
asyncHandler(async (req: Request, res: Response) => {
|
||||||
|
successResponse(res, {
|
||||||
|
notebookRoot: NOTEBOOK_ROOT,
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import { Messages } from './Messages'
|
|||||||
import { MultimodalInput } from './MultimodalInput'
|
import { MultimodalInput } from './MultimodalInput'
|
||||||
import { generateUUID } from '@/lib/utils'
|
import { generateUUID } from '@/lib/utils'
|
||||||
import { Eraser } from 'lucide-react'
|
import { Eraser } from 'lucide-react'
|
||||||
|
import { useDragStore } from '@/stores/dragStore'
|
||||||
|
import { getSettingsConfig } from '@/lib/api'
|
||||||
|
|
||||||
type Status = 'submitted' | 'streaming' | 'ready' | 'error'
|
type Status = 'submitted' | 'streaming' | 'ready' | 'error'
|
||||||
|
|
||||||
@@ -51,6 +53,49 @@ export function Chat({
|
|||||||
const [status, setStatus] = useState<Status>('ready')
|
const [status, setStatus] = useState<Status>('ready')
|
||||||
const [attachments, setAttachments] = useState<Attachment[]>([])
|
const [attachments, setAttachments] = useState<Attachment[]>([])
|
||||||
const [showInputAtBottom, setShowInputAtBottom] = useState(false)
|
const [showInputAtBottom, setShowInputAtBottom] = useState(false)
|
||||||
|
const [notebookRoot, setNotebookRoot] = useState<string>('')
|
||||||
|
const { state: dragState } = useDragStore()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getSettingsConfig().then(config => {
|
||||||
|
// 统一 notebookRoot 的斜杠
|
||||||
|
const normalizedRoot = (config.notebookRoot || '').replace(/\\/g, '/')
|
||||||
|
setNotebookRoot(normalizedRoot)
|
||||||
|
}).catch(e => {
|
||||||
|
console.error('Failed to get notebook root:', e)
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleDrop = useCallback((e: React.DragEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
|
||||||
|
// 优先从 dragStore 获取
|
||||||
|
let { draggedPath, draggedType } = dragState
|
||||||
|
|
||||||
|
// 如果 dragStore 为空,尝试从 dataTransfer 获取
|
||||||
|
if (!draggedPath) {
|
||||||
|
draggedPath = e.dataTransfer.getData('text/plain')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (draggedPath) {
|
||||||
|
// 统一路径中的斜杠,并拼接完整路径,用【】包围
|
||||||
|
const normalizedPath = draggedPath.replace(/\\/g, '/')
|
||||||
|
const fullPath = notebookRoot ? `${notebookRoot}/${normalizedPath}` : normalizedPath
|
||||||
|
const pathWithBrackets = `【${fullPath}】`
|
||||||
|
setInput(prev => prev + pathWithBrackets)
|
||||||
|
|
||||||
|
// 延迟聚焦输入框
|
||||||
|
setTimeout(() => {
|
||||||
|
const textarea = document.querySelector('textarea') as HTMLTextAreaElement
|
||||||
|
textarea?.focus()
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
}, [dragState, notebookRoot])
|
||||||
|
|
||||||
|
const handleDragOver = useCallback((e: React.DragEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
|
}, [])
|
||||||
|
|
||||||
const scrollToBottom = useCallback(() => {
|
const scrollToBottom = useCallback(() => {
|
||||||
messagesEndRef.current?.scrollIntoView({ behavior: 'auto' })
|
messagesEndRef.current?.scrollIntoView({ behavior: 'auto' })
|
||||||
@@ -211,6 +256,8 @@ export function Chat({
|
|||||||
setInput={setInput}
|
setInput={setInput}
|
||||||
status={status}
|
status={status}
|
||||||
stop={handleStop}
|
stop={handleStop}
|
||||||
|
onDrop={handleDrop}
|
||||||
|
onDragOver={handleDragOver}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
export function Greeting() {
|
export function Greeting() {
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto mt-64 max-w-2xl px-4 text-center">
|
<div className="mx-auto mt-60 max-w-2xl px-4 text-center">
|
||||||
<h1 className="text-3xl font-bold tracking-tight">
|
<h1 className="text-3xl font-bold tracking-tight">
|
||||||
开始今天的工作吧!
|
开始今天的工作吧!
|
||||||
</h1>
|
</h1>
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ interface MultimodalInputProps {
|
|||||||
messages: ChatMessage[]
|
messages: ChatMessage[]
|
||||||
sendMessage: (text: string, attachments: Attachment[]) => Promise<void>
|
sendMessage: (text: string, attachments: Attachment[]) => Promise<void>
|
||||||
className?: string
|
className?: string
|
||||||
|
onDrop?: (e: React.DragEvent) => void
|
||||||
|
onDragOver?: (e: React.DragEvent) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MultimodalInput({
|
export function MultimodalInput({
|
||||||
@@ -42,6 +44,8 @@ export function MultimodalInput({
|
|||||||
setAttachments,
|
setAttachments,
|
||||||
sendMessage,
|
sendMessage,
|
||||||
className,
|
className,
|
||||||
|
onDrop,
|
||||||
|
onDragOver,
|
||||||
}: MultimodalInputProps) {
|
}: MultimodalInputProps) {
|
||||||
const textareaRef = useRef<HTMLTextAreaElement>(null)
|
const textareaRef = useRef<HTMLTextAreaElement>(null)
|
||||||
|
|
||||||
@@ -135,6 +139,14 @@ export function MultimodalInput({
|
|||||||
value={input}
|
value={input}
|
||||||
onChange={handleInput}
|
onChange={handleInput}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
|
onDrop={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
onDrop?.(e)
|
||||||
|
}}
|
||||||
|
onDragOver={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
onDragOver?.(e)
|
||||||
|
}}
|
||||||
placeholder="发送消息..."
|
placeholder="发送消息..."
|
||||||
rows={1}
|
rows={1}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -83,6 +83,10 @@ export const getSettings = async (): Promise<Settings> => {
|
|||||||
return await fetchApi<Settings>('/api/settings')
|
return await fetchApi<Settings>('/api/settings')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getSettingsConfig = async (): Promise<{ notebookRoot: string }> => {
|
||||||
|
return await fetchApi<{ notebookRoot: string }>('/api/settings/config')
|
||||||
|
}
|
||||||
|
|
||||||
export const saveSettings = async (settings: Settings): Promise<Settings> => {
|
export const saveSettings = async (settings: Settings): Promise<Settings> => {
|
||||||
return await fetchApi<Settings>('/api/settings', {
|
return await fetchApi<Settings>('/api/settings', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export {
|
|||||||
renameItem,
|
renameItem,
|
||||||
runAiTask,
|
runAiTask,
|
||||||
getSettings,
|
getSettings,
|
||||||
|
getSettingsConfig,
|
||||||
saveSettings,
|
saveSettings,
|
||||||
uploadPdfForParsing,
|
uploadPdfForParsing,
|
||||||
parseLocalHtml,
|
parseLocalHtml,
|
||||||
|
|||||||
Reference in New Issue
Block a user