Fix GUI image bridge wiring
Some checks failed
Security Scan / security (push) Has been cancelled
Test / test (push) Has been cancelled

This commit is contained in:
Snider 2026-04-15 23:22:04 +01:00
parent e83d2559bb
commit dbe6fabc58
4 changed files with 51 additions and 4 deletions

View file

@ -3,6 +3,7 @@ package clipboard
import (
"context"
"encoding/base64"
core "dappco.re/go/core"
)
@ -36,7 +37,7 @@ func (s *Service) OnStartup(_ context.Context) core.Result {
if !ok {
return core.Result{Value: false, OK: true}
}
data, _ := opts.Get("data").Value.([]byte)
data := clipboardImageData(opts)
if len(data) == 0 || len(data) > MaxImageBytes {
return core.Result{Value: false, OK: true}
}
@ -92,3 +93,20 @@ func (s *Service) handleQuery(_ *core.Core, q core.Query) core.Result {
return core.Result{}
}
}
// clipboardImageData normalizes clipboard image inputs from MCP, preload bridge, and WS callers.
// Use: bytes := clipboardImageData(core.NewOptions(core.Option{Key: "data", Value: "iVBORw0KGgo..."}))
func clipboardImageData(opts core.Options) []byte {
if raw, ok := opts.Get("data").Value.([]byte); ok && len(raw) > 0 {
return append([]byte(nil), raw...)
}
encoded := opts.String("data")
if encoded == "" {
return nil
}
data, err := base64.StdEncoding.DecodeString(encoded)
if err != nil {
return nil
}
return data
}

View file

@ -545,6 +545,8 @@ func (s *Service) handleWSMessage(msg WSMessage) core.Result {
return c.Action("gui.chat.conversations.export").Run(ctx, wsOptions(msg.Data))
case "chat:attach-image":
return c.Action("gui.chat.attachImage").Run(ctx, wsOptions(msg.Data))
case "chat:attach-image-file":
return c.Action("gui.chat.attachImageFile").Run(ctx, wsOptions(msg.Data))
case "chat:remove-image":
return c.Action("gui.chat.removeImage").Run(ctx, wsOptions(msg.Data))
case "chat:thinking:start":

View file

@ -188,10 +188,13 @@ export class ChatStateService {
continue;
}
const attachment = await this.fileToAttachment(file);
await this.invoke('gui.chat.attachImage', {
const queued = await this.invoke('gui.chat.attachImage', {
conversation_id: this.activeConversation()?.id,
...attachment,
});
if (queued) {
this.upsertQueuedAttachment(queued);
}
}
}
@ -214,10 +217,13 @@ export class ChatStateService {
return;
}
for (const path of paths) {
await this.invokeGUI<ImageAttachment>('gui.chat.attachImageFile', {
const attachment = await this.invoke('gui.chat.attachImageFile', {
conversation_id: this.activeConversation()?.id,
path,
});
if (attachment) {
this.upsertQueuedAttachment(attachment);
}
}
}
@ -398,7 +404,7 @@ export class ChatStateService {
if (!data.attachment || conversationID !== activeID && !(conversationID === 'draft' && !this.activeConversation()?.messages.length)) {
return;
}
this.queuedAttachments.update((items) => [...items, data.attachment!]);
this.upsertQueuedAttachment(data.attachment);
});
}
@ -480,6 +486,23 @@ export class ChatStateService {
return model?.supports_vision ?? false;
}
private upsertQueuedAttachment(attachment: ImageAttachment): void {
this.queuedAttachments.update((items) => {
if (items.some((item) => this.sameAttachment(item, attachment))) {
return items;
}
return [...items, attachment];
});
}
private sameAttachment(left: ImageAttachment, right: ImageAttachment): boolean {
return left.filename === right.filename &&
left.mime_type === right.mime_type &&
left.data === right.data &&
left.width === right.width &&
left.height === right.height;
}
private async fileToAttachment(file: File): Promise<ImageAttachment> {
const data = await this.readFileAsDataURL(file);
const dimensions = await this.readImageDimensions(data);

View file

@ -42,6 +42,10 @@ export interface ChatRouteMap {
request: ({ conversation_id?: string } & ImageAttachment);
response: ImageAttachment;
};
'gui.chat.attachImageFile': {
request: { conversation_id?: string; path: string };
response: ImageAttachment;
};
'gui.chat.removeImage': {
request: { conversation_id?: string; index: number };
response: ImageAttachment;