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
|
||||
|
||||
@@ -13,6 +13,8 @@ import { Messages } from './Messages'
|
||||
import { MultimodalInput } from './MultimodalInput'
|
||||
import { generateUUID } from '@/lib/utils'
|
||||
import { Eraser } from 'lucide-react'
|
||||
import { useDragStore } from '@/stores/dragStore'
|
||||
import { getSettingsConfig } from '@/lib/api'
|
||||
|
||||
type Status = 'submitted' | 'streaming' | 'ready' | 'error'
|
||||
|
||||
@@ -51,6 +53,49 @@ export function Chat({
|
||||
const [status, setStatus] = useState<Status>('ready')
|
||||
const [attachments, setAttachments] = useState<Attachment[]>([])
|
||||
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(() => {
|
||||
messagesEndRef.current?.scrollIntoView({ behavior: 'auto' })
|
||||
@@ -211,6 +256,8 @@ export function Chat({
|
||||
setInput={setInput}
|
||||
status={status}
|
||||
stop={handleStop}
|
||||
onDrop={handleDrop}
|
||||
onDragOver={handleDragOver}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export function Greeting() {
|
||||
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>
|
||||
|
||||
@@ -30,6 +30,8 @@ interface MultimodalInputProps {
|
||||
messages: ChatMessage[]
|
||||
sendMessage: (text: string, attachments: Attachment[]) => Promise<void>
|
||||
className?: string
|
||||
onDrop?: (e: React.DragEvent) => void
|
||||
onDragOver?: (e: React.DragEvent) => void
|
||||
}
|
||||
|
||||
export function MultimodalInput({
|
||||
@@ -42,6 +44,8 @@ export function MultimodalInput({
|
||||
setAttachments,
|
||||
sendMessage,
|
||||
className,
|
||||
onDrop,
|
||||
onDragOver,
|
||||
}: MultimodalInputProps) {
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null)
|
||||
|
||||
@@ -135,6 +139,14 @@ export function MultimodalInput({
|
||||
value={input}
|
||||
onChange={handleInput}
|
||||
onKeyDown={handleKeyDown}
|
||||
onDrop={(e) => {
|
||||
e.preventDefault()
|
||||
onDrop?.(e)
|
||||
}}
|
||||
onDragOver={(e) => {
|
||||
e.preventDefault()
|
||||
onDragOver?.(e)
|
||||
}}
|
||||
placeholder="发送消息..."
|
||||
rows={1}
|
||||
/>
|
||||
|
||||
@@ -83,6 +83,10 @@ export const getSettings = async (): Promise<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> => {
|
||||
return await fetchApi<Settings>('/api/settings', {
|
||||
method: 'POST',
|
||||
|
||||
@@ -13,6 +13,7 @@ export {
|
||||
renameItem,
|
||||
runAiTask,
|
||||
getSettings,
|
||||
getSettingsConfig,
|
||||
saveSettings,
|
||||
uploadPdfForParsing,
|
||||
parseLocalHtml,
|
||||
|
||||
Reference in New Issue
Block a user