Files
DemonGatanjieuandClaude Opus 5.5 ecbebc049d Dock the message box at the foot of the task like a chat composer
The message box stays at the bottom of the task column however the timeline scrolls. Over the
input, what the message carries (ui_ai_tray.js): pictures with ✎ to mark up (sent on their own)
and ×, chips for what "@" names; gallery pictures are attached, not typed in as "@name". Beside
it the gallery and materials buttons with their counts; the input grows (Enter sends,
Shift+Enter breaks the line) and pictures alone may be sent. "New steps below" appears when the
AI adds steps while the user reads further up; one's own message scrolls into view. Pictures a
message carries show as small image cards with the usual tools. The ready page keeps the big
gallery and what is ready; the box below takes its pictures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 21:11:10 +08:00

65 lines
2.4 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* What a message to the AI carries, over its input (ui_ai_message.js; the refs are
* ui_ai_mention.js's): the pictures as small pictures, each with ✎ to mark it up for the AI
* (ui_ai_marker.js, sent on its own) and × to leave it out, then what "@" names (models, combos,
* nodes…) as chips with ×. Nothing shows while nothing is attached.
*/
import { translate as t } from './locales.js';
import { icon } from './ui_ai_icons.js';
import { thumbUrl } from './ui_ai_gallery_pick.js';
function el(tag, className, text) {
const node = document.createElement(tag);
if (className) node.className = className;
if (text !== undefined) node.textContent = String(text);
return node;
}
function tool(className, name, label, run) {
const node = el('button', className);
node.type = 'button';
node.title = label;
node.setAttribute('aria-label', label);
node.append(icon(name, 12));
node.onclick = (event) => {
event.stopPropagation();
run();
};
return node;
}
function picture(ref, ctx) {
const tile = el('div', 'anomalous-ai-tray-picture');
tile.title = ref.id;
const img = el('img');
img.src = thumbUrl(ref.item.image || {});
img.alt = '';
img.onerror = () => tile.classList.add('is-gone');
tile.append(img, tool('anomalous-ai-tray-x', 'close', t('aiTrayRemove'), () => ctx.remove(ref)));
if (ctx.mark) {
const mark = el('button', 'anomalous-ai-tray-mark');
mark.type = 'button';
mark.title = t('aiTrayMarkHint');
mark.append(icon('pencil', 11), el('span', '', t('aiTrayMark')));
mark.onclick = () => ctx.mark(ref);
tile.append(mark);
}
return tile;
}
function chip(ref, ctx) {
const node = el('span', 'anomalous-ai-mention-chip');
node.append(ctx.lead(ref.item), el('span', 'anomalous-ai-mention-chip-name', ref.label),
tool('anomalous-ai-mention-chip-x', 'close', t('aiMentionRemove'), () => ctx.remove(ref)));
return node;
}
/** `refs` into `tray`. `ctx`: {remove(ref), mark(ref) (or none), lead(item) (a chip's small picture or icon)}. */
export function drawTray(tray, refs, ctx) {
const pictures = refs.filter(ref => ref.kind === 'pictures');
const others = refs.filter(ref => ref.kind !== 'pictures');
tray.hidden = !refs.length;
tray.replaceChildren(...pictures.map(ref => picture(ref, ctx)), ...others.map(ref => chip(ref, ctx)));
}