add skills to the chat composer (#8881)

Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
This commit is contained in:
morgmart
2026-04-28 14:21:45 -07:00
committed by GitHub
parent 23d3db445f
commit 69efb796ae
49 changed files with 2086 additions and 540 deletions
@@ -1,5 +1,7 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import type { ChatSkillDraft } from "../types";
import type { ChatAttachmentDraft } from "@/shared/types/messages";
import type { ChatSendOptions } from "../types";
import { INITIAL_TOKEN_STATE } from "@/shared/types/chat";
import { useChat } from "./useChat";
import { useAutoCompactPreferences } from "./useAutoCompactPreferences";
@@ -36,6 +38,7 @@ interface UseChatSessionControllerOptions {
}
const PENDING_HOME_SESSION_ID = "__home_pending__";
const EMPTY_SKILL_DRAFTS: ChatSkillDraft[] = [];
export function useChatSessionController({
sessionId,
@@ -71,6 +74,10 @@ export function useChatSessionController({
const pendingDraftValue = useChatStore(
(s) => s.draftsBySession[PENDING_HOME_SESSION_ID] ?? "",
);
const pendingSkillDrafts = useChatStore(
(s) =>
s.skillDraftsBySession[PENDING_HOME_SESSION_ID] ?? EMPTY_SKILL_DRAFTS,
);
const pendingQueuedMessage = useChatStore(
(s) => s.queuedMessageBySession[PENDING_HOME_SESSION_ID] ?? null,
);
@@ -478,9 +485,14 @@ export function useChatSessionController({
text: string,
overridePersona?: { id: string; name?: string },
attachments?: ChatAttachmentDraft[],
sendOptions?: ChatSendOptions,
) => {
if (!canAutoCompactBeforeSend(overridePersona)) {
void sendMessage(text, overridePersona, attachments);
if (sendOptions) {
void sendMessage(text, overridePersona, attachments, sendOptions);
} else {
void sendMessage(text, overridePersona, attachments);
}
return true;
}
@@ -490,7 +502,11 @@ export function useChatSessionController({
return false;
}
void sendMessage(text, overridePersona, attachments);
if (sendOptions) {
void sendMessage(text, overridePersona, attachments, sendOptions);
} else {
void sendMessage(text, overridePersona, attachments);
}
return true;
})();
},
@@ -505,6 +521,7 @@ export function useChatSessionController({
const deferredSend = useRef<{
text: string;
attachments?: ChatAttachmentDraft[];
sendOptions?: ChatSendOptions;
resolve?: (accepted: boolean) => void;
} | null>(null);
const queue = useMessageQueue(
@@ -514,10 +531,15 @@ export function useChatSessionController({
);
const handleSend = useCallback(
(text: string, personaId?: string, attachments?: ChatAttachmentDraft[]) => {
(
text: string,
personaId?: string,
attachments?: ChatAttachmentDraft[],
sendOptions?: ChatSendOptions,
) => {
if (!sessionId) {
if (!queue.queuedMessage) {
queue.enqueue(text, personaId, attachments);
queue.enqueue(text, personaId, attachments, sendOptions);
}
return true;
}
@@ -525,16 +547,16 @@ export function useChatSessionController({
if (personaId && personaId !== selectedPersonaId) {
handlePersonaChange(personaId);
return new Promise<boolean>((resolve) => {
deferredSend.current = { text, attachments, resolve };
deferredSend.current = { text, attachments, sendOptions, resolve };
});
}
if (chatState !== "idle" && !queue.queuedMessage) {
queue.enqueue(text, personaId, attachments);
queue.enqueue(text, personaId, attachments, sendOptions);
return true;
}
return sendWithAutoCompact(text, undefined, attachments);
return sendWithAutoCompact(text, undefined, attachments, sendOptions);
},
[
chatState,
@@ -548,9 +570,14 @@ export function useChatSessionController({
useEffect(() => {
if (deferredSend.current && selectedPersona) {
const { text, attachments, resolve } = deferredSend.current;
const { text, attachments, sendOptions, resolve } = deferredSend.current;
deferredSend.current = null;
const sendResult = sendWithAutoCompact(text, undefined, attachments);
const sendResult = sendWithAutoCompact(
text,
undefined,
attachments,
sendOptions,
);
if (sendResult instanceof Promise) {
void sendResult.then((accepted) => {
if (accepted === false) {
@@ -575,13 +602,25 @@ export function useChatSessionController({
const sessionDraftValue = useChatStore((s) =>
sessionId ? (s.draftsBySession[sessionId] ?? "") : "",
);
const sessionSkillDrafts = useChatStore((s) =>
sessionId
? (s.skillDraftsBySession[sessionId] ?? EMPTY_SKILL_DRAFTS)
: EMPTY_SKILL_DRAFTS,
);
const draftValue = sessionId ? sessionDraftValue : pendingDraftValue;
const selectedSkills = sessionId ? sessionSkillDrafts : pendingSkillDrafts;
const handleDraftChange = useCallback(
(text: string) => {
useChatStore.getState().setDraft(stateSessionId, text);
},
[stateSessionId],
);
const handleSkillsChange = useCallback(
(skills: typeof selectedSkills) => {
useChatStore.getState().setSkillDrafts(stateSessionId, skills);
},
[stateSessionId],
);
const scrollTarget = useChatStore((s) =>
sessionId ? (s.scrollTargetMessageBySession[sessionId] ?? null) : null,
);
@@ -599,16 +638,25 @@ export function useChatSessionController({
let cancelled = false;
void pendingDraftValue;
void pendingSkillDrafts;
void pendingQueuedMessage;
const syncPendingHomeState = async () => {
const chatState = useChatStore.getState();
const pendingDraft =
chatState.draftsBySession[PENDING_HOME_SESSION_ID] ?? "";
const pendingSkills =
chatState.skillDraftsBySession[PENDING_HOME_SESSION_ID] ?? [];
if (pendingDraft && !chatState.draftsBySession[sessionId]) {
chatState.setDraft(sessionId, pendingDraft);
}
if (
pendingSkills.length > 0 &&
!chatState.skillDraftsBySession[sessionId]?.length
) {
chatState.setSkillDrafts(sessionId, pendingSkills);
}
const hasPendingProvider = pendingProviderId !== undefined;
const hasPendingPersona = pendingPersonaId !== undefined;
@@ -704,6 +752,7 @@ export function useChatSessionController({
}
useChatStore.getState().clearDraft(PENDING_HOME_SESSION_ID);
useChatStore.getState().clearSkillDrafts(PENDING_HOME_SESSION_ID);
useChatStore.getState().dismissQueuedMessage(PENDING_HOME_SESSION_ID);
useChatStore.getState().cleanupSession(PENDING_HOME_SESSION_ID);
};
@@ -716,6 +765,7 @@ export function useChatSessionController({
}, [
activeWorkspace?.path,
pendingDraftValue,
pendingSkillDrafts,
pendingModelSelection,
pendingPersonaId,
pendingProjectId,
@@ -750,6 +800,8 @@ export function useChatSessionController({
handleSend,
draftValue,
handleDraftChange,
selectedSkills,
handleSkillsChange,
scrollTarget,
handleScrollTargetHandled,
projectMetadataPending,