release: prepare 0629001 portal updates

This commit is contained in:
john
2026-06-29 22:20:04 +08:00
parent 18ea4f82fd
commit a40e340a41
84 changed files with 17516 additions and 2475 deletions
+4 -1
View File
@@ -1,5 +1,8 @@
export const CHAT_IMAGE_UPLOAD_MAX_INPUT_BYTES = 8 * 1024 * 1024;
export const CHAT_IMAGE_UPLOAD_MAX_INPUT_BYTES = 4 * 1024 * 1024;
export const CHAT_IMAGE_UPLOAD_MAX_OUTPUT_BYTES = 1.5 * 1024 * 1024;
export const MINDSPACE_IMAGE_UPLOAD_MAX_BYTES = 4 * 1024 * 1024;
export const CHAT_IMAGE_UPLOAD_MAX_COUNT = 10;
export const CHAT_IMAGE_UPLOAD_MAX_TOTAL_BYTES = 20 * 1024 * 1024;
export const CHAT_IMAGE_MAX_SIDE = 1920;
const MAX_PIXELS = 2_500_000;
const ACCEPTED_IMAGE_MIME = new Set(['image/jpeg', 'image/png', 'image/webp']);
+22 -11
View File
@@ -3,6 +3,9 @@ import { mergeMessageContent } from '../../message-stream.mjs';
import { stripUserAddressPrefix } from './userAddress';
const IMAGE_URL_LINE_RE = /^\[图片\d+]: (.+)$/;
const IMAGE_URL_LINES_RE = /\n*\[图片\d+]: [^\n]+/g;
const USER_IDENTITY_BLOCK_RE = /^\[用户身份\][\s\S]*?(?:\n{2,}|$)/;
const TKMIND_VISION_NOTE_RE = /\n*【TKMind 图片分析结果[\s\S]*$/;
function normalizeImageUrl(url: string): string {
const value = String(url ?? '').trim();
@@ -32,6 +35,14 @@ function formatImageUrlsForAgentText(urls: string[]): string {
.join('\n');
}
function stripImageUrlLines(text: string): string {
return String(text ?? '')
.replace(USER_IDENTITY_BLOCK_RE, '')
.replace(TKMIND_VISION_NOTE_RE, '')
.replace(IMAGE_URL_LINES_RE, '')
.trim();
}
function parseImageUrlsFromText(text: string): string[] {
const urls: string[] = [];
for (const line of text.split('\n')) {
@@ -71,12 +82,12 @@ export function normalizeUserMessageForApi(message: Message): Message {
const imageUrls = getImageUrls(message);
const displayText =
message.metadata.displayText ??
message.content
.filter((item): item is Extract<MessageContent, { type: 'text' }> => item.type === 'text')
.map((item) => item.text)
.join('\n')
.replace(/\n*\[图片\d+]: [^\n]+/g, '')
.trim();
stripImageUrlLines(
message.content
.filter((item): item is Extract<MessageContent, { type: 'text' }> => item.type === 'text')
.map((item) => item.text)
.join('\n'),
);
const textOnly = message.content
.filter((item): item is Extract<MessageContent, { type: 'text' }> => item.type === 'text')
@@ -89,7 +100,7 @@ export function normalizeUserMessageForApi(message: Message): Message {
}
const imageText = formatImageUrlsForAgentText(imageUrls);
const agentText = [textOnly.replace(/\n*\[图片\d+]: [^\n]+/g, '').trim(), imageText]
const agentText = [stripImageUrlLines(textOnly), imageText]
.filter(Boolean)
.join('\n\n');
@@ -108,7 +119,7 @@ export function buildUserMessage(
text: string,
options?: { agentText?: string; displayText?: string; imageUrls?: string[]; previewImageUrls?: string[] },
): Message {
const displayText = options?.displayText ?? text;
const displayText = stripImageUrlLines(options?.displayText ?? text);
const imageUrls = (options?.imageUrls ?? []).filter((value) => typeof value === 'string' && value.trim());
const previewImageUrls = (options?.previewImageUrls ?? []).filter(
(value) => typeof value === 'string' && value.trim(),
@@ -160,12 +171,12 @@ export function mergeConversationSnapshot(current: Message[], incoming: Message[
export function getDisplayText(message: Message): string {
if ('displayText' in message.metadata) {
return message.metadata.displayText ?? '';
return stripImageUrlLines(message.metadata.displayText ?? '');
}
const systemText = getSystemNotificationText(message);
if (systemText) return systemText;
const visible = getVisibleText(message);
return message.role === 'user' ? stripUserAddressPrefix(visible) : visible;
return message.role === 'user' ? stripImageUrlLines(stripUserAddressPrefix(visible)) : visible;
}
export function pushMessage(messages: Message[], incoming: Message): Message[] {
@@ -187,7 +198,7 @@ export function getVisibleText(message: Message): string {
.filter((c): c is Extract<MessageContent, { type: 'text' }> => c.type === 'text')
.map((c) => c.text)
.join('');
return text.replace(/\n*\[图片\d+]: [^\n]+/g, '').trim();
return stripImageUrlLines(text);
}
export function getImageUrls(message: Message): string[] {
+44 -17
View File
@@ -1,5 +1,4 @@
import { appConfig } from '../config';
import type { Session } from '../types';
import type { Session, SessionSummary } from '../types';
export const DEFAULT_CHAT_TITLE = 'New Chat';
@@ -12,28 +11,42 @@ function isDefaultSessionName(name: string): boolean {
return !trimmed || trimmed === DEFAULT_CHAT_TITLE || trimmed === '新对话' || isGeneratedSessionName(trimmed);
}
export function shouldShowNewChatTitle(session: Session): boolean {
export function shouldShowNewChatTitle(session: SessionSummary): boolean {
if (session.recipe) return false;
return !session.user_set_name && session.message_count === 0 && isDefaultSessionName(session.name);
}
export function sortAndTrim(sessions: Session[]): Session[] {
export function sortAndTrim<T extends SessionSummary>(sessions: T[]): T[] {
return [...sessions]
.sort((a, b) => {
const aTime = new Date(a.updated_at ?? a.created_at ?? 0).getTime();
const bTime = new Date(b.updated_at ?? b.created_at ?? 0).getTime();
return bTime - aTime;
})
.slice(0, appConfig.sessionMaxCount);
});
}
export function hasMeaningfulSessionTitle(session: Session): boolean {
export function hasMeaningfulSessionTitle(session: SessionSummary): boolean {
if (session.user_set_name) return Boolean(session.name.trim());
if (session.recipe?.title?.trim()) return true;
return !isDefaultSessionName(session.name);
}
export function mergeSessionRecord(existing: Session | undefined, incoming: Session): Session {
export function toSessionSummary(session: Session | SessionSummary): SessionSummary {
return {
id: session.id,
name: session.name,
message_count: session.message_count,
created_at: session.created_at,
updated_at: session.updated_at,
user_set_name: session.user_set_name,
recipe: session.recipe ?? null,
};
}
export function mergeSessionRecord(
existing: SessionSummary | undefined,
incoming: SessionSummary,
): SessionSummary {
if (!existing) return incoming;
const keepExistingTitle =
@@ -50,19 +63,33 @@ export function mergeSessionRecord(existing: Session | undefined, incoming: Sess
};
}
export function mergeSessionLists(prev: Session[], incoming: Session[]): Session[] {
export function mergeSessionLists(prev: SessionSummary[], incoming: SessionSummary[]): SessionSummary[] {
const previousById = new Map(prev.map((item) => [item.id, item]));
return sortAndTrim(incoming.map((item) => mergeSessionRecord(previousById.get(item.id), item)));
}
export function prependUnique(prev: Session[], session: Session): Session[] {
export function appendSessionLists(prev: SessionSummary[], incoming: SessionSummary[]): SessionSummary[] {
const previousById = new Map(prev.map((item) => [item.id, item]));
const mergedIncoming = incoming.map((item) => mergeSessionRecord(previousById.get(item.id), item));
const incomingIds = new Set(mergedIncoming.map((item) => item.id));
return sortAndTrim([
...prev.filter((item) => !incomingIds.has(item.id)),
...mergedIncoming,
]);
}
export function prependUnique(prev: SessionSummary[], session: SessionSummary): SessionSummary[] {
return sortAndTrim([
mergeSessionRecord(prev.find((s) => s.id === session.id), session),
...prev.filter((s) => s.id !== session.id),
]);
}
export function touchSession(sessions: Session[], sessionId: string, messageDelta = 0): Session[] {
export function touchSession(
sessions: SessionSummary[],
sessionId: string,
messageDelta = 0,
): SessionSummary[] {
const now = new Date().toISOString();
return sortAndTrim(
sessions.map((s) =>
@@ -77,7 +104,7 @@ export function touchSession(sessions: Session[], sessionId: string, messageDelt
);
}
export function getSessionDisplayName(session: Session): string {
export function getSessionDisplayName(session: SessionSummary): string {
if (session.user_set_name) return session.name;
if (session.recipe?.title) return session.recipe.title;
if (shouldShowNewChatTitle(session)) return DEFAULT_CHAT_TITLE;
@@ -92,7 +119,7 @@ function sameLocalDay(a: Date, b: Date): boolean {
);
}
export function getSessionDateKey(session: Session): string {
export function getSessionDateKey(session: SessionSummary): string {
const iso = session.updated_at ?? session.created_at;
if (!iso) return '';
const date = new Date(iso);
@@ -100,7 +127,7 @@ export function getSessionDateKey(session: Session): string {
return `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`;
}
export function formatSessionDateLabel(session: Session): string {
export function formatSessionDateLabel(session: SessionSummary): string {
const iso = session.updated_at ?? session.created_at;
if (!iso) return '';
const date = new Date(iso);
@@ -123,10 +150,10 @@ export function formatSessionDateLabel(session: Session): string {
export type SessionDateGroup = {
dateKey: string;
label: string;
sessions: Session[];
sessions: SessionSummary[];
};
export function groupSessionsByDate(sessions: Session[]): SessionDateGroup[] {
export function groupSessionsByDate(sessions: SessionSummary[]): SessionDateGroup[] {
const groups: SessionDateGroup[] = [];
for (const session of sessions) {
const dateKey = getSessionDateKey(session);
@@ -143,7 +170,7 @@ export function groupSessionsByDate(sessions: Session[]): SessionDateGroup[] {
const DEFAULT_SESSION_NAMES = new Set([DEFAULT_CHAT_TITLE, '新对话']);
export function getSessionListLabel(session: Session): string {
export function getSessionListLabel(session: SessionSummary): string {
const displayName = getSessionDisplayName(session).trim();
if (displayName && !DEFAULT_SESSION_NAMES.has(displayName) && !isGeneratedSessionName(displayName)) {
return displayName;