diff --git a/src/app/api/chat/lib/chat-store.ts b/src/app/api/chat/lib/chat-store.ts new file mode 100644 index 0000000..a4c6d9d --- /dev/null +++ b/src/app/api/chat/lib/chat-store.ts @@ -0,0 +1,129 @@ +type ChatLocale = 'zh' | 'en' +type ChatRole = 'USER' | 'ASSISTANT' | 'SYSTEM' +type ChatStatus = 'ACTIVE' + +interface ChatMessage { + id: string + sessionId: string + role: ChatRole + contentText: string + mode: string | null + createdAt: string +} + +interface ChatSession { + id: string + clientId: string + locale: ChatLocale + title: string + status: ChatStatus + createdAt: string + updatedAt: string + lastMessageAt: string +} + +interface ChatStoreState { + sessions: Map + messages: Map +} + +declare global { + // eslint-disable-next-line no-var + var __agentParkChatStore: ChatStoreState | undefined +} + +function getStore(): ChatStoreState { + if (!globalThis.__agentParkChatStore) { + globalThis.__agentParkChatStore = { + sessions: new Map(), + messages: new Map(), + } + } + + return globalThis.__agentParkChatStore +} + +function nowIso(): string { + return new Date().toISOString() +} + +export async function createChatSession(input: { + clientId: string + locale: ChatLocale + title?: string +}): Promise { + const store = getStore() + const now = nowIso() + + const session: ChatSession = { + id: crypto.randomUUID(), + clientId: input.clientId, + locale: input.locale, + title: input.title?.trim() || 'New Chat', + status: 'ACTIVE', + createdAt: now, + updatedAt: now, + lastMessageAt: now, + } + + store.sessions.set(session.id, session) + store.messages.set(session.id, []) + + return session +} + +export async function listChatSessions(input: { + clientId: string + locale?: ChatLocale + limit: number + cursor?: string +}): Promise> { + const store = getStore() + + const ordered = Array.from(store.sessions.values()) + .filter((session) => + input.locale + ? session.clientId === input.clientId && session.locale === input.locale + : session.clientId === input.clientId + ) + .sort((a, b) => b.lastMessageAt.localeCompare(a.lastMessageAt)) + + const cursorIndex = input.cursor ? ordered.findIndex((session) => session.id === input.cursor) : -1 + const page = cursorIndex >= 0 ? ordered.slice(cursorIndex + 1, cursorIndex + 1 + input.limit) : ordered.slice(0, input.limit) + + return page.map((session) => { + const messageList = store.messages.get(session.id) || [] + const latest = messageList.length > 0 ? [messageList[messageList.length - 1] as ChatMessage] : [] + return { + ...session, + messages: latest, + } + }) +} + +export async function assertSessionOwnership(input: { + sessionId: string + clientId: string +}): Promise { + const store = getStore() + const session = store.sessions.get(input.sessionId) + + if (!session) { + throw new Error('CHAT_SESSION_NOT_FOUND') + } + + if (session.clientId !== input.clientId) { + throw new Error('CHAT_SESSION_FORBIDDEN') + } +} + +export async function listChatMessages(input: { + sessionId: string + limit: number +}): Promise { + const store = getStore() + const messages = store.messages.get(input.sessionId) || [] + const normalizedLimit = Math.max(1, input.limit) + + return messages.slice(-normalizedLimit) +} diff --git a/src/app/api/signals/route.ts b/src/app/api/signals/route.ts index 418efaa..6eeaa76 100644 --- a/src/app/api/signals/route.ts +++ b/src/app/api/signals/route.ts @@ -232,13 +232,6 @@ export async function GET(request: NextRequest) { ? [{ isHot: 'desc' }, { hotScore: 'desc' }, { engagement: 'desc' }, { publishedAt: 'desc' }, { id: 'desc' }] : [{ publishedAt: 'desc' }, { id: 'desc' }] - const cursorArgs = parsed.cursor - ? { - cursor: { id: parsed.cursor }, - skip: 1 as const, - } - : {} - let rows: Array<{ id: string source: string @@ -262,7 +255,8 @@ export async function GET(request: NextRequest) { where, orderBy, take: parsed.limit + 1, - ...cursorArgs, + cursor: parsed.cursor ? { id: parsed.cursor } : undefined, + skip: parsed.cursor ? 1 : undefined, select: { id: true, source: true, @@ -293,7 +287,8 @@ export async function GET(request: NextRequest) { where, orderBy: fallbackOrderBy, take: parsed.limit + 1, - ...cursorArgs, + cursor: parsed.cursor ? { id: parsed.cursor } : undefined, + skip: parsed.cursor ? 1 : undefined, select: { id: true, source: true,