feat: 支持远程会话分页发现

This commit is contained in:
digua
2026-04-26 22:25:22 +08:00
committed by digua
parent ef2334b758
commit 352a071a21
13 changed files with 295 additions and 62 deletions
+68
View File
@@ -0,0 +1,68 @@
export interface RemoteSession {
id: string
name: string
platform: string
type: string
messageCount?: number
memberCount?: number
lastMessageAt?: number
}
export interface RemoteSessionDiscoveryPage {
hasMore: boolean
nextCursor?: string
}
export interface RemoteSessionDiscoveryResult {
sessions: RemoteSession[]
page?: RemoteSessionDiscoveryPage
}
export interface RemoteSessionDiscoveryQuery {
keyword?: string
limit?: number
cursor?: string
}
export function buildRemoteSessionsUrl(baseUrl: string, query: RemoteSessionDiscoveryQuery = {}): string {
const searchParams = new URLSearchParams()
searchParams.set('format', 'chatlab')
if (query.keyword?.trim()) searchParams.set('keyword', query.keyword.trim())
if (query.limit && query.limit > 0) searchParams.set('limit', String(query.limit))
if (query.cursor) searchParams.set('cursor', query.cursor)
return `${baseUrl}/sessions?${searchParams.toString()}`
}
/**
* Parse remote sessions response with backward compatibility.
* Supports: Pull protocol `{ sessions, page? }`, ChatLab API `{ success, data }`, and plain array.
*/
export function parseRemoteSessionsResponse(body: string): RemoteSessionDiscoveryResult {
const parsed = JSON.parse(body)
let sessions: RemoteSession[]
let pageSource: Record<string, unknown> | undefined
if (Array.isArray(parsed)) {
sessions = parsed
} else if (parsed && typeof parsed === 'object') {
sessions = parsed.sessions ?? parsed.data?.sessions ?? parsed.data ?? []
if (!Array.isArray(sessions)) sessions = []
pageSource = parsed.page ?? parsed.data?.page
} else {
sessions = []
}
return {
sessions,
page:
pageSource && typeof pageSource === 'object'
? {
hasMore: Boolean(pageSource.hasMore),
nextCursor: typeof pageSource.nextCursor === 'string' ? pageSource.nextCursor : undefined,
}
: undefined,
}
}
+15 -17
View File
@@ -5,24 +5,26 @@
import { net } from 'electron'
import { normalizeBaseUrl } from './dataSource'
import {
buildRemoteSessionsUrl,
parseRemoteSessionsResponse,
type RemoteSessionDiscoveryQuery,
type RemoteSessionDiscoveryResult,
} from './pullDiscovery.shared'
export interface RemoteSession {
id: string
name: string
platform: string
type: string
messageCount?: number
memberCount?: number
lastMessageAt?: number
}
export type { RemoteSession, RemoteSessionDiscoveryQuery, RemoteSessionDiscoveryResult } from './pullDiscovery.shared'
/**
* Fetch available sessions from a remote data source.
* Calls GET {baseUrl}/sessions according to the Pull protocol.
*/
export function fetchRemoteSessions(baseUrl: string, token?: string): Promise<RemoteSession[]> {
return new Promise<RemoteSession[]>((resolve, reject) => {
const url = normalizeBaseUrl(baseUrl) + '/sessions?format=chatlab&limit=10000'
export function fetchRemoteSessions(
baseUrl: string,
token?: string,
query: RemoteSessionDiscoveryQuery = {}
): Promise<RemoteSessionDiscoveryResult> {
return new Promise<RemoteSessionDiscoveryResult>((resolve, reject) => {
const url = buildRemoteSessionsUrl(normalizeBaseUrl(baseUrl), query)
const request = net.request(url)
if (token) {
@@ -44,11 +46,7 @@ export function fetchRemoteSessions(baseUrl: string, token?: string): Promise<Re
response.on('end', () => {
try {
const parsed = JSON.parse(body)
const sessions: RemoteSession[] = Array.isArray(parsed)
? parsed
: (parsed.data?.sessions ?? parsed.sessions ?? [])
resolve(sessions)
resolve(parseRemoteSessionsResponse(body))
} catch (err) {
reject(new Error('Failed to parse remote sessions response'))
}
+9 -6
View File
@@ -114,13 +114,16 @@ export function registerApiHandlers(_ctx: IpcContext): void {
// ==================== Remote Discovery ====================
ipcMain.handle('api:fetchRemoteSessions', async (_event, baseUrl: string, token: string) => {
try {
return await fetchRemoteSessions(baseUrl, token || undefined)
} catch (err: any) {
throw new Error(err.message || 'Failed to fetch remote sessions')
ipcMain.handle(
'api:fetchRemoteSessions',
async (_event, baseUrl: string, token: string, query?: { keyword?: string; limit?: number; cursor?: string }) => {
try {
return await fetchRemoteSessions(baseUrl, token || undefined, query)
} catch (err: any) {
throw new Error(err.message || 'Failed to fetch remote sessions')
}
}
})
)
}
/**
+16 -2
View File
@@ -50,6 +50,16 @@ export interface RemoteSession {
lastMessageAt?: number
}
export interface RemoteSessionDiscoveryPage {
hasMore: boolean
nextCursor?: string
}
export interface RemoteSessionDiscoveryResult {
sessions: RemoteSession[]
page?: RemoteSessionDiscoveryPage
}
export const apiServerApi = {
// ==================== API 服务管理 ====================
@@ -128,8 +138,12 @@ export const apiServerApi = {
return ipcRenderer.invoke('api:triggerPullAll', sourceId)
},
fetchRemoteSessions: (baseUrl: string, token?: string): Promise<RemoteSession[]> => {
return ipcRenderer.invoke('api:fetchRemoteSessions', baseUrl, token || '')
fetchRemoteSessions: (
baseUrl: string,
token?: string,
query?: { keyword?: string; limit?: number; cursor?: string }
): Promise<RemoteSessionDiscoveryResult> => {
return ipcRenderer.invoke('api:fetchRemoteSessions', baseUrl, token || '', query)
},
onPullResult: (
+15 -1
View File
@@ -1034,6 +1034,16 @@ interface RemoteSession {
lastMessageAt?: number
}
interface RemoteSessionDiscoveryPage {
hasMore: boolean
nextCursor?: string
}
interface RemoteSessionDiscoveryResult {
sessions: RemoteSession[]
page?: RemoteSessionDiscoveryPage
}
interface ApiServerApi {
getConfig: () => Promise<ApiServerConfig>
getStatus: () => Promise<ApiServerStatus>
@@ -1060,7 +1070,11 @@ interface ApiServerApi {
removeImportSession: (sourceId: string, sessionId: string) => Promise<boolean>
triggerPull: (sourceId: string, sessionId?: string) => Promise<{ success: boolean; error?: string }>
triggerPullAll: (sourceId: string) => Promise<{ success: boolean; error?: string }>
fetchRemoteSessions: (baseUrl: string, token?: string) => Promise<RemoteSession[]>
fetchRemoteSessions: (
baseUrl: string,
token?: string,
query?: { keyword?: string; limit?: number; cursor?: string }
) => Promise<RemoteSessionDiscoveryResult>
onPullResult: (
callback: (data: { sourceId: string; sessionId?: string; status: string; detail: string }) => void
) => () => void