diff --git a/tools/ui/src/lib/constants/context-keys.constants.ts b/tools/ui/src/lib/constants/context-keys.constants.ts
index 0bd733b37..62ff5413e 100644
--- a/tools/ui/src/lib/constants/context-keys.constants.ts
+++ b/tools/ui/src/lib/constants/context-keys.constants.ts
@@ -1,3 +1,3 @@
-export const CONTEXT_KEY_MESSAGE_EDIT = 'chat-message-edit';
-export const CONTEXT_KEY_CHAT_ACTIONS = 'chat-actions';
-export const CONTEXT_KEY_CHAT_SETTINGS_CONFIG = 'chat-settings-config';
+export const CONTEXT_KEY_CHAT_MESSAGE_EDIT = 'chat-message-edit';
+export const CONTEXT_KEY_CHAT_MESSAGE_ACTIONS = 'chat-message-actions';
+export const CONTEXT_KEY_CHAT_FORM_ACTIONS = 'chat-form-actions';
diff --git a/tools/ui/src/lib/contexts/chat-actions.context.ts b/tools/ui/src/lib/contexts/chat-actions.context.ts
deleted file mode 100644
index dffb5f3a3..000000000
--- a/tools/ui/src/lib/contexts/chat-actions.context.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import { CONTEXT_KEY_CHAT_ACTIONS } from '$lib/constants';
-import { getContext, setContext } from 'svelte';
-
-export interface ChatActionsContext {
- copy: (message: DatabaseMessage) => void;
- delete: (message: DatabaseMessage) => void;
- navigateToSibling: (siblingId: string) => void;
- editWithBranching: (
- message: DatabaseMessage,
- newContent: string,
- newExtras?: DatabaseMessageExtra[]
- ) => void;
- editWithReplacement: (
- message: DatabaseMessage,
- newContent: string,
- shouldBranch: boolean
- ) => void;
- editUserMessagePreserveResponses: (
- message: DatabaseMessage,
- newContent: string,
- newExtras?: DatabaseMessageExtra[]
- ) => void;
- regenerateWithBranching: (message: DatabaseMessage, modelOverride?: string) => void;
- continueAssistantMessage: (message: DatabaseMessage) => void;
- forkConversation: (
- message: DatabaseMessage,
- options: { name: string; includeAttachments: boolean }
- ) => void;
-}
-
-const CHAT_ACTIONS_KEY = Symbol.for(CONTEXT_KEY_CHAT_ACTIONS);
-
-export function setChatActionsContext(ctx: ChatActionsContext): ChatActionsContext {
- return setContext(CHAT_ACTIONS_KEY, ctx);
-}
-
-export function getChatActionsContext(): ChatActionsContext {
- return getContext(CHAT_ACTIONS_KEY);
-}
diff --git a/tools/ui/src/lib/contexts/chat-form-actions.context.ts b/tools/ui/src/lib/contexts/chat-form-actions.context.ts
new file mode 100644
index 000000000..a49f17447
--- /dev/null
+++ b/tools/ui/src/lib/contexts/chat-form-actions.context.ts
@@ -0,0 +1,19 @@
+import { CONTEXT_KEY_CHAT_FORM_ACTIONS } from '$lib/constants';
+import type { ChatFormActionsContext } from '$lib/types';
+import { getContext, setContext } from 'svelte';
+
+const CHAT_FORM_ACTIONS_KEY = Symbol.for(CONTEXT_KEY_CHAT_FORM_ACTIONS);
+
+/**
+ * Sets the chat form actions context. Call in the parent component (ChatFormActions.svelte).
+ */
+export function setChatFormActionsContext(ctx: ChatFormActionsContext): ChatFormActionsContext {
+ return setContext(CHAT_FORM_ACTIONS_KEY, ctx);
+}
+
+/**
+ * Gets the chat form actions context. Call in child components.
+ */
+export function getChatFormActionsContext(): ChatFormActionsContext {
+ return getContext(CHAT_FORM_ACTIONS_KEY);
+}
diff --git a/tools/ui/src/lib/contexts/chat-message-actions.context.ts b/tools/ui/src/lib/contexts/chat-message-actions.context.ts
new file mode 100644
index 000000000..fb075b3b0
--- /dev/null
+++ b/tools/ui/src/lib/contexts/chat-message-actions.context.ts
@@ -0,0 +1,21 @@
+import { CONTEXT_KEY_CHAT_MESSAGE_ACTIONS } from '$lib/constants';
+import type { ChatMessageActionsContext } from '$lib/types';
+import { getContext, setContext } from 'svelte';
+
+const CHAT_MESSAGE_ACTIONS_KEY = Symbol.for(CONTEXT_KEY_CHAT_MESSAGE_ACTIONS);
+
+/**
+ * Sets the per-message actions context. Call this in the parent component (ChatMessage.svelte).
+ */
+export function setChatMessageActionsContext(
+ ctx: ChatMessageActionsContext
+): ChatMessageActionsContext {
+ return setContext(CHAT_MESSAGE_ACTIONS_KEY, ctx);
+}
+
+/**
+ * Gets the per-message actions context. Call this in child components.
+ */
+export function getChatMessageActionsContext(): ChatMessageActionsContext {
+ return getContext(CHAT_MESSAGE_ACTIONS_KEY);
+}
diff --git a/tools/ui/src/lib/contexts/chat-message-edit.context.ts b/tools/ui/src/lib/contexts/chat-message-edit.context.ts
new file mode 100644
index 000000000..e9c053036
--- /dev/null
+++ b/tools/ui/src/lib/contexts/chat-message-edit.context.ts
@@ -0,0 +1,19 @@
+import { CONTEXT_KEY_CHAT_MESSAGE_EDIT } from '$lib/constants';
+import type { ChatMessageEditContext } from '$lib/types';
+import { getContext, setContext } from 'svelte';
+
+const CHAT_MESSAGE_EDIT_KEY = Symbol.for(CONTEXT_KEY_CHAT_MESSAGE_EDIT);
+
+/**
+ * Sets the message edit context. Call this in the parent component (ChatMessage.svelte).
+ */
+export function setChatMessageEditContext(ctx: ChatMessageEditContext): ChatMessageEditContext {
+ return setContext(CHAT_MESSAGE_EDIT_KEY, ctx);
+}
+
+/**
+ * Gets the message edit context. Call this in child components.
+ */
+export function getChatMessageEditContext(): ChatMessageEditContext {
+ return getContext(CHAT_MESSAGE_EDIT_KEY);
+}
diff --git a/tools/ui/src/lib/contexts/chat-settings-config.context.ts b/tools/ui/src/lib/contexts/chat-settings-config.context.ts
deleted file mode 100644
index a90f709b4..000000000
--- a/tools/ui/src/lib/contexts/chat-settings-config.context.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { CONTEXT_KEY_CHAT_SETTINGS_CONFIG } from '$lib/constants';
-import { getContext, setContext } from 'svelte';
-
-export interface ChatSettingsConfigContext {
- readonly localConfig: SettingsConfigType;
- handleConfigChange: (key: string, value: string | boolean) => void;
- handleThemeChange: (theme: string) => void;
-}
-
-const CHAT_SETTINGS_CONFIG_KEY = Symbol.for(CONTEXT_KEY_CHAT_SETTINGS_CONFIG);
-
-export function setChatSettingsConfigContext(
- ctx: ChatSettingsConfigContext
-): ChatSettingsConfigContext {
- return setContext(CHAT_SETTINGS_CONFIG_KEY, ctx);
-}
-
-export function getChatSettingsConfigContext(): ChatSettingsConfigContext {
- return getContext(CHAT_SETTINGS_CONFIG_KEY);
-}
diff --git a/tools/ui/src/lib/contexts/index.ts b/tools/ui/src/lib/contexts/index.ts
index c6719fa9e..4aaec8148 100644
--- a/tools/ui/src/lib/contexts/index.ts
+++ b/tools/ui/src/lib/contexts/index.ts
@@ -1,19 +1,8 @@
-export {
- getMessageEditContext,
- setMessageEditContext,
- type MessageEditContext,
- type MessageEditState,
- type MessageEditActions
-} from './message-edit.context';
+export { getChatMessageEditContext, setChatMessageEditContext } from './chat-message-edit.context';
export {
- getChatActionsContext,
- setChatActionsContext,
- type ChatActionsContext
-} from './chat-actions.context';
+ getChatMessageActionsContext,
+ setChatMessageActionsContext
+} from './chat-message-actions.context';
-export {
- getChatSettingsConfigContext,
- setChatSettingsConfigContext,
- type ChatSettingsConfigContext
-} from './chat-settings-config.context';
+export { getChatFormActionsContext, setChatFormActionsContext } from './chat-form-actions.context';
diff --git a/tools/ui/src/lib/contexts/message-edit.context.ts b/tools/ui/src/lib/contexts/message-edit.context.ts
deleted file mode 100644
index 80f3e6eee..000000000
--- a/tools/ui/src/lib/contexts/message-edit.context.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import { CONTEXT_KEY_MESSAGE_EDIT } from '$lib/constants';
-import { MessageRole } from '$lib/enums';
-import { getContext, setContext } from 'svelte';
-
-export interface MessageEditState {
- readonly isEditing: boolean;
- readonly editedContent: string;
- readonly editedExtras: DatabaseMessageExtra[];
- readonly editedUploadedFiles: ChatUploadedFile[];
- readonly originalContent: string;
- readonly originalExtras: DatabaseMessageExtra[];
- readonly showSaveOnlyOption: boolean;
- readonly showBranchAfterEditOption: boolean;
- readonly shouldBranchAfterEdit: boolean;
- readonly messageRole: MessageRole;
- readonly rawEditContent?: string;
-}
-
-export interface MessageEditActions {
- setContent: (content: string) => void;
- setExtras: (extras: DatabaseMessageExtra[]) => void;
- setUploadedFiles: (files: ChatUploadedFile[]) => void;
- save: () => void;
- saveOnly: () => void;
- cancel: () => void;
- startEdit: () => void;
-}
-
-export interface AssistantEditActions {
- setShouldBranchAfterEdit: (value: boolean) => void;
-}
-
-export type MessageEditContext = MessageEditState &
- MessageEditActions &
- Partial
;
-
-const MESSAGE_EDIT_KEY = Symbol.for(CONTEXT_KEY_MESSAGE_EDIT);
-
-/**
- * Sets the message edit context. Call this in the parent component (ChatMessage.svelte).
- */
-export function setMessageEditContext(ctx: MessageEditContext): MessageEditContext {
- return setContext(MESSAGE_EDIT_KEY, ctx);
-}
-
-/**
- * Gets the message edit context. Call this in child components.
- */
-export function getMessageEditContext(): MessageEditContext {
- return getContext(MESSAGE_EDIT_KEY);
-}
diff --git a/tools/ui/src/lib/hooks/use-message-edit-context.svelte.ts b/tools/ui/src/lib/hooks/use-chat-message-edit-context.svelte.ts
similarity index 91%
rename from tools/ui/src/lib/hooks/use-message-edit-context.svelte.ts
rename to tools/ui/src/lib/hooks/use-chat-message-edit-context.svelte.ts
index 271675404..de2994e73 100644
--- a/tools/ui/src/lib/hooks/use-message-edit-context.svelte.ts
+++ b/tools/ui/src/lib/hooks/use-chat-message-edit-context.svelte.ts
@@ -1,15 +1,15 @@
-import { setMessageEditContext } from '$lib/contexts';
+import { setChatMessageEditContext } from '$lib/contexts';
import { MessageRole } from '$lib/enums';
import { parseFilesToMessageExtras } from '$lib/utils/convert-files-to-extra';
-interface UseMessageEditContextOptions {
+interface UseChatMessageEditContextOptions {
getContent: () => string;
getExtras: () => DatabaseMessageExtra[];
showSaveOnlyOption?: boolean;
onSave: (content: string, extras?: DatabaseMessageExtra[]) => void;
}
-export function useMessageEditContext(options: UseMessageEditContextOptions) {
+export function useChatMessageEditContext(options: UseChatMessageEditContextOptions) {
let isEditing = $state(false);
let editedContent = $state('');
let editedExtras = $state([]);
@@ -45,7 +45,7 @@ export function useMessageEditContext(options: UseMessageEditContextOptions) {
isEditing = false;
}
- setMessageEditContext({
+ setChatMessageEditContext({
cancel: handleCancelEdit,
get editedContent() {
return editedContent;
diff --git a/tools/ui/src/lib/types/chat.d.ts b/tools/ui/src/lib/types/chat.d.ts
index cb93e7795..f0f3a297e 100644
--- a/tools/ui/src/lib/types/chat.d.ts
+++ b/tools/ui/src/lib/types/chat.d.ts
@@ -7,7 +7,8 @@ import type {
AttachmentMenuItemId,
ChatFormCommandAction,
ErrorDialogType,
- FileMentionEntryType
+ FileMentionEntryType,
+ MessageRole
} from '$lib/enums';
import type { Component } from 'svelte';
@@ -235,3 +236,111 @@ export interface ChatFormCommand {
action: ChatFormCommandAction;
disabled: boolean;
}
+
+/**
+ * Data shown in the message delete confirmation dialog.
+ */
+export interface ChatMessageDeletionInfo {
+ totalCount: number;
+ userMessages: number;
+ assistantMessages: number;
+ messageTypes: string[];
+}
+
+/**
+ * Conversation-level message operations owned by ChatMessages (store calls + list
+ * refresh + user-action notification), passed to each ChatMessage as a prop.
+ */
+export interface ChatMessageActions {
+ copy: (message: DatabaseMessage) => void;
+ delete: (message: DatabaseMessage) => void;
+ navigateToSibling: (siblingId: string) => void;
+ editWithBranching: (
+ message: DatabaseMessage,
+ newContent: string,
+ newExtras?: DatabaseMessageExtra[]
+ ) => void;
+ editWithReplacement: (
+ message: DatabaseMessage,
+ newContent: string,
+ shouldBranch: boolean
+ ) => void;
+ editUserMessagePreserveResponses: (
+ message: DatabaseMessage,
+ newContent: string,
+ newExtras?: DatabaseMessageExtra[]
+ ) => void;
+ regenerateWithBranching: (message: DatabaseMessage, modelOverride?: string) => void;
+ continueAssistantMessage: (message: DatabaseMessage) => void;
+ forkConversation: (
+ message: DatabaseMessage,
+ options: { name: string; includeAttachments: boolean }
+ ) => void;
+}
+
+/**
+ * Per-message actions and state. Set once per message in ChatMessage.svelte and
+ * consumed by its descendants (action icons, branching controls).
+ */
+export interface ChatMessageActionsContext {
+ readonly siblingInfo: ChatMessageSiblingInfo | null;
+ readonly deletionInfo: ChatMessageDeletionInfo | null;
+ readonly showDeleteDialog: boolean;
+ copy: () => void;
+ requestDelete: () => void;
+ confirmDelete: () => void;
+ setShowDeleteDialog: (show: boolean) => void;
+ navigateToSibling: (siblingId: string) => void;
+ forkConversation?: (options: { name: string; includeAttachments: boolean }) => void;
+}
+
+export interface ChatMessageEditState {
+ readonly isEditing: boolean;
+ readonly editedContent: string;
+ readonly editedExtras: DatabaseMessageExtra[];
+ readonly editedUploadedFiles: ChatUploadedFile[];
+ readonly originalContent: string;
+ readonly originalExtras: DatabaseMessageExtra[];
+ readonly showSaveOnlyOption: boolean;
+ readonly showBranchAfterEditOption: boolean;
+ readonly shouldBranchAfterEdit: boolean;
+ readonly messageRole: MessageRole;
+ readonly rawEditContent?: string;
+}
+
+export interface ChatMessageEditActions {
+ setContent: (content: string) => void;
+ setExtras: (extras: DatabaseMessageExtra[]) => void;
+ setUploadedFiles: (files: ChatUploadedFile[]) => void;
+ save: () => void;
+ saveOnly: () => void;
+ cancel: () => void;
+ startEdit: () => void;
+}
+
+export interface ChatMessageAssistantEditActions {
+ setShouldBranchAfterEdit: (value: boolean) => void;
+}
+
+export type ChatMessageEditContext = ChatMessageEditState &
+ ChatMessageEditActions &
+ Partial;
+
+/**
+ * Actions and capability flags for the ChatForm add-menu. Set once in
+ * ChatFormActions.svelte and consumed by its deep descendants (the add sheet,
+ * dropdown and MCP servers submenu) to avoid relaying them through props.
+ */
+export interface ChatFormActionsContext {
+ readonly disabled: boolean;
+ readonly hasAudioModality: boolean;
+ readonly hasVideoModality: boolean;
+ readonly hasVisionModality: boolean;
+ readonly hasMcpPromptsSupport: boolean;
+ readonly hasMcpResourcesSupport: boolean;
+ onFileUpload?: () => void;
+ onSystemPromptClick?: () => void;
+ onMcpPromptClick?: () => void;
+ onMcpResourcesClick?: () => void;
+ onMcpSettingsClick?: () => void;
+}
diff --git a/tools/ui/src/lib/types/index.ts b/tools/ui/src/lib/types/index.ts
index 917c9516b..34767e2ae 100644
--- a/tools/ui/src/lib/types/index.ts
+++ b/tools/ui/src/lib/types/index.ts
@@ -44,6 +44,14 @@ export type {
ChatUploadedFile,
ChatAttachmentDisplayItem,
ChatMessageSiblingInfo,
+ ChatMessageActions,
+ ChatMessageActionsContext,
+ ChatMessageDeletionInfo,
+ ChatMessageEditContext,
+ ChatMessageEditState,
+ ChatMessageEditActions,
+ ChatMessageAssistantEditActions,
+ ChatFormActionsContext,
ChatMessagePromptProgress,
ChatMessageTimings,
ChatMessageAgenticTimings,
diff --git a/tools/ui/tests/stories/ChatMessage.stories.svelte b/tools/ui/tests/stories/ChatMessage.stories.svelte
index 023ba1ac4..84fee2ea1 100644
--- a/tools/ui/tests/stories/ChatMessage.stories.svelte
+++ b/tools/ui/tests/stories/ChatMessage.stories.svelte
@@ -1,6 +1,7 @@