mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-08-27 09:34:43 +00:00
fix(web): show an image placeholder for image-only queued prompts
A prompt queued while the agent is busy can carry image attachments
with no text; the queue strip rendered its bare text — an empty string,
so the row was just a blank button next to a remove cross. Queue items
now expose {text, attachmentCount}: image-only prompts render an
'image ×N' placeholder with a small badge, and any item carrying
images disables the load-back-into-input edit action (the uploaded
files can't be restored to the composer; editing would silently drop
them — remove stays available). Verified with component render tests
for the three shapes (image-only / text-only / text+images).
This commit is contained in:
parent
d992719560
commit
d65b499084
6 changed files with 49 additions and 14 deletions
|
|
@ -7,7 +7,7 @@ import MentionMenu from './MentionMenu.vue';
|
|||
import type { SlashCommand } from '../lib/slashCommands';
|
||||
import { filterCommands, parseSlash } from '../lib/slashCommands';
|
||||
import type { FileItem } from './MentionMenu.vue';
|
||||
import type { ConversationStatus, PermissionMode } from '../types';
|
||||
import type { ConversationStatus, PermissionMode, QueuedPromptView } from '../types';
|
||||
import type { AppModel, ThinkingLevel } from '../api/types';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -35,7 +35,7 @@ interface Attachment {
|
|||
|
||||
const props = withDefaults(defineProps<{
|
||||
running?: boolean;
|
||||
queued?: string[];
|
||||
queued?: QueuedPromptView[];
|
||||
searchFiles?: (q: string) => Promise<FileItem[]>;
|
||||
/** If undefined, attach button is hidden and paste/drag are no-ops. */
|
||||
uploadImage?: (file: Blob, name?: string) => Promise<{ fileId: string; name: string; mediaType: string } | null>;
|
||||
|
|
@ -592,7 +592,10 @@ function selectModel(modelId: string): void {
|
|||
@dragleave="handleDragLeave"
|
||||
@drop="handleDrop"
|
||||
>
|
||||
<!-- Queued message strip -->
|
||||
<!-- Queued message strip. Items carrying image attachments can't be
|
||||
edited (the uploaded files can't be loaded back into the input);
|
||||
they show an image badge and an "image ×N" placeholder when the
|
||||
prompt has no text. -->
|
||||
<div v-if="queued && queued.length > 0" class="queue-strip">
|
||||
<span class="queue-label">{{ t('composer.queueLabel') }}</span>
|
||||
<div
|
||||
|
|
@ -603,9 +606,13 @@ function selectModel(modelId: string): void {
|
|||
<button
|
||||
class="queue-text"
|
||||
type="button"
|
||||
:title="t('composer.editQueued')"
|
||||
@click="editQueued(i, msg)"
|
||||
>{{ msg }}</button>
|
||||
:disabled="msg.attachmentCount > 0"
|
||||
:title="msg.attachmentCount > 0 ? t('composer.queuedHasImage', { n: msg.attachmentCount }) : t('composer.editQueued')"
|
||||
@click="msg.attachmentCount === 0 && editQueued(i, msg.text)"
|
||||
>
|
||||
<svg v-if="msg.attachmentCount > 0" class="queue-img" viewBox="0 0 16 16" width="11" height="11" fill="none" stroke="currentColor" stroke-width="1.4" aria-hidden="true"><rect x="1.5" y="2.5" width="13" height="11" rx="1.5"/><circle cx="5.5" cy="6.5" r="1.2"/><path d="M2.5 12l3.5-3.5 2.5 2.5 3-3 2 2"/></svg>
|
||||
<span class="queue-text-inner" :class="{ placeholder: !msg.text }">{{ msg.text || t('composer.queuedImageOnly', { n: msg.attachmentCount }) }}</span>
|
||||
</button>
|
||||
<button class="queue-rm" :title="t('composer.remove')" @click="emit('unqueue', i)">
|
||||
<svg viewBox="0 0 12 12" width="10" height="10" fill="none" stroke="currentColor" stroke-width="1.5" xmlns="http://www.w3.org/2000/svg"><line x1="2" y1="2" x2="10" y2="10"/><line x1="10" y1="2" x2="2" y2="10"/></svg>
|
||||
</button>
|
||||
|
|
@ -896,9 +903,10 @@ function selectModel(modelId: string): void {
|
|||
}
|
||||
|
||||
.queue-text {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
|
|
@ -910,9 +918,19 @@ function selectModel(modelId: string): void {
|
|||
max-width: 168px;
|
||||
text-align: left;
|
||||
}
|
||||
.queue-text:hover {
|
||||
.queue-text:hover:not(:disabled) {
|
||||
color: var(--blue);
|
||||
}
|
||||
.queue-text:disabled {
|
||||
cursor: default;
|
||||
}
|
||||
.queue-img { flex: none; color: var(--muted); }
|
||||
.queue-text-inner {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.queue-text-inner.placeholder { color: var(--muted); }
|
||||
|
||||
.queue-rm {
|
||||
display: flex;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import type { ActivityState, ApprovalBlock, ChatTurn, ConnectionState, ContentAlign, ConversationStatus, DiffViewLine, PaneKey, PermissionMode, TaskItem, UIQuestion } from '../types';
|
||||
import type { ActivityState, ApprovalBlock, ChatTurn, ConnectionState, ContentAlign, ConversationStatus, DiffViewLine, PaneKey, PermissionMode, QueuedPromptView, TaskItem, UIQuestion } from '../types';
|
||||
import type { AppModel, ApprovalDecision, FsEntry, QuestionResponse, ThinkingLevel } from '../api/types';
|
||||
import type { FileItem } from './MentionMenu.vue';
|
||||
import type { FileData } from './FilePreview.vue';
|
||||
|
|
@ -33,7 +33,7 @@ const props = defineProps<{
|
|||
planMode?: boolean;
|
||||
questions?: UIQuestion[];
|
||||
running?: boolean;
|
||||
queued?: string[];
|
||||
queued?: QueuedPromptView[];
|
||||
searchFiles?: (q: string) => Promise<FileItem[]>;
|
||||
uploadImage?: (file: Blob, name?: string) => Promise<{ fileId: string; name: string; mediaType: string } | null>;
|
||||
connection?: ConnectionState;
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import type {
|
|||
DiffLine,
|
||||
DiffViewLine,
|
||||
PermissionMode,
|
||||
QueuedPromptView,
|
||||
Session,
|
||||
TaskItem,
|
||||
TaskState,
|
||||
|
|
@ -818,11 +819,16 @@ const permission = computed<PermissionMode>(() => rawState.permission);
|
|||
const thinking = computed<ThinkingLevel>(() => rawState.thinking);
|
||||
const planMode = computed<boolean>(() => rawState.planMode);
|
||||
|
||||
/** Queued messages for the active session (display texts). */
|
||||
const queued = computed<string[]>(() => {
|
||||
/** Queued messages for the active session (text + attachment count for the
|
||||
composer strip — an image-only prompt would otherwise render as an empty
|
||||
string). */
|
||||
const queued = computed<QueuedPromptView[]>(() => {
|
||||
const sid = rawState.activeSessionId;
|
||||
if (!sid) return [];
|
||||
return (rawState.queuedBySession[sid] ?? []).map((q) => q.text);
|
||||
return (rawState.queuedBySession[sid] ?? []).map((q) => ({
|
||||
text: q.text,
|
||||
attachmentCount: q.attachments?.length ?? 0,
|
||||
}));
|
||||
});
|
||||
|
||||
/** Pending warnings list */
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ export default {
|
|||
queue: 'Queue',
|
||||
queueLabel: 'Queue',
|
||||
editQueued: 'Edit (load back into the input)',
|
||||
queuedImageOnly: 'image ×{n}',
|
||||
queuedHasImage: 'Contains {n} image(s) — remove only, not editable',
|
||||
remove: 'Remove',
|
||||
removeNamed: 'Remove {name}',
|
||||
uploading: 'Uploading',
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ export default {
|
|||
queue: '排队',
|
||||
queueLabel: '队列',
|
||||
editQueued: '编辑(载入到输入框)',
|
||||
queuedImageOnly: '图片 ×{n}',
|
||||
queuedHasImage: '包含 {n} 张图片 — 只能移除,不能编辑',
|
||||
remove: '移除',
|
||||
removeNamed: '移除 {name}',
|
||||
uploading: '上传中',
|
||||
|
|
|
|||
|
|
@ -149,6 +149,13 @@ export interface ConversationStatus {
|
|||
// preview for unchanged ones). 'diff' is gone; 'files' is the merged key.
|
||||
export type PaneKey = 'chat' | 'files' | 'tasks';
|
||||
|
||||
/** A queued prompt as shown in the composer's queue strip. */
|
||||
export interface QueuedPromptView {
|
||||
text: string;
|
||||
/** Number of image attachments waiting with this prompt. */
|
||||
attachmentCount: number;
|
||||
}
|
||||
|
||||
/** Horizontal alignment of the conversation reading column within the pane. */
|
||||
export type ContentAlign = 'left' | 'center';
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue