feat(chat): add vision input with drag-and-drop & paste images (M9.4)
Drag-and-drop or paste images into chat input. Thumbnail preview above input, max 4 images per message. Images encoded as base64 and sent in Claude vision format (multimodal content arrays). Image attach button in chat input toolbar. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
0ae497f1db
commit
27aeb2b188
@@ -2,6 +2,7 @@ import { ref, computed } from 'vue'
|
||||
import { useChatStore } from '@/stores/chat'
|
||||
import { searchWeb } from '@/composables/useWebSearch'
|
||||
import { getApiKey } from '@/utils/key-vault'
|
||||
import type { ImageAttachment } from '@aiui/core/types/message'
|
||||
|
||||
type Provider = 'claude' | 'openrouter' | 'mock'
|
||||
|
||||
@@ -110,6 +111,23 @@ function setModel(model: string) {
|
||||
interface ChatMessage {
|
||||
role: 'user' | 'assistant'
|
||||
content: string
|
||||
images?: ImageAttachment[]
|
||||
}
|
||||
|
||||
/** Build Claude API content array for a message (multimodal when images present) */
|
||||
function buildClaudeContent(msg: ChatMessage): string | Array<Record<string, unknown>> {
|
||||
if (!msg.images || msg.images.length === 0) return msg.content
|
||||
const blocks: Array<Record<string, unknown>> = []
|
||||
for (const img of msg.images) {
|
||||
blocks.push({
|
||||
type: 'image',
|
||||
source: { type: 'base64', media_type: img.mediaType, data: img.data },
|
||||
})
|
||||
}
|
||||
if (msg.content) {
|
||||
blocks.push({ type: 'text', text: msg.content })
|
||||
}
|
||||
return blocks
|
||||
}
|
||||
|
||||
async function streamMock(
|
||||
@@ -145,13 +163,19 @@ async function streamClaude(
|
||||
headers['x-api-key'] = vaultKey
|
||||
}
|
||||
|
||||
// Build API messages with multimodal content arrays when images present
|
||||
const apiMessages = messages.map(m => ({
|
||||
role: m.role,
|
||||
content: buildClaudeContent(m),
|
||||
}))
|
||||
|
||||
const res = await fetch(CLAUDE_PATH, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify({
|
||||
model: activeModel.value,
|
||||
system: systemPrompt,
|
||||
messages,
|
||||
messages: apiMessages,
|
||||
stream: true,
|
||||
webSearch,
|
||||
}),
|
||||
@@ -363,7 +387,7 @@ export function useAI() {
|
||||
chatStore.isStreaming = false
|
||||
}
|
||||
|
||||
async function sendMessage(userText: string) {
|
||||
async function sendMessage(userText: string, images?: ImageAttachment[]) {
|
||||
const provider = activeProvider.value
|
||||
currentAbort = new AbortController()
|
||||
const signal = currentAbort.signal
|
||||
@@ -374,7 +398,11 @@ export function useAI() {
|
||||
}
|
||||
const cid = convId
|
||||
|
||||
chatStore.addMessage(cid, { role: 'user', content: userText })
|
||||
chatStore.addMessage(cid, {
|
||||
role: 'user',
|
||||
content: userText,
|
||||
images: images && images.length > 0 ? images : undefined,
|
||||
})
|
||||
const assistantMsg = chatStore.addMessage(cid, { role: 'assistant', content: '' })
|
||||
if (!assistantMsg) return
|
||||
|
||||
@@ -399,7 +427,7 @@ export function useAI() {
|
||||
|
||||
const history: ChatMessage[] = chatStore.messages
|
||||
.filter((m) => m.id !== assistantMsg.id)
|
||||
.map((m) => ({ role: m.role as 'user' | 'assistant', content: m.content }))
|
||||
.map((m) => ({ role: m.role as 'user' | 'assistant', content: m.content, images: m.images }))
|
||||
|
||||
const onToken = (text: string) => chatStore.appendToLastMessage(cid, text)
|
||||
const onError = (err: string) => {
|
||||
@@ -510,7 +538,7 @@ export function useAI() {
|
||||
|
||||
const history: ChatMessage[] = chatStore.messages
|
||||
.filter((m) => m.id !== assistantMsg.id)
|
||||
.map((m) => ({ role: m.role as 'user' | 'assistant', content: m.content }))
|
||||
.map((m) => ({ role: m.role as 'user' | 'assistant', content: m.content, images: m.images }))
|
||||
|
||||
const onToken = (text: string) => chatStore.appendToLastMessage(cid, text)
|
||||
const onError = (err: string) => {
|
||||
|
||||
Reference in New Issue
Block a user