Draw a picture in a chat: force_tool image_generate on POST /chats and /messages
ci / ui (push) Waiting to run
ci / check (push) Failing after 3s
ci / start (push) Failing after 3s

A switch among a chat's chips (not an agent chat's), offered where /me says
the server draws for the account, asks for the next message's reply to be a
picture; it goes off once the message is sent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
HomerandClaude Opus 5.5 committed 2026-11-10 14:31:02 +00:00
1 parent df27ca82fa
commit 2cdd4e5355
5 files changed
+110 -2

No files matched your search

+8
View File
@@ -377,6 +377,7 @@ const LEMBAS_I18N = {
"The previous command (Ctrl+Shift+↑)": "Předchozí příkaz (Ctrl+Shift+↑)",
"The release that goes with the server's version. This is %(current)s.": "Vydání, které patří k verzi serveru. Toto je %(current)s.",
"The reply is ready.": "Odpověď je hotová.",
"The reply to the next message is a picture drawn from it.": "Odpovědí na další zprávu bude obrázek nakreslený podle ní.",
"The result is no longer there.": "Výsledek už tu není.",
"The server refused this computer's link: %(why)s": "Server odmítl propojení tohoto počítače: %(why)s",
"The server's answer had no picture in it.": "V odpovědi serveru nebyl žádný obrázek.",
@@ -861,6 +862,7 @@ const LEMBAS_I18N = {
"The previous command (Ctrl+Shift+↑)": "Vorheriger Befehl (Ctrl+Shift+↑)",
"The release that goes with the server's version. This is %(current)s.": "Die Version, die zur Version des Servers passt. Dies ist %(current)s.",
"The reply is ready.": "Die Antwort ist fertig.",
"The reply to the next message is a picture drawn from it.": "Die Antwort auf die nächste Nachricht ist ein danach gezeichnetes Bild.",
"The result is no longer there.": "Das Ergebnis ist nicht mehr da.",
"The server refused this computer's link: %(why)s": "Der Server hat die Verknüpfung dieses Computers abgelehnt: %(why)s",
"The server's answer had no picture in it.": "Die Antwort des Servers enthielt kein Bild.",
@@ -1345,6 +1347,7 @@ const LEMBAS_I18N = {
"The previous command (Ctrl+Shift+↑)": "El comando anterior (Ctrl+Shift+↑)",
"The release that goes with the server's version. This is %(current)s.": "La versión que corresponde a la del servidor. Esta es %(current)s.",
"The reply is ready.": "La respuesta está lista.",
"The reply to the next message is a picture drawn from it.": "La respuesta al siguiente mensaje es una imagen dibujada a partir de él.",
"The result is no longer there.": "El resultado ya no está.",
"The server refused this computer's link: %(why)s": "El servidor rechazó la vinculación de este equipo: %(why)s",
"The server's answer had no picture in it.": "La respuesta del servidor no traía ninguna imagen.",
@@ -1829,6 +1832,7 @@ const LEMBAS_I18N = {
"The previous command (Ctrl+Shift+↑)": "La commande précédente (Ctrl+Shift+↑)",
"The release that goes with the server's version. This is %(current)s.": "La version qui va avec celle du serveur. Celle-ci est la %(current)s.",
"The reply is ready.": "La réponse est prête.",
"The reply to the next message is a picture drawn from it.": "La réponse au prochain message est une image dessinée d’après lui.",
"The result is no longer there.": "Le résultat n'est plus là.",
"The server refused this computer's link: %(why)s": "Le serveur a refusé la liaison de cet ordinateur : %(why)s",
"The server's answer had no picture in it.": "La réponse du serveur ne contenait aucune image.",
@@ -2313,6 +2317,7 @@ const LEMBAS_I18N = {
"The previous command (Ctrl+Shift+↑)": "Előző parancs (Ctrl+Shift+↑)",
"The release that goes with the server's version. This is %(current)s.": "A szerver verziójához tartozó kiadás. Ez a %(current)s.",
"The reply is ready.": "A válasz kész.",
"The reply to the next message is a picture drawn from it.": "A következő üzenetre adott válasz egy abból rajzolt kép lesz.",
"The result is no longer there.": "Az eredmény már nincs meg.",
"The server refused this computer's link: %(why)s": "A szerver elutasította ennek a számítógépnek a kapcsolatát: %(why)s",
"The server's answer had no picture in it.": "A szerver válaszában nem volt kép.",
@@ -2797,6 +2802,7 @@ const LEMBAS_I18N = {
"The previous command (Ctrl+Shift+↑)": "Il comando precedente (Ctrl+Shift+↑)",
"The release that goes with the server's version. This is %(current)s.": "La versione che va con quella del server. Questa è la %(current)s.",
"The reply is ready.": "La risposta è pronta.",
"The reply to the next message is a picture drawn from it.": "La risposta al prossimo messaggio è un’immagine disegnata a partire da esso.",
"The result is no longer there.": "Il risultato non c’è più.",
"The server refused this computer's link: %(why)s": "Il server ha rifiutato il collegamento di questo computer: %(why)s",
"The server's answer had no picture in it.": "La risposta del server non conteneva immagini.",
@@ -3281,6 +3287,7 @@ const LEMBAS_I18N = {
"The previous command (Ctrl+Shift+↑)": "Poprzednie polecenie (Ctrl+Shift+↑)",
"The release that goes with the server's version. This is %(current)s.": "Wydanie pasujące do wersji serwera. To jest %(current)s.",
"The reply is ready.": "Odpowiedź jest gotowa.",
"The reply to the next message is a picture drawn from it.": "Odpowiedzią na następną wiadomość będzie obraz narysowany na jej podstawie.",
"The result is no longer there.": "Wyniku już nie ma.",
"The server refused this computer's link: %(why)s": "Serwer odrzucił połączenie tego komputera: %(why)s",
"The server's answer had no picture in it.": "W odpowiedzi serwera nie było obrazu.",
@@ -3765,6 +3772,7 @@ const LEMBAS_I18N = {
"The previous command (Ctrl+Shift+↑)": "Predchádzajúci príkaz (Ctrl+Shift+↑)",
"The release that goes with the server's version. This is %(current)s.": "Vydanie, ktoré patrí k verzii servera. Toto je %(current)s.",
"The reply is ready.": "Odpoveď je hotová.",
"The reply to the next message is a picture drawn from it.": "Odpoveďou na ďalšiu správu bude obrázok nakreslený podľa nej.",
"The result is no longer there.": "Výsledok už tu nie je.",
"The server refused this computer's link: %(why)s": "Server odmietol prepojenie tohto počítača: %(why)s",
"The server's answer had no picture in it.": "V odpovedi servera nebol žiadny obrázok.",
+50 -2
View File
@@ -789,6 +789,47 @@ const MODE_WORDS = {
plan: () => t("Plan — changes nothing"),
}
// --- Draw a picture: the next message's reply is a picture (`force_tool: "image_generate"`) -------
let wantPicture = false // the next message, in a chat (not an agent chat), asks for a picture
/** Whether a chat here may be asked for a picture: the server draws for this account. */
function picturesOffered() {
const me = ctx.me()
return !!me?.services?.images && me?.permissions?.["tools.image"] !== false
}
function pictureChip() {
const b = chip({
ico: ICONS.picture,
label: t("Draw a picture"),
title: t("The reply to the next message is a picture drawn from it."),
menuish: false,
cls: "cchip--picture",
onclick: () => {
wantPicture = !wantPicture
b.setAttribute("aria-pressed", String(wantPicture))
$("composer-text").placeholder = wantPicture ? t("What to draw…") : t("Message")
$("composer-text").focus()
},
})
b.setAttribute("aria-pressed", String(wantPicture))
return b
}
/** The message's body, asking for a picture while the switch is on. */
function takePicture(body) {
return wantPicture ? { ...body, force_tool: "image_generate" } : body
}
/** The message went: the switch goes off with it. */
function pictureSent() {
if (!wantPicture) return
wantPicture = false
$("composer-text").placeholder = t("Message")
document.querySelector(".cchip--picture")?.setAttribute("aria-pressed", "false")
}
function modeChip(value, onChange, disabled) {
return chip({
ico: ICONS.shield,
@@ -953,6 +994,8 @@ async function renderChips() {
renderChips()
}, mayPick))
if (model) parts.push(effortChip(model, draft.effort, (l) => ((draft.effort = l), renderChips())))
if (draft.kind === "chat" && picturesOffered()) parts.push(pictureChip())
else wantPicture = false
if (draft.kind === "chat" && teamPossible(model)) parts.push(agentsChip(draft.agents, (a) => ((draft.agents = a), renderChips()), { cap: subagentsCap(), own: namedOnly(model?.subagents) }))
// An agent chat's subagents run on its device: offered as the server says for that device.
const device = draft.kind === "agent" ? draftDevice() : ""
@@ -998,6 +1041,8 @@ async function renderChips() {
if (chat.kind === "agent") parts.push(modeChip(chat.mode || "edit", (m) => save({ mode: m })))
if (chat.model) parts.push(modelChip(models, chat.model, (id) => save({ model: id }), mayPick))
parts.push(effortChip(current, chat.effort || levelsOf(current).start, (l) => save({ effort: l })))
if (chat.kind === "chat" && picturesOffered()) parts.push(pictureChip())
else wantPicture = false
box.replaceChildren(...parts.filter(Boolean))
const offer = await teamOffered(chat)
if (!offer || drawing !== chipsDrawn || open?.chat?.id !== chat.id) return
@@ -1368,7 +1413,8 @@ async function createChat(text, attachments) {
}
Object.assign(body, { kind: "agent", device, directory: draft.dir, mode: draft.mode || "edit" })
}
const r = await post("/chats", body)
const r = await post("/chats", draft.kind === "chat" ? takePicture(body) : body)
pictureSent()
draft = null
$("new-chat").hidden = true
$("main").dataset.new = "false"
@@ -1405,9 +1451,11 @@ async function submit() {
try {
if (draft) await createChat(text, files)
else if (open?.chat?.id) {
const body = { text }
let body = { text }
if (files.length) body.attachments = files
if (open.chat.kind === "chat") body = takePicture(body)
const r = await post(`/chats/${encodeURIComponent(open.chat.id)}/messages`, body)
pictureSent()
if (r.message) $("messages").append(userMessage(r.message))
if (r.reply) follower(open.chat, r.reply.id)
} else return