feat(chat): persistance bornee du brouillon custom chat par projet/layout (#182)
This commit is contained in:
@ -253,6 +253,33 @@ describe("TauriAgentGateway invoke payloads", () => {
|
||||
expect(out.effect.kind).toBe("cleanConversation");
|
||||
});
|
||||
|
||||
it("wraps chat draft persistence commands with the project/layout/node scope", async () => {
|
||||
const gateway = new TauriAgentGateway();
|
||||
const scope = {
|
||||
projectId: "proj-1",
|
||||
layoutId: "layout-1",
|
||||
nodeId: "node-1",
|
||||
};
|
||||
invoke.mockResolvedValueOnce({ text: "hello", updatedAtMs: 42 });
|
||||
|
||||
await expect(gateway.readChatDraft(scope)).resolves.toEqual({
|
||||
text: "hello",
|
||||
updatedAtMs: 42,
|
||||
});
|
||||
await gateway.saveChatDraft(scope, "edited");
|
||||
await gateway.clearChatDraft(scope);
|
||||
|
||||
expect(invoke).toHaveBeenNthCalledWith(1, "read_chat_draft", {
|
||||
request: scope,
|
||||
});
|
||||
expect(invoke).toHaveBeenNthCalledWith(2, "save_chat_draft", {
|
||||
request: { ...scope, text: "edited" },
|
||||
});
|
||||
expect(invoke).toHaveBeenNthCalledWith(3, "clear_chat_draft", {
|
||||
request: scope,
|
||||
});
|
||||
});
|
||||
|
||||
it("launchAgentChat returns a handle only when launch_agent confirms cellKind chat", async () => {
|
||||
invoke.mockResolvedValueOnce({
|
||||
sessionId: "chat-session-1",
|
||||
|
||||
@ -28,6 +28,8 @@ import type {
|
||||
import type {
|
||||
AgentChatHandle,
|
||||
AgentGateway,
|
||||
ChatDraftScope,
|
||||
ChatDraftSnapshot,
|
||||
ConversationDetails,
|
||||
CreateAgentInput,
|
||||
LiveAgent,
|
||||
@ -297,6 +299,24 @@ export class TauriAgentGateway implements AgentGateway {
|
||||
});
|
||||
}
|
||||
|
||||
readChatDraft(scope: ChatDraftScope): Promise<ChatDraftSnapshot> {
|
||||
return invoke<ChatDraftSnapshot>("read_chat_draft", {
|
||||
request: scope,
|
||||
});
|
||||
}
|
||||
|
||||
async saveChatDraft(scope: ChatDraftScope, text: string): Promise<void> {
|
||||
await invoke("save_chat_draft", {
|
||||
request: { ...scope, text },
|
||||
});
|
||||
}
|
||||
|
||||
async clearChatDraft(scope: ChatDraftScope): Promise<void> {
|
||||
await invoke("clear_chat_draft", {
|
||||
request: scope,
|
||||
});
|
||||
}
|
||||
|
||||
async closeAgentChat(sessionId: string): Promise<void> {
|
||||
await invoke("close_agent_session", { sessionId });
|
||||
}
|
||||
|
||||
@ -41,6 +41,8 @@ import type {
|
||||
} from "@/domain";
|
||||
import type {
|
||||
AgentGateway,
|
||||
ChatDraftScope,
|
||||
ChatDraftSnapshot,
|
||||
ConversationDetails,
|
||||
CreateAgentInput,
|
||||
CreateTicketInput,
|
||||
@ -255,6 +257,18 @@ export class HttpAgentGateway implements AgentGateway {
|
||||
});
|
||||
}
|
||||
|
||||
readChatDraft(scope: ChatDraftScope): Promise<ChatDraftSnapshot> {
|
||||
return this.http.invoke<ChatDraftSnapshot>("read_chat_draft", { request: scope });
|
||||
}
|
||||
|
||||
async saveChatDraft(scope: ChatDraftScope, text: string): Promise<void> {
|
||||
await this.http.invoke("save_chat_draft", { request: { ...scope, text } });
|
||||
}
|
||||
|
||||
async clearChatDraft(scope: ChatDraftScope): Promise<void> {
|
||||
await this.http.invoke("clear_chat_draft", { request: scope });
|
||||
}
|
||||
|
||||
async launchAgent(
|
||||
projectId: string,
|
||||
agentId: string,
|
||||
|
||||
@ -103,6 +103,8 @@ import type {
|
||||
import type {
|
||||
AgentChatHandle,
|
||||
AgentGateway,
|
||||
ChatDraftScope,
|
||||
ChatDraftSnapshot,
|
||||
ConversationGateway,
|
||||
ConversationPageRequest,
|
||||
ConversationDetails,
|
||||
@ -401,6 +403,8 @@ export class MockAgentGateway implements AgentGateway {
|
||||
private liveKindByAgent = new Map<string, "pty" | "structured">();
|
||||
/** Retained structured reply chunks per live chat session. */
|
||||
private chatScrollback = new Map<string, ReplyChunk[]>();
|
||||
/** Persisted custom chat drafts keyed by project/layout/node. */
|
||||
private chatDrafts = new Map<string, ChatDraftSnapshot>();
|
||||
private slashCommands: SlashCommand[] = [
|
||||
{
|
||||
name: "/help",
|
||||
@ -437,6 +441,10 @@ export class MockAgentGateway implements AgentGateway {
|
||||
return `${projectId}::${agentId}`;
|
||||
}
|
||||
|
||||
private chatDraftKey(scope: ChatDraftScope): string {
|
||||
return `${scope.projectId}::${scope.layoutId}::${scope.nodeId}`;
|
||||
}
|
||||
|
||||
async listAgents(projectId: string): Promise<Agent[]> {
|
||||
return structuredClone(this.getAgents(projectId));
|
||||
}
|
||||
@ -985,6 +993,28 @@ export class MockAgentGateway implements AgentGateway {
|
||||
};
|
||||
}
|
||||
|
||||
async readChatDraft(scope: ChatDraftScope): Promise<ChatDraftSnapshot> {
|
||||
return structuredClone(
|
||||
this.chatDrafts.get(this.chatDraftKey(scope)) ?? { text: null, updatedAtMs: null },
|
||||
);
|
||||
}
|
||||
|
||||
async saveChatDraft(scope: ChatDraftScope, text: string): Promise<void> {
|
||||
const key = this.chatDraftKey(scope);
|
||||
if (text.length === 0) {
|
||||
this.chatDrafts.delete(key);
|
||||
return;
|
||||
}
|
||||
this.chatDrafts.set(key, {
|
||||
text,
|
||||
updatedAtMs: Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
async clearChatDraft(scope: ChatDraftScope): Promise<void> {
|
||||
this.chatDrafts.delete(this.chatDraftKey(scope));
|
||||
}
|
||||
|
||||
async closeAgentChat(sessionId: string): Promise<void> {
|
||||
this.chatScrollback.delete(sessionId);
|
||||
for (const [agentId, liveSessionId] of this.liveSessionByAgent) {
|
||||
|
||||
Reference in New Issue
Block a user