diff --git a/apps/web/app/(app)/[organization]/[connectionId]/chatbot/core/api.ts b/apps/web/app/(app)/[organization]/[connectionId]/chatbot/core/api.ts index 732eb100..b3a1b3c1 100644 --- a/apps/web/app/(app)/[organization]/[connectionId]/chatbot/core/api.ts +++ b/apps/web/app/(app)/[organization]/[connectionId]/chatbot/core/api.ts @@ -27,9 +27,12 @@ function assertOk(res: Response, data: ApiEnvelope, fallback: string) { } } -export async function apiFetchSessions(params?: { mode?: ChatMode; errorMessage?: string }) { - const type = params?.mode ?? 'global'; - const res = await authFetch(`/api/chat/sessions?type=${encodeURIComponent(type)}`, { +export async function apiFetchSessions(params: { mode?: ChatMode; connectionId: string; errorMessage?: string }) { + const type = params.mode ?? 'global'; + const url = new URL('/api/chat/sessions', window.location.origin); + url.searchParams.set('type', type); + url.searchParams.set('connectionId', params.connectionId); + const res = await authFetch(url.toString(), { method: 'GET', cache: 'no-store', }); @@ -54,7 +57,7 @@ export async function apiFetchSessionDetail(sessionId: string, options?: { error return { detail, messages }; } -export async function apiCreateSession(params: { mode: ChatMode; errorMessage?: string; copilotNotSupportedMessage?: string }) { +export async function apiCreateSession(params: { mode: ChatMode; connectionId: string; errorMessage?: string; copilotNotSupportedMessage?: string }) { if (params.mode === 'copilot') { throw new Error(params.copilotNotSupportedMessage ?? 'Copilot sessions cannot be created manually.'); } @@ -65,6 +68,7 @@ export async function apiCreateSession(params: { mode: ChatMode; errorMessage?: cache: 'no-store', body: JSON.stringify({ type: 'global', + connectionId: params.connectionId, }), }); diff --git a/apps/web/app/(app)/[organization]/[connectionId]/chatbot/core/session-controller.ts b/apps/web/app/(app)/[organization]/[connectionId]/chatbot/core/session-controller.ts index 4b6bd669..af73281b 100644 --- a/apps/web/app/(app)/[organization]/[connectionId]/chatbot/core/session-controller.ts +++ b/apps/web/app/(app)/[organization]/[connectionId]/chatbot/core/session-controller.ts @@ -5,6 +5,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { toast } from 'sonner'; import type { UIMessage } from 'ai'; import { useTranslations } from 'next-intl'; +import { useParams } from 'next/navigation'; import posthog from 'posthog-js'; import type { ChatSessionItem, ChatMode } from './types'; @@ -23,6 +24,8 @@ export function useChatSessions(params: { mode: ChatMode; copilotEnvelope?: Copi const { mode, copilotEnvelope } = params; const copilotTabId = copilotEnvelope?.meta?.tabId ?? null; const t = useTranslations('Chatbot'); + const routeParams = useParams<{ connectionId: string }>(); + const connectionId = routeParams.connectionId; const [sessions, setSessions] = useState([]); const [selectedSessionId, setSelectedSessionId] = useState(null); @@ -55,7 +58,7 @@ export function useChatSessions(params: { mode: ChatMode; copilotEnvelope?: Copi setLoadingSessions(true); try { - const list = await apiFetchSessions({ mode: 'global', errorMessage: t('Errors.FetchSessions') }); + const list = await apiFetchSessions({ mode: 'global', connectionId, errorMessage: t('Errors.FetchSessions') }); setSessions(list); const currentPreferred = preferredId ?? selectedSessionRef.current; @@ -74,7 +77,7 @@ export function useChatSessions(params: { mode: ChatMode; copilotEnvelope?: Copi setLoadingSessions(false); } }, - [mode, t], + [mode, connectionId, t], ); const fetchSessionDetail = useCallback(async (sessionId: string) => { @@ -178,7 +181,7 @@ export function useChatSessions(params: { mode: ChatMode; copilotEnvelope?: Copi if (creatingSession) return; setCreatingSession(true); try { - const created = await apiCreateSession({ mode: 'global', errorMessage: t('Errors.CreateSession') }); + const created = await apiCreateSession({ mode: 'global', connectionId, errorMessage: t('Errors.CreateSession') }); if (created?.id) { posthog.capture('chat_session_created', { session_id: created.id }); } diff --git a/apps/web/app/api/chat/sessions/route.ts b/apps/web/app/api/chat/sessions/route.ts index de6ba097..85fe51e3 100644 --- a/apps/web/app/api/chat/sessions/route.ts +++ b/apps/web/app/api/chat/sessions/route.ts @@ -36,6 +36,7 @@ export const GET = withUserAndOrganizationHandler(async ({ req, db, userId, orga const locale = await getApiLocale(); const { searchParams } = new URL(req.url); const type = (searchParams.get('type') as ChatSessionType | null) ?? 'global'; + const connectionId = searchParams.get('connectionId'); if (type !== 'global' && type !== 'copilot') { return NextResponse.json( @@ -47,6 +48,16 @@ export const GET = withUserAndOrganizationHandler(async ({ req, db, userId, orga ); } + if (!connectionId) { + return NextResponse.json( + ResponseUtil.error({ + code: ErrorCodes.INVALID_PARAMS, + message: translateApi('Api.Chat.Errors.ConnectionIdRequired', undefined, locale), + }), + { status: 400 }, + ); + } + try { if (!db?.chat) throw new Error('Chat repository not available'); @@ -55,6 +66,7 @@ export const GET = withUserAndOrganizationHandler(async ({ req, db, userId, orga userId, type, includeArchived: false, + connectionId, }); return NextResponse.json( @@ -81,7 +93,7 @@ export const POST = withUserAndOrganizationHandler(async ({ req, db, userId, org const locale = await getApiLocale(); console.log('POST /api/chat/sessions called', userId, organizationId); - let payload: { type?: string } | null = null; + let payload: { type?: string; connectionId?: string } | null = null; try { payload = await req.json(); } catch { @@ -89,6 +101,8 @@ export const POST = withUserAndOrganizationHandler(async ({ req, db, userId, org } const type = payload?.type ?? 'global'; + const connectionId = payload?.connectionId ?? null; + if (type !== 'global') { return NextResponse.json( ResponseUtil.error({ @@ -99,12 +113,23 @@ export const POST = withUserAndOrganizationHandler(async ({ req, db, userId, org ); } + if (!connectionId) { + return NextResponse.json( + ResponseUtil.error({ + code: ErrorCodes.INVALID_PARAMS, + message: translateApi('Api.Chat.Errors.ConnectionIdRequired', undefined, locale), + }), + { status: 400 }, + ); + } + try { if (!db?.chat) throw new Error('Chat repository not available'); const created = await db.chat.createGlobalSession({ organizationId, userId, + connectionId, title: null, metadata: null, }); diff --git a/apps/web/lib/database/postgres/impl/chat/index.ts b/apps/web/lib/database/postgres/impl/chat/index.ts index c68beb7f..1ad60e04 100644 --- a/apps/web/lib/database/postgres/impl/chat/index.ts +++ b/apps/web/lib/database/postgres/impl/chat/index.ts @@ -179,12 +179,13 @@ export class PostgresChatRepository implements ChatRepository { async listSessions(params: { organizationId: string; userId: string; + connectionId: string; includeArchived?: boolean; type?: ChatSessionType; }) { this.assertInited(); - const conds = [eq(chatSessions.organizationId, params.organizationId), eq(chatSessions.userId, params.userId)]; + const conds = [eq(chatSessions.organizationId, params.organizationId), eq(chatSessions.userId, params.userId), eq(chatSessions.connectionId, params.connectionId)]; if (!params.includeArchived) conds.push(isNull(chatSessions.archivedAt)); if (params.type) conds.push(eq(chatSessions.type, params.type)); diff --git a/apps/web/public/locales/en.json b/apps/web/public/locales/en.json index 47f14696..e83d187d 100644 --- a/apps/web/public/locales/en.json +++ b/apps/web/public/locales/en.json @@ -1736,6 +1736,7 @@ "MissingTabId": "Missing tabId", "FetchCopilotSessionFailed": "Failed to fetch Copilot session", "InvalidSessionType": "Invalid session type", + "ConnectionIdRequired": "Connection ID is required", "ListSessionsFailed": "Failed to fetch sessions", "CopilotCreationNotAllowed": "Copilot sessions cannot be created via this endpoint", "CreateSessionFailed": "Failed to create session" diff --git a/apps/web/public/locales/es.json b/apps/web/public/locales/es.json index dae47047..2b5fd0ea 100644 --- a/apps/web/public/locales/es.json +++ b/apps/web/public/locales/es.json @@ -1724,6 +1724,7 @@ "MissingTabId": "Falta tabId", "FetchCopilotSessionFailed": "No se pudo obtener la sesión de Copilot", "InvalidSessionType": "Tipo de sesión no válido", + "ConnectionIdRequired": "Se requiere el ID de conexión", "ListSessionsFailed": "No se pudieron obtener las sesiones", "CopilotCreationNotAllowed": "Las sesiones de Copilot no se pueden crear desde este endpoint", "CreateSessionFailed": "No se pudo crear la sesión" diff --git a/apps/web/public/locales/ja.json b/apps/web/public/locales/ja.json index 398d47a7..d589340f 100644 --- a/apps/web/public/locales/ja.json +++ b/apps/web/public/locales/ja.json @@ -1724,6 +1724,7 @@ "MissingTabId": "tabId がありません", "FetchCopilotSessionFailed": "Copilot セッションの取得に失敗しました", "InvalidSessionType": "無効なセッション種別です", + "ConnectionIdRequired": "接続IDが必要です", "ListSessionsFailed": "セッションの取得に失敗しました", "CopilotCreationNotAllowed": "このエンドポイントでは Copilot セッションを作成できません", "CreateSessionFailed": "セッションの作成に失敗しました" diff --git a/apps/web/public/locales/zh.json b/apps/web/public/locales/zh.json index fa435af1..3c5c40d7 100644 --- a/apps/web/public/locales/zh.json +++ b/apps/web/public/locales/zh.json @@ -1738,6 +1738,7 @@ "MissingTabId": "缺少 tabId", "FetchCopilotSessionFailed": "获取 Copilot 会话失败", "InvalidSessionType": "非法的 session type", + "ConnectionIdRequired": "缺少 Connection ID 参数", "ListSessionsFailed": "获取会话列表失败", "CopilotCreationNotAllowed": "Copilot 会话不能通过该接口创建", "CreateSessionFailed": "创建会话失败" diff --git a/apps/web/types/chat.ts b/apps/web/types/chat.ts index 1c34099a..ba5919db 100644 --- a/apps/web/types/chat.ts +++ b/apps/web/types/chat.ts @@ -178,7 +178,7 @@ export interface ChatRepository { createGlobalSession(input: ChatSessionCreateGlobal): Promise; - listSessions(params: { organizationId: string; userId: string; includeArchived?: boolean; type?: ChatSessionType }): Promise; + listSessions(params: { organizationId: string; userId: string; connectionId: string; includeArchived?: boolean; type?: ChatSessionType }): Promise; readSession(params: { organizationId: string; sessionId: string; userId: string }): Promise;