mirror of
https://github.com/Twigpine/zero.git
synced 2026-11-11 18:00:54 -10:00
* tui: image-input UX — drag-drop attach, inline numbered chips, vision fallback, cursor + copy fixes Improves the image/PDF attachment flow and two long-standing composer papercuts. - Drag-and-drop: dropping an image/PDF onto the composer attaches it instead of erroring as an unknown "/…" command. The dropped path is detected on paste and on submit (absolute/escaped/quoted paths to an existing image or PDF). - Inline numbered chips: staged attachments render as compact [Image #1] / [Doc #1] chips INSIDE the input box; the verbose "Attached … (image/png)" system line is dropped. Backspace on an empty composer removes the last chip (/image clear still drops all). - Vision fallback: a real multimodal model absent from the curated catalog (custom / openai-compatible / local ids) now accepts images via a conservative name match; text-only ids stay refused, and the catalog stays authoritative. - Blinking composer cursor, including when the box is empty (previously no caret was drawn for a focused, empty box). - Ctrl+E releases/recaptures the mouse so native drag-select + copy works (mouse capture otherwise intercepts terminal text selection). Tests: dropped-path detection, vision-by-name + unknown-model fallback, mouse-release capture toggle, attachment removal, and updated chip-render tests. * fix(vision): a "vision-less" model name must not match the bare-vision fallback visionCapableByName treated any id containing "vision" as multimodal, so a model named "...vision-less..." (i.e. WITHOUT vision) was wrongly classified vision- capable — which silently skipped the non-vision image gate and broke TestRunExecDropsImagesOnNonVisionModelWithWarning in internal/cli. Add mentionsVision, which still matches the "vision" word but excludes the negated forms (vision-less / visionless / no-vision / non-vision). Used for both the grok-vision and the general open-multimodal fallback. Added negation cases to the name table. * fix(tui): drag-drop path detection corrupted Windows paths (backslash unescape) unescapeDroppedPath stripped backslashes to undo Unix terminal drag-drop escaping ("\ " -> " "), but on Windows the backslash is the path separator, so a plain dropped path like C:\Users\x\file.png became C:Usersxfile.png and never resolved -- TestDroppedAttachmentPath failed on windows-latest. No-op the unescape on Windows (dropped paths there arrive quoted or plain), and gate the Unix-only backslash-escaped positive case behind runtime.GOOS.
36 lines
1.2 KiB
Go
36 lines
1.2 KiB
Go
package tui
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/Gitlawb/zero/internal/zeroruntime"
|
|
)
|
|
|
|
func TestRemoveLastAttachment(t *testing.T) {
|
|
m := model{
|
|
pendingImages: []zeroruntime.ImageBlock{{MediaType: "image/png"}, {MediaType: "image/png"}},
|
|
pendingImageLabels: []string{"a.png", "b.png"},
|
|
pendingDocuments: []pendingDocument{{label: "spec.pdf"}},
|
|
}
|
|
|
|
// Documents render last, so a staged doc is removed first.
|
|
m, ok := m.removeLastAttachment()
|
|
if !ok || len(m.pendingDocuments) != 0 {
|
|
t.Fatalf("doc should be removed first: ok=%v docs=%d", ok, len(m.pendingDocuments))
|
|
}
|
|
// Then the last image (images + labels stay in lockstep).
|
|
m, ok = m.removeLastAttachment()
|
|
if !ok || len(m.pendingImages) != 1 || len(m.pendingImageLabels) != 1 || m.pendingImageLabels[0] != "a.png" {
|
|
t.Fatalf("last image should be removed: ok=%v imgs=%d labels=%v", ok, len(m.pendingImages), m.pendingImageLabels)
|
|
}
|
|
// Remove the final image.
|
|
m, ok = m.removeLastAttachment()
|
|
if !ok || len(m.pendingImages) != 0 || len(m.pendingImageLabels) != 0 {
|
|
t.Fatalf("all images should be removed: ok=%v imgs=%d", ok, len(m.pendingImages))
|
|
}
|
|
// Nothing left.
|
|
if _, ok := m.removeLastAttachment(); ok {
|
|
t.Fatal("removeLastAttachment on an empty set must report false")
|
|
}
|
|
}
|