u
This commit is contained in:
@@ -1119,7 +1119,7 @@
|
|||||||
* description: Id of the conversation that started this one as a subagent, or null for a top-level conversation. Deleting a conversation also deletes every conversation below it in this chain.
|
* description: Id of the conversation that started this one as a subagent, or null for a top-level conversation. Deleting a conversation also deletes every conversation below it in this chain.
|
||||||
* first_message:
|
* first_message:
|
||||||
* type: [string, 'null']
|
* type: [string, 'null']
|
||||||
* description: Only returned by the list endpoint. Plain text of the conversation's first user message (max 200 chars, ellipsis if cut), or null if there is none yet.
|
* description: Plain text of the conversation's first user message (XML stripped, max 100 chars, ellipsis if cut), or null if there is none yet.
|
||||||
* ModelConversationCreateInput:
|
* ModelConversationCreateInput:
|
||||||
* type: object
|
* type: object
|
||||||
* required: [provider, model]
|
* required: [provider, model]
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { RETIRED_MODELS, resolveDefaultModel } from '../../tools/llm-key.js';
|
|||||||
import { emit } from '../sse.js';
|
import { emit } from '../sse.js';
|
||||||
import { getOrCreateManager, removeManager, cleanUserMessageContent } from '../../services/conversationManager.js';
|
import { getOrCreateManager, removeManager, cleanUserMessageContent } from '../../services/conversationManager.js';
|
||||||
|
|
||||||
const FIRST_MESSAGE_MAX_CHARS = 200;
|
const FIRST_MESSAGE_MAX_CHARS = 100;
|
||||||
|
|
||||||
// #region Helpers
|
// #region Helpers
|
||||||
|
|
||||||
@@ -24,9 +24,26 @@ function parsePositiveInt(value, field) {
|
|||||||
return { id };
|
return { id };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes XML elements together with their content (e.g. `<USERENV><ROUTE>…</ROUTE></USERENV>`)
|
||||||
|
* and any leftover self-closing tags.
|
||||||
|
* @param {string} text
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function stripXmlElements(text) {
|
||||||
|
const elementPattern = /<([A-Za-z][\w:.-]*)(?:\s[^>]*)?>[\s\S]*?<\/\1\s*>/g;
|
||||||
|
let previous;
|
||||||
|
let stripped = text;
|
||||||
|
do {
|
||||||
|
previous = stripped;
|
||||||
|
stripped = stripped.replace(elementPattern, '');
|
||||||
|
} while (stripped !== previous);
|
||||||
|
return stripped.replace(/<[A-Za-z][\w:.-]*(?:\s[^>]*)?\/>/g, '');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Turns the stored first user message payload (`{"content": ...}`) into short plain text
|
* Turns the stored first user message payload (`{"content": ...}`) into short plain text
|
||||||
* for conversation lists.
|
* for conversation lists: JSON content only, XML elements stripped, outer whitespace trimmed.
|
||||||
* @param {string|null} storedMessage
|
* @param {string|null} storedMessage
|
||||||
* @returns {string|null}
|
* @returns {string|null}
|
||||||
*/
|
*/
|
||||||
@@ -38,10 +55,19 @@ function buildFirstMessagePreview(storedMessage) {
|
|||||||
} catch {
|
} catch {
|
||||||
rawContent = storedMessage;
|
rawContent = storedMessage;
|
||||||
}
|
}
|
||||||
const text = cleanUserMessageContent(rawContent).trim();
|
const text = stripXmlElements(cleanUserMessageContent(rawContent)).trim();
|
||||||
return text.length > FIRST_MESSAGE_MAX_CHARS ? `${text.slice(0, FIRST_MESSAGE_MAX_CHARS)}…` : text;
|
return text.length > FIRST_MESSAGE_MAX_CHARS ? `${text.slice(0, FIRST_MESSAGE_MAX_CHARS)}…` : text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces the store's raw `first_user_message` with the plain-text `first_message` preview.
|
||||||
|
* @param {{ first_user_message: string|null }} row
|
||||||
|
* @returns {object}
|
||||||
|
*/
|
||||||
|
function buildConversationResponse({ first_user_message: storedFirstMessage, ...conversation }) {
|
||||||
|
return { ...conversation, first_message: buildFirstMessagePreview(storedFirstMessage) };
|
||||||
|
}
|
||||||
|
|
||||||
function modelConversationErrorStatus(error) {
|
function modelConversationErrorStatus(error) {
|
||||||
if (error.message === 'No company selected') return 400;
|
if (error.message === 'No company selected') return 400;
|
||||||
if (error.message === 'Not authorized') return 403;
|
if (error.message === 'Not authorized') return 403;
|
||||||
@@ -113,10 +139,7 @@ export function register(app, basePath = '') {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const rows = await db.list(req.user.id);
|
const rows = await db.list(req.user.id);
|
||||||
const conversations = rows.map(({ first_user_message: storedFirstMessage, ...conversation }) => ({
|
const conversations = rows.map(buildConversationResponse);
|
||||||
...conversation,
|
|
||||||
first_message: buildFirstMessagePreview(storedFirstMessage),
|
|
||||||
}));
|
|
||||||
res.status(200).json({ conversations });
|
res.status(200).json({ conversations });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const status = modelConversationErrorStatus(error);
|
const status = modelConversationErrorStatus(error);
|
||||||
@@ -269,7 +292,7 @@ export function register(app, basePath = '') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const conversation = await db.get(req.user.id, parsedId.id);
|
const conversation = buildConversationResponse(await db.get(req.user.id, parsedId.id));
|
||||||
res.status(200).json({ conversation });
|
res.status(200).json({ conversation });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const status = modelConversationErrorStatus(error);
|
const status = modelConversationErrorStatus(error);
|
||||||
|
|||||||
Reference in New Issue
Block a user