mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
fix(composer): unify suggestion state construction
This commit is contained in:
parent
cb6e89b1ae
commit
edf2b70902
2 changed files with 62 additions and 28 deletions
|
|
@ -159,10 +159,6 @@ export class ChatComposerController {
|
|||
this.options.contextReferenceProvider.dispose();
|
||||
}
|
||||
|
||||
refreshSuggestions(): void {
|
||||
this.updateSuggestions();
|
||||
}
|
||||
|
||||
captureInputSnapshot(): ComposerInputSnapshot {
|
||||
const sourcePath = this.options.sourcePath();
|
||||
return {
|
||||
|
|
@ -245,23 +241,7 @@ export class ChatComposerController {
|
|||
this.dispatchSuggestions({ type: "composer/suggestions-set", suggestions: [] });
|
||||
return;
|
||||
}
|
||||
const suggestions = activeComposerSuggestions(
|
||||
beforeCursor,
|
||||
this.noteCandidates(),
|
||||
state.connection.availableSkills,
|
||||
state.threadList.listedThreads,
|
||||
state.connection.availableModels,
|
||||
this.options.currentModelForSuggestions(),
|
||||
{
|
||||
activeThreadId: activeThreadState(state)?.id ?? null,
|
||||
activeThreadEphemeral: activeThreadState(state)?.lifetime?.kind === "ephemeral",
|
||||
activeThreadSubagent: activeThreadState(state)?.provenance?.kind === "subagent",
|
||||
contextReferences: this.contextReferences(),
|
||||
dailyNoteReferences: () => this.options.noteCandidateProvider.dailyNoteReferences(this.options.sourcePath()),
|
||||
permissionProfiles: state.connection.availablePermissionProfiles,
|
||||
tagCandidates: () => this.options.noteCandidateProvider.tags(),
|
||||
},
|
||||
);
|
||||
const suggestions = this.activeSuggestions(beforeCursor, state);
|
||||
|
||||
this.dispatchSuggestions({
|
||||
type: "composer/suggestions-set",
|
||||
|
|
@ -298,7 +278,17 @@ export class ChatComposerController {
|
|||
}
|
||||
const beforeCursor = composerTextBeforeCursor(this.composer);
|
||||
if (beforeCursor === null) return { suggestions: [], selected: 0, dismissedSignature: null };
|
||||
const suggestions = activeComposerSuggestions(
|
||||
const suggestions = this.activeSuggestions(beforeCursor, state);
|
||||
return {
|
||||
suggestions,
|
||||
selected: state.composer.suggestSelected >= suggestions.length ? 0 : state.composer.suggestSelected,
|
||||
dismissedSignature: null,
|
||||
};
|
||||
}
|
||||
|
||||
private activeSuggestions(beforeCursor: string, state: ChatState): readonly ComposerSuggestion[] {
|
||||
const activeThread = activeThreadState(state);
|
||||
return activeComposerSuggestions(
|
||||
beforeCursor,
|
||||
this.noteCandidates(),
|
||||
state.connection.availableSkills,
|
||||
|
|
@ -306,18 +296,15 @@ export class ChatComposerController {
|
|||
state.connection.availableModels,
|
||||
this.options.currentModelForSuggestions(),
|
||||
{
|
||||
activeThreadId: activeThreadState(state)?.id ?? null,
|
||||
activeThreadId: activeThread?.id ?? null,
|
||||
activeThreadEphemeral: activeThread?.lifetime?.kind === "ephemeral",
|
||||
activeThreadSubagent: activeThread?.provenance?.kind === "subagent",
|
||||
contextReferences: this.contextReferences(),
|
||||
dailyNoteReferences: () => this.options.noteCandidateProvider.dailyNoteReferences(this.options.sourcePath()),
|
||||
permissionProfiles: state.connection.availablePermissionProfiles,
|
||||
tagCandidates: () => this.options.noteCandidateProvider.tags(),
|
||||
},
|
||||
);
|
||||
return {
|
||||
suggestions,
|
||||
selected: state.composer.suggestSelected >= suggestions.length ? 0 : state.composer.suggestSelected,
|
||||
dismissedSignature: null,
|
||||
};
|
||||
}
|
||||
|
||||
private selectSuggestion(index: number): void {
|
||||
|
|
|
|||
|
|
@ -213,6 +213,53 @@ describe("ChatComposerController", () => {
|
|||
expect(parent.querySelector(".codex-panel__composer-suggestion")?.textContent).toContain("/");
|
||||
});
|
||||
|
||||
it("applies ephemeral-thread suggestion restrictions on input without waiting for keyup", () => {
|
||||
const stateStore = createChatStateStore(
|
||||
chatStateFixture({
|
||||
activeThread: {
|
||||
id: "side-thread",
|
||||
lifetime: { kind: "ephemeral", sourceThreadId: "source-thread", sourceThreadTitle: "Source" },
|
||||
},
|
||||
}),
|
||||
);
|
||||
const { controller, parent } = composerControllerFixture({ stateStore });
|
||||
|
||||
renderComposerController(parent, controller, stateStore);
|
||||
setTextAreaValue(composer(parent), "/f");
|
||||
composer(parent).setSelectionRange(2, 2);
|
||||
composer(parent).dispatchEvent(new Event("input", { bubbles: true }));
|
||||
|
||||
expect(stateStore.getState().composer.suggestions.map((suggestion) => suggestion.replacement)).not.toContain("/fork");
|
||||
expect(stateStore.getState().composer.suggestions.map((suggestion) => suggestion.replacement)).toContain("/fast");
|
||||
});
|
||||
|
||||
it("omits subagent slash suggestions on input without waiting for keyup", () => {
|
||||
const stateStore = createChatStateStore(
|
||||
chatStateFixture({
|
||||
activeThread: {
|
||||
id: "child-thread",
|
||||
provenance: {
|
||||
kind: "subagent",
|
||||
subagentKind: "thread-spawn",
|
||||
parentThreadId: "parent-thread",
|
||||
sessionId: "session",
|
||||
depth: 1,
|
||||
agentNickname: "Scout",
|
||||
agentRole: "explorer",
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
const { controller, parent } = composerControllerFixture({ stateStore });
|
||||
|
||||
renderComposerController(parent, controller, stateStore);
|
||||
setTextAreaValue(composer(parent), "/");
|
||||
composer(parent).setSelectionRange(1, 1);
|
||||
composer(parent).dispatchEvent(new Event("input", { bubbles: true }));
|
||||
|
||||
expect(stateStore.getState().composer.suggestions).toEqual([]);
|
||||
});
|
||||
|
||||
it("updates Obsidian tag suggestions when the input changes", () => {
|
||||
const { controller, parent, stateStore } = composerControllerFixture({
|
||||
controller: {
|
||||
|
|
|
|||
Loading…
Reference in a new issue