| |
| |
| |
| |
| |
| |
| |
| |
|
|
| const STOPWORDS = new Set([ |
| "a","an","the","and","or","but","of","for","in","on","at","to","with","by","from", |
| "is","are","was","were","be","being","been","have","has","had","do","does","did", |
| "can","could","should","would","will","may","might","must","shall", |
| "i","we","you","they","it","this","that","these","those","my","our","your","their", |
| "please","kindly","help","me","us","need","want","like","get","give","tell","show", |
| "using","based","above","below","about","into","than","then","as","so","also", |
| "trial","run","pipeline","task","question","query", |
| ]); |
|
|
| |
| function tokenize(text: string): string[] { |
| return text |
| .replace(/[\r\n]+/g, " ") |
| .split(/[^A-Za-z0-9+\-./]+/) |
| .filter(Boolean); |
| } |
|
|
| export function inferTaskTitle(firstUserMessage: string, maxChars = 48): string { |
| if (!firstUserMessage) return "Untitled Session"; |
| |
| const stripped = firstUserMessage.replace(/^\s*TRIAL\s+\d+\s*\([^)]*\)\s*:\s*/i, "").trim(); |
| const tokens = tokenize(stripped); |
|
|
| const kept: string[] = []; |
| for (const t of tokens) { |
| const lower = t.toLowerCase(); |
| if (STOPWORDS.has(lower)) continue; |
| |
| const preserved = /^[A-Z0-9][A-Z0-9+./\-]+$/.test(t) |
| ? t |
| : t.charAt(0).toUpperCase() + t.slice(1).toLowerCase(); |
| kept.push(preserved); |
| if (kept.join(" ").length >= maxChars) break; |
| } |
|
|
| if (kept.length === 0) { |
| |
| const fallback = stripped.split(/\s+/).slice(0, 6).join(" "); |
| return truncate(fallback || "Untitled Session", maxChars); |
| } |
|
|
| return truncate(kept.join(" "), maxChars); |
| } |
|
|
| function truncate(s: string, max: number): string { |
| return s.length <= max ? s : s.slice(0, max - 1).trimEnd() + "…"; |
| } |
|
|