Name sessions with the model and keep the rail list live

Sessions no longer take their title from the first message: once the first exchange is sent, the text model writes the title and summary, which the rail and sessions list show. The summary records how many messages it covers so a stale one is rewritten before it is carried into the next session.

The sessions hook now reloads on live message and session events, so a session started by the first message, a rollover or another tab appears without a reload. New sessions are pushed as server:bot-session, both from the process that changed them and through the live relay.
This commit is contained in:
2026-10-19 18:05:57 +01:00
parent 9293a55032
commit 4d5195447b
19 changed files with 420 additions and 38 deletions
+56 -2
View File
@@ -1,6 +1,7 @@
import { useEffect, useState } from "react";
import type { BotSessionListItem } from "@/src/types";
import type { BotSessionChangedData, BotSessionListItem } from "@/src/types";
import postSessionApi from "@/src/functions/frontend/post-session-api";
import useServerEvent from "./use-server-event";
type Params = {
bot_id: number;
@@ -15,9 +16,37 @@ type Params = {
limit?: number;
};
type ChangedSession = {
bot_id?: number | string | null;
workspace_id?: number | string | null;
};
/**
* Whether a pushed message or session change belongs to this conversation:
* the same bot, in its own chat or in the same workspace.
*/
function belongsToConversation({
session,
bot_id,
workspace_id,
}: {
session: ChangedSession;
bot_id: number;
workspace_id?: number | null;
}) {
return (
Number(session.bot_id) == bot_id &&
(session.workspace_id ? Number(session.workspace_id) : null) ==
(workspace_id ? Number(workspace_id) : null)
);
}
/**
* A conversation's sessions, latest first, searched as the user types
* (debounced). Starting or reopening a session reloads the page onto it.
* New messages and session changes pushed live reload the list, so a
* session started in the conversation (or named once its first reply is
* in) shows without a reload.
*/
export default function useBotSessions({ bot_id, workspace_id, open, limit }: Params) {
const [query, setQuery] = useState("");
@@ -25,6 +54,31 @@ export default function useBotSessions({ bot_id, workspace_id, open, limit }: Pa
const [loading, setLoading] = useState(false);
const [busy_id, setBusyId] = useState<number | "new">();
const [error, setError] = useState<string>();
const [revision, setRevision] = useState(0);
useServerEvent({
event: "server:bot-message",
onData: (message: BotSessionListItem) => {
if (
message &&
belongsToConversation({ session: message, bot_id, workspace_id })
) {
setRevision((value) => value + 1);
}
},
});
useServerEvent({
event: "server:bot-session",
onData: (session: BotSessionChangedData) => {
if (
session &&
belongsToConversation({ session, bot_id, workspace_id })
) {
setRevision((value) => value + 1);
}
},
});
useEffect(() => {
if (!open) return;
@@ -54,7 +108,7 @@ export default function useBotSessions({ bot_id, workspace_id, open, limit }: Pa
stale = true;
clearTimeout(timer);
};
}, [open, query, bot_id, workspace_id, limit]);
}, [open, query, bot_id, workspace_id, limit, revision]);
const act = async ({ id, path, body }: { id: number | "new"; path: "new-session" | "reopen-session"; body: Record<string, unknown> }) => {
if (busy_id) return;