mirror of
https://github.com/hansjone/dsh-im-ops.git
synced 2026-10-09 06:30:45 +08:00
74 lines
2.8 KiB
JavaScript
74 lines
2.8 KiB
JavaScript
const DEFAULT_MAX_TITLE_BYTES = 60;
|
|
|
|
const OSC_SEQUENCE = /(?:\u001b\]|\u009d)(?:(?!\u0007|\u001b\\)[\s\S])*(?:\u0007|\u001b\\|$)/gu;
|
|
const CSI_SEQUENCE = /(?:\u001b\[|\u009b)[0-?]*[ -/]*[@-~]/gu;
|
|
const ESC_SEQUENCE = /\u001b[@-_]/gu;
|
|
const CONTROL_CHARACTER = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/gu;
|
|
const DIRECTIONAL_CONTROL = /[\u200b\u200e\u200f\u202a-\u202e\u2060-\u2064\u2066-\u206f\ufeff]/gu;
|
|
const INJECTED_CONTEXT_PREFIX = /^(?:<dsh_im_source>[\s\S]*?<\/dsh_im_source>\s*)?(?:<dsh_im_source_guidance>[\s\S]*?<\/dsh_im_source_guidance>\s*)?(?:<dsh_im_reply_to>[\s\S]*?<\/dsh_im_reply_to>\s*)?/u;
|
|
const SEGMENTER = new Intl.Segmenter(undefined, { granularity: 'grapheme' });
|
|
|
|
function cleanTitleText(input) {
|
|
return input
|
|
.replace(OSC_SEQUENCE, '')
|
|
.replace(CSI_SEQUENCE, '')
|
|
.replace(ESC_SEQUENCE, '')
|
|
.replace(CONTROL_CHARACTER, '')
|
|
.replace(DIRECTIONAL_CONTROL, '')
|
|
.replace(/\s+/gu, ' ')
|
|
.trim();
|
|
}
|
|
|
|
function truncateTitle(input, maxBytes = DEFAULT_MAX_TITLE_BYTES) {
|
|
if (Buffer.byteLength(input, 'utf8') <= maxBytes) return input;
|
|
const suffix = '…';
|
|
const budget = maxBytes - Buffer.byteLength(suffix, 'utf8');
|
|
let output = '';
|
|
for (const { segment } of SEGMENTER.segment(input)) {
|
|
if (Buffer.byteLength(output + segment, 'utf8') > budget) break;
|
|
output += segment;
|
|
}
|
|
return `${output.trimEnd()}${suffix}`;
|
|
}
|
|
|
|
function contentText(content) {
|
|
if (typeof content === 'string') return content;
|
|
if (!Array.isArray(content)) return '';
|
|
return content
|
|
.filter((block) => block?.type === 'text' && typeof block.text === 'string')
|
|
.map((block) => block.text)
|
|
.join('\n');
|
|
}
|
|
|
|
function fileDisplayName(file) {
|
|
const value = file?.name ?? file?.filename ?? file?.fileName;
|
|
if (typeof value !== 'string') return '';
|
|
return value
|
|
.replaceAll('\\', '/')
|
|
.split('/')
|
|
.at(-1)
|
|
?.replace(/[\u0000-\u001f\u007f]/gu, '')
|
|
.trim() ?? '';
|
|
}
|
|
|
|
/** Build a deterministic title from the unenhanced first user message. */
|
|
export function initialSessionTitle({ text, content, files } = {}) {
|
|
const original = typeof text === 'string' ? cleanTitleText(text) : '';
|
|
if (original) return truncateTitle(original);
|
|
|
|
// Structured image prompts may only expose their default text through content.
|
|
// Strip only the leading blocks inserted by dsh-im; matching tags later in user
|
|
// content remain ordinary user text.
|
|
const visibleContent = cleanTitleText(
|
|
contentText(content).replace(INJECTED_CONTEXT_PREFIX, ''),
|
|
);
|
|
if (visibleContent) return truncateTitle(visibleContent);
|
|
|
|
const fileName = Array.isArray(files)
|
|
? files.map(fileDisplayName).find(Boolean)
|
|
: '';
|
|
const cleanFileName = fileName ? cleanTitleText(fileName) : '';
|
|
return cleanFileName ? truncateTitle(cleanFileName) : null;
|
|
}
|
|
|
|
export const INITIAL_SESSION_TITLE_MAX_BYTES = DEFAULT_MAX_TITLE_BYTES;
|