feat: chat stop button, image paste, vision AI, search fixes
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 43s
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 43s
- Add stop button to all chat interfaces (floating, contextual, full-page) - Add conversation sliding window (50 messages) to prevent context overflow - Add chat timeout warning (30s toast) - Force response language in chat system prompt (mandatory per-locale) - Add image paste from clipboard in all note editors (card, list, input) - Fix upload API to infer extension from MIME type for clipboard images - Add image description support in note AI chat (base64 vision) - Fix search regex crash on special characters (escape user input) - Fix search case-insensitivity on PostgreSQL (mode: 'insensitive') - Add try/catch around semantic search in chat route (prevent blocking) - Add new chat button to floating AI assistant - Fix empty thinking bubbles for reasoning models (filter non-text parts) - Remove duplicate AI assistant toggle from note editor header - Improve link metadata scraping (timeout, content-type check, relative URLs) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -7,7 +7,7 @@ import type { UIMessage } from 'ai'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
X, Bot, Sparkles, Send, Loader2,
|
||||
X, Bot, Sparkles, Send, Loader2, Square,
|
||||
Briefcase, Palette, GraduationCap, Coffee,
|
||||
Lightbulb, Minimize2, AlignLeft, Wand2,
|
||||
Globe, BookOpen, FileText, RotateCcw, Check,
|
||||
@@ -70,6 +70,7 @@ interface ContextualAIChatProps {
|
||||
onClose: () => void
|
||||
noteTitle?: string
|
||||
noteContent?: string
|
||||
noteImages?: string[]
|
||||
/** Called when an action result should be injected into the note */
|
||||
onApplyToNote?: (newContent: string) => void
|
||||
/** Called when the user wants to undo the last injected action */
|
||||
@@ -88,6 +89,7 @@ export function ContextualAIChat({
|
||||
onClose,
|
||||
noteTitle,
|
||||
noteContent,
|
||||
noteImages,
|
||||
onApplyToNote,
|
||||
onUndoLastAction,
|
||||
lastActionApplied = false,
|
||||
@@ -115,7 +117,12 @@ export function ContextualAIChat({
|
||||
const buildChatBody = () => {
|
||||
const body: Record<string, any> = { language, webSearch }
|
||||
if (chatScope === 'note') {
|
||||
body.noteContext = { title: noteTitle || '', content: noteContent || '', tone: selectedTone }
|
||||
body.noteContext = {
|
||||
title: noteTitle || '',
|
||||
content: noteContent || '',
|
||||
tone: selectedTone,
|
||||
images: noteImages || [],
|
||||
}
|
||||
} else if (chatScope !== 'all') {
|
||||
// scope is a notebook ID
|
||||
body.notebookId = chatScope
|
||||
@@ -123,7 +130,7 @@ export function ContextualAIChat({
|
||||
return body
|
||||
}
|
||||
|
||||
const { messages, sendMessage, status } = useChat({ transport })
|
||||
const { messages, sendMessage, status, stop } = useChat({ transport })
|
||||
|
||||
const isLoading = status === 'submitted' || status === 'streaming'
|
||||
|
||||
@@ -273,6 +280,8 @@ export function ContextualAIChat({
|
||||
|
||||
{messages.map((msg: UIMessage) => {
|
||||
const content = getMessageContent(msg)
|
||||
// Skip empty assistant messages (thinking/reasoning phase)
|
||||
if (msg.role === 'assistant' && !content) return null
|
||||
return (
|
||||
<div key={msg.id} className={cn('flex gap-2', msg.role === 'user' && 'flex-row-reverse')}>
|
||||
<div className={cn(
|
||||
@@ -414,14 +423,24 @@ export function ContextualAIChat({
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
size="icon"
|
||||
className="h-7 w-7 rounded-lg bg-primary text-primary-foreground shadow-sm disabled:opacity-50"
|
||||
onClick={handleSend}
|
||||
disabled={isLoading || !input.trim()}
|
||||
>
|
||||
{isLoading ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Send className="h-3.5 w-3.5 ml-0.5" />}
|
||||
</Button>
|
||||
{isLoading ? (
|
||||
<Button
|
||||
size="icon"
|
||||
className="h-7 w-7 rounded-lg bg-red-500 text-white shadow-sm hover:bg-red-600"
|
||||
onClick={() => stop()}
|
||||
>
|
||||
<Square className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
size="icon"
|
||||
className="h-7 w-7 rounded-lg bg-primary text-primary-foreground shadow-sm disabled:opacity-50"
|
||||
onClick={handleSend}
|
||||
disabled={!input.trim()}
|
||||
>
|
||||
<Send className="h-3.5 w-3.5 ml-0.5" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-[9px] text-muted-foreground/40 text-center mt-1">{t('ai.newLineHint')}</p>
|
||||
|
||||
Reference in New Issue
Block a user