mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-05-21 22:00:39 +08:00
feat: 支持远程会话分页发现
This commit is contained in:
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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'))
|
||||
}
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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: (
|
||||
|
||||
Vendored
+15
-1
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user