mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-07-09 17:29:12 +00:00
feat(web): enlarge user message images on click
Wrap user-message image thumbnails in a button that opens the existing image preview panel, matching the tool-result image behavior. Adds the en/zh aria-label and resets the button chrome so the thumbnail looks unchanged.
This commit is contained in:
parent
276407d2a4
commit
1da6446437
4 changed files with 64 additions and 16 deletions
5
.changeset/web-user-image-preview.md
Normal file
5
.changeset/web-user-image-preview.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@moonshot-ai/kimi-code": minor
|
||||
---
|
||||
|
||||
Click an image in a user message to open its full-size preview in the web chat.
|
||||
|
|
@ -8,6 +8,7 @@ import ToolGroup from './ToolGroup.vue';
|
|||
import Markdown from './Markdown.vue';
|
||||
import ThinkingBlock from './ThinkingBlock.vue';
|
||||
import ActivityNotice from './ActivityNotice.vue';
|
||||
import AuthMedia from './AuthMedia.vue';
|
||||
import MoonSpinner from '../ui/MoonSpinner.vue';
|
||||
import Spinner from '../ui/Spinner.vue';
|
||||
import Icon from '../ui/Icon.vue';
|
||||
|
|
@ -487,6 +488,12 @@ function copyUserMessage(turn: ChatTurn): void {
|
|||
}).catch(() => {/* ignore */});
|
||||
}
|
||||
|
||||
function userImageMedia(img: { url: string; alt?: string }): ToolMedia {
|
||||
// User-uploaded images carry no path/mime metadata; the preview panel falls
|
||||
// back to a generic label and sniffs the mime from the URL when needed.
|
||||
return { kind: 'image', url: img.url, path: img.alt };
|
||||
}
|
||||
|
||||
function isStreamingRenderBlock(turn: ChatTurn, block: { sourceIndex: number }): boolean {
|
||||
if (turn.id !== streamingTurnId.value) return false;
|
||||
return block.sourceIndex === turnBlocks(turn).length - 1;
|
||||
|
|
@ -537,21 +544,28 @@ function isStreamingRenderBlock(turn: ChatTurn, block: { sourceIndex: number }):
|
|||
<!-- Image / video attachments -->
|
||||
<div v-if="turn.images && turn.images.length > 0" class="u-imgs">
|
||||
<template v-for="(img, ii) in turn.images" :key="ii">
|
||||
<video
|
||||
<AuthMedia
|
||||
v-if="img.kind === 'video'"
|
||||
class="u-img"
|
||||
:src="img.url"
|
||||
controls
|
||||
playsinline
|
||||
preload="metadata"
|
||||
:url="img.url"
|
||||
kind="video"
|
||||
:file-id="img.fileId"
|
||||
media-class="u-img"
|
||||
/>
|
||||
<img
|
||||
<button
|
||||
v-else
|
||||
class="u-img"
|
||||
:src="img.url"
|
||||
:alt="img.alt || ''"
|
||||
loading="lazy"
|
||||
/>
|
||||
type="button"
|
||||
class="u-img-btn"
|
||||
:aria-label="t('filePreview.enlargeImage')"
|
||||
@click="emit('openMedia', userImageMedia(img))"
|
||||
>
|
||||
<AuthMedia
|
||||
:url="img.url"
|
||||
kind="image"
|
||||
:alt="img.alt"
|
||||
:file-id="img.fileId"
|
||||
media-class="u-img"
|
||||
/>
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
<!-- Skill activation card (replaces raw XML) -->
|
||||
|
|
@ -716,10 +730,16 @@ function isStreamingRenderBlock(turn: ChatTurn, block: { sourceIndex: number }):
|
|||
</span>
|
||||
</button>
|
||||
<div v-if="hasImages(item)" class="q-imgs">
|
||||
<template v-for="(att, ai) in item.attachments" :key="ai">
|
||||
<video v-if="att.kind === 'video'" class="q-img" :src="att.url" muted playsinline preload="metadata" />
|
||||
<img v-else class="q-img" :src="att.url" alt="" loading="lazy" />
|
||||
</template>
|
||||
<AuthMedia
|
||||
v-for="(att, ai) in item.attachments"
|
||||
:key="ai"
|
||||
:url="att.url"
|
||||
:kind="att.kind"
|
||||
:file-id="att.fileId"
|
||||
media-class="q-img"
|
||||
:controls="false"
|
||||
muted
|
||||
/>
|
||||
</div>
|
||||
<span v-if="qi === 0" class="q-tag q-tag-next">{{ t('composer.queueNext') }}</span>
|
||||
<span v-else class="q-tag q-tag-idx">#{{ qi + 1 }}</span>
|
||||
|
|
@ -1078,6 +1098,27 @@ function isStreamingRenderBlock(turn: ChatTurn, block: { sourceIndex: number }):
|
|||
border-radius: 8px;
|
||||
object-fit: cover;
|
||||
}
|
||||
/* Clickable image thumbnail — reset button chrome so it looks like the plain
|
||||
image it replaced, while still opening the preview on click. */
|
||||
.u-img-btn {
|
||||
display: block;
|
||||
flex: none;
|
||||
align-self: flex-start;
|
||||
max-width: 100%;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.u-img-btn .u-img {
|
||||
display: block;
|
||||
}
|
||||
.u-img-btn:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: var(--p-focus-ring);
|
||||
}
|
||||
|
||||
/* NOTE: Chat/bubble styles live in src/style.css (global). Scoped `.u-bub`
|
||||
rules here did NOT win the cascade, so they were moved to the global sheet. */
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ export default {
|
|||
binaryNoPreview: 'Binary file · {mime} · {size} bytes · preview unavailable',
|
||||
unknownType: 'unknown type',
|
||||
copyCode: 'Copy code',
|
||||
enlargeImage: 'Enlarge image',
|
||||
errors: {
|
||||
emptyPath: 'File path is empty',
|
||||
unsupportedPath: 'URLs and remote paths cannot be previewed',
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ export default {
|
|||
binaryNoPreview: '二进制文件 · {mime} · {size} 字节 · 暂不预览',
|
||||
unknownType: '未知类型',
|
||||
copyCode: '复制代码',
|
||||
enlargeImage: '放大图片',
|
||||
errors: {
|
||||
emptyPath: '文件路径为空',
|
||||
unsupportedPath: '不支持预览 URL 或远程路径',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue