新增: 多模态图片 URL 转 image part + 修复 / 技能联想浮层定位

前端发送前扫图片扩展 URL 转 ContentPart image(模型原生视觉,非工具下载);
SkillMention 移入 .ai-input-wrap relative 内(治 / 浮层定位错位不可见)。
This commit is contained in:
lxy
2026-08-02 02:21:32 +08:00
parent 023377ab24
commit e4f7b432aa
3 changed files with 250 additions and 11 deletions
+22 -11
View File
@@ -1,16 +1,18 @@
<template>
<div class="ai-input-area">
<SkillMention
:skill-open="skillOpen"
:filtered-skills="filteredSkills"
:skill-index="skillIndex"
:pending-skill="pendingSkill"
:skill-query="skillQuery"
@select="selectSkill"
@clear="clearSkill"
@update:skill-index="skillIndex = $event"
/>
<div class="ai-input-wrap">
<!-- 技能联想浮层须在 .ai-input-wrap(position:relative),
否则 .ai-skill-popover absolute 定位错位跑到页面外不可见(/ ) -->
<SkillMention
:skill-open="skillOpen"
:filtered-skills="filteredSkills"
:skill-index="skillIndex"
:pending-skill="pendingSkill"
:skill-query="skillQuery"
@select="selectSkill"
@clear="clearSkill"
@update:skill-index="skillIndex = $event"
/>
<!-- F-260614-05 Phase 2b: 待发送图片预览 -->
<ImageInput :pending-images="pendingImages" @remove="removePendingImage" />
<textarea
@@ -103,6 +105,7 @@ import { useI18n } from 'vue-i18n'
import { useAiStore } from '../../stores/ai'
import { useProjectStore } from '../../stores/project'
import { getConvState, textIdle } from '../../composables/ai/useAiEvents'
import { extractImageUrlParts } from '../../composables/ai/utils'
import SkillMention from './SkillMention.vue'
import ImageInput from './ImageInput.vue'
import MentionPopover from './MentionPopover.vue'
@@ -688,6 +691,14 @@ async function handleSend() {
}),
]
: undefined
// F-260614-05 Phase 2c: 扫输入文本里的图片 URL → image 片(url 模式),与粘贴 base64 图合并。
// 治会话 01f05167:用户贴 URL 文本 → 没转 parts → 模型只收字符串 → AI fetch 失败 L1 熔断。
// text 保留原样(供 audit/纯文本模型),parts 附加 image 让 vision 模型直接看图。
// parts 已含 text 片(粘贴图场景)时直接展开追加 url 图;parts 为空且 text 非空时补 text 片对齐上面构造逻辑。
const urlParts = extractImageUrlParts(text)
const mergedParts: ContentPart[] | undefined = urlParts.length > 0
? [...(parts ?? (text ? [{ type: 'text' as const, text } as ContentPart] : [])), ...urlParts]
: parts
// 生成中不再拦截:sendMessage 会把消息排入待发送队列,完成后自动续发
// 先清输入(无论立即发还是入队);队列满/IPC 失败时 catch 回填
inputText.value = ''
@@ -704,7 +715,7 @@ async function handleSend() {
try {
// Input Augmentation: 透传 mentionSpans(L0 直接 doSend 注入 + 本地 user 消息挂 mentionSpans 渲染 chip;
// L1 入队续发当前不挂 spans,见 useAiSend.sendMessage 设计取舍注释)
await store.sendMessage(text, skill?.name, false, parts, snapshotSpans.length > 0 ? snapshotSpans : undefined)
await store.sendMessage(text, skill?.name, false, mergedParts, snapshotSpans.length > 0 ? snapshotSpans : undefined)
} catch (e) {
console.error('[AI] 发送失败:', e)
inputText.value = text