mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-21 16:01:05 +00:00
Add defaultConvertToLlm to web-ui, simplify example
- web-ui exports: defaultConvertToLlm, convertAttachments, isUserMessageWithAttachments, isArtifactMessage - defaultConvertToLlm handles UserMessageWithAttachments and filters ArtifactMessage - Example's customMessageTransformer now extends defaultConvertToLlm - Removes duplicated attachment conversion logic from example
This commit is contained in:
parent
7a39f9eb11
commit
13a1991ec2
6 changed files with 127 additions and 76 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
import { Alert } from "@mariozechner/mini-lit/dist/Alert.js";
|
import { Alert } from "@mariozechner/mini-lit/dist/Alert.js";
|
||||||
import type { ImageContent, Message, TextContent } from "@mariozechner/pi-ai";
|
import type { Message } from "@mariozechner/pi-ai";
|
||||||
import type { AgentMessage, Attachment, MessageRenderer, UserMessageWithAttachments } from "@mariozechner/pi-web-ui";
|
import type { AgentMessage, MessageRenderer } from "@mariozechner/pi-web-ui";
|
||||||
import { registerMessageRenderer } from "@mariozechner/pi-web-ui";
|
import { defaultConvertToLlm, registerMessageRenderer } from "@mariozechner/pi-web-ui";
|
||||||
import { html } from "lit";
|
import { html } from "lit";
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
@ -75,72 +75,25 @@ export function createSystemNotification(
|
||||||
// 5. CUSTOM MESSAGE TRANSFORMER
|
// 5. CUSTOM MESSAGE TRANSFORMER
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
// Convert attachments to content blocks
|
/**
|
||||||
function convertAttachments(attachments: Attachment[]): (TextContent | ImageContent)[] {
|
* Custom message transformer that extends defaultConvertToLlm.
|
||||||
const content: (TextContent | ImageContent)[] = [];
|
* Handles system-notification messages by converting them to user messages.
|
||||||
for (const attachment of attachments) {
|
*/
|
||||||
if (attachment.type === "image") {
|
|
||||||
content.push({
|
|
||||||
type: "image",
|
|
||||||
data: attachment.content,
|
|
||||||
mimeType: attachment.mimeType,
|
|
||||||
} as ImageContent);
|
|
||||||
} else if (attachment.type === "document" && attachment.extractedText) {
|
|
||||||
content.push({
|
|
||||||
type: "text",
|
|
||||||
text: `\n\n[Document: ${attachment.fileName}]\n${attachment.extractedText}`,
|
|
||||||
} as TextContent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transform custom messages to LLM-compatible messages
|
|
||||||
export function customMessageTransformer(messages: AgentMessage[]): Message[] {
|
export function customMessageTransformer(messages: AgentMessage[]): Message[] {
|
||||||
return messages
|
// First, handle our custom system-notification type
|
||||||
.filter((m) => {
|
const processed = messages.map((m): AgentMessage => {
|
||||||
// Filter out artifact messages - they're for session reconstruction only
|
if (m.role === "system-notification") {
|
||||||
if (m.role === "artifact") {
|
const notification = m as SystemNotificationMessage;
|
||||||
return false;
|
// Convert to user message with <system> tags
|
||||||
}
|
return {
|
||||||
|
role: "user",
|
||||||
|
content: `<system>${notification.message}</system>`,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return m;
|
||||||
|
});
|
||||||
|
|
||||||
// Keep LLM-compatible messages + custom messages
|
// Then use defaultConvertToLlm for standard handling
|
||||||
return (
|
return defaultConvertToLlm(processed);
|
||||||
m.role === "user" ||
|
|
||||||
m.role === "user-with-attachments" ||
|
|
||||||
m.role === "assistant" ||
|
|
||||||
m.role === "toolResult" ||
|
|
||||||
m.role === "system-notification"
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.map((m) => {
|
|
||||||
// Transform system notifications to user messages
|
|
||||||
if (m.role === "system-notification") {
|
|
||||||
const notification = m as SystemNotificationMessage;
|
|
||||||
return {
|
|
||||||
role: "user",
|
|
||||||
content: `<system>${notification.message}</system>`,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
} as Message;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert user-with-attachments to user message with content blocks
|
|
||||||
if (m.role === "user-with-attachments") {
|
|
||||||
const msg = m as UserMessageWithAttachments;
|
|
||||||
const textContent: (TextContent | ImageContent)[] =
|
|
||||||
typeof msg.content === "string" ? [{ type: "text", text: msg.content }] : [...msg.content];
|
|
||||||
|
|
||||||
if (msg.attachments) {
|
|
||||||
textContent.push(...convertAttachments(msg.attachments));
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
role: "user",
|
|
||||||
content: textContent,
|
|
||||||
timestamp: msg.timestamp,
|
|
||||||
} as Message;
|
|
||||||
}
|
|
||||||
|
|
||||||
return m as Message;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -74,8 +74,8 @@ let chatPanel: ChatPanel;
|
||||||
let agentUnsubscribe: (() => void) | undefined;
|
let agentUnsubscribe: (() => void) | undefined;
|
||||||
|
|
||||||
const generateTitle = (messages: AgentMessage[]): string => {
|
const generateTitle = (messages: AgentMessage[]): string => {
|
||||||
const firstUserMsg = messages.find((m) => m.role === "user");
|
const firstUserMsg = messages.find((m) => m.role === "user" || m.role === "user-with-attachments");
|
||||||
if (!firstUserMsg || firstUserMsg.role !== "user") return "";
|
if (!firstUserMsg || (firstUserMsg.role !== "user" && firstUserMsg.role !== "user-with-attachments")) return "";
|
||||||
|
|
||||||
let text = "";
|
let text = "";
|
||||||
const content = firstUserMsg.content;
|
const content = firstUserMsg.content;
|
||||||
|
|
@ -98,7 +98,7 @@ const generateTitle = (messages: AgentMessage[]): string => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const shouldSaveSession = (messages: AgentMessage[]): boolean => {
|
const shouldSaveSession = (messages: AgentMessage[]): boolean => {
|
||||||
const hasUserMsg = messages.some((m: any) => m.role === "user");
|
const hasUserMsg = messages.some((m: any) => m.role === "user" || m.role === "user-with-attachments");
|
||||||
const hasAssistantMsg = messages.some((m: any) => m.role === "assistant");
|
const hasAssistantMsg = messages.some((m: any) => m.role === "assistant");
|
||||||
return hasUserMsg && hasAssistantMsg;
|
return hasUserMsg && hasAssistantMsg;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ export class MessageList extends LitElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fall back to built-in renderers
|
// Fall back to built-in renderers
|
||||||
if (msg.role === "user") {
|
if (msg.role === "user" || msg.role === "user-with-attachments") {
|
||||||
items.push({
|
items.push({
|
||||||
key: `msg:${index}`,
|
key: `msg:${index}`,
|
||||||
template: html`<user-message .message=${msg}></user-message>`,
|
template: html`<user-message .message=${msg}></user-message>`,
|
||||||
|
|
|
||||||
|
|
@ -285,3 +285,93 @@ export class AbortedMessage extends LitElement {
|
||||||
return html`<span class="text-sm text-destructive italic">${i18n("Request aborted")}</span>`;
|
return html`<span class="text-sm text-destructive italic">${i18n("Request aborted")}</span>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Default Message Transformer
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
import type { AgentMessage } from "@mariozechner/pi-agent-core";
|
||||||
|
import type { Message } from "@mariozechner/pi-ai";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert attachments to content blocks for LLM.
|
||||||
|
* - Images become ImageContent blocks
|
||||||
|
* - Documents with extractedText become TextContent blocks with filename header
|
||||||
|
*/
|
||||||
|
export function convertAttachments(attachments: Attachment[]): (TextContent | ImageContent)[] {
|
||||||
|
const content: (TextContent | ImageContent)[] = [];
|
||||||
|
for (const attachment of attachments) {
|
||||||
|
if (attachment.type === "image") {
|
||||||
|
content.push({
|
||||||
|
type: "image",
|
||||||
|
data: attachment.content,
|
||||||
|
mimeType: attachment.mimeType,
|
||||||
|
} as ImageContent);
|
||||||
|
} else if (attachment.type === "document" && attachment.extractedText) {
|
||||||
|
content.push({
|
||||||
|
type: "text",
|
||||||
|
text: `\n\n[Document: ${attachment.fileName}]\n${attachment.extractedText}`,
|
||||||
|
} as TextContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a message is a UserMessageWithAttachments.
|
||||||
|
*/
|
||||||
|
export function isUserMessageWithAttachments(msg: AgentMessage): msg is UserMessageWithAttachments {
|
||||||
|
return (msg as UserMessageWithAttachments).role === "user-with-attachments";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a message is an ArtifactMessage.
|
||||||
|
*/
|
||||||
|
export function isArtifactMessage(msg: AgentMessage): msg is ArtifactMessage {
|
||||||
|
return (msg as ArtifactMessage).role === "artifact";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default convertToLlm for web-ui apps.
|
||||||
|
*
|
||||||
|
* Handles:
|
||||||
|
* - UserMessageWithAttachments: converts to user message with content blocks
|
||||||
|
* - ArtifactMessage: filtered out (UI-only, for session reconstruction)
|
||||||
|
* - Standard LLM messages (user, assistant, toolResult): passed through
|
||||||
|
*/
|
||||||
|
export function defaultConvertToLlm(messages: AgentMessage[]): Message[] {
|
||||||
|
return messages
|
||||||
|
.filter((m) => {
|
||||||
|
// Filter out artifact messages - they're for session reconstruction only
|
||||||
|
if (isArtifactMessage(m)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.map((m): Message | null => {
|
||||||
|
// Convert user-with-attachments to user message with content blocks
|
||||||
|
if (isUserMessageWithAttachments(m)) {
|
||||||
|
const textContent: (TextContent | ImageContent)[] =
|
||||||
|
typeof m.content === "string" ? [{ type: "text", text: m.content }] : [...m.content];
|
||||||
|
|
||||||
|
if (m.attachments) {
|
||||||
|
textContent.push(...convertAttachments(m.attachments));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
role: "user",
|
||||||
|
content: textContent,
|
||||||
|
timestamp: m.timestamp,
|
||||||
|
} as Message;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass through standard LLM roles
|
||||||
|
if (m.role === "user" || m.role === "assistant" || m.role === "toolResult") {
|
||||||
|
return m as Message;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter out unknown message types
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.filter((m): m is Message => m !== null);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ export class StreamingMessageContainer extends LitElement {
|
||||||
if (msg.role === "toolResult") {
|
if (msg.role === "toolResult") {
|
||||||
// Skip standalone tool result in streaming; the stable list will render paired tool-message
|
// Skip standalone tool result in streaming; the stable list will render paired tool-message
|
||||||
return html``;
|
return html``;
|
||||||
} else if (msg.role === "user") {
|
} else if (msg.role === "user" || msg.role === "user-with-attachments") {
|
||||||
// Skip standalone tool result in streaming; the stable list will render it immediiately
|
// Skip standalone tool result in streaming; the stable list will render it immediiately
|
||||||
return html``;
|
return html``;
|
||||||
} else if (msg.role === "assistant") {
|
} else if (msg.role === "assistant") {
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,16 @@ export { Input } from "./components/Input.js";
|
||||||
export { MessageEditor } from "./components/MessageEditor.js";
|
export { MessageEditor } from "./components/MessageEditor.js";
|
||||||
export { MessageList } from "./components/MessageList.js";
|
export { MessageList } from "./components/MessageList.js";
|
||||||
// Message components
|
// Message components
|
||||||
export type { UserMessageWithAttachments } from "./components/Messages.js";
|
export type { ArtifactMessage, UserMessageWithAttachments } from "./components/Messages.js";
|
||||||
export { AssistantMessage, ToolMessage, UserMessage } from "./components/Messages.js";
|
export {
|
||||||
|
AssistantMessage,
|
||||||
|
convertAttachments,
|
||||||
|
defaultConvertToLlm,
|
||||||
|
isArtifactMessage,
|
||||||
|
isUserMessageWithAttachments,
|
||||||
|
ToolMessage,
|
||||||
|
UserMessage,
|
||||||
|
} from "./components/Messages.js";
|
||||||
// Message renderer registry
|
// Message renderer registry
|
||||||
export {
|
export {
|
||||||
getMessageRenderer,
|
getMessageRenderer,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue