@kine-design/ai-chat 0.0.1-beta.1 → 0.0.1-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.vlaude/last-session-id +1 -0
- package/composables/__tests__/createSocketIOTransport.test.ts +181 -0
- package/composables/__tests__/createWebSocketTransport.test.ts +145 -0
- package/composables/createSocketIOTransport.ts +76 -0
- package/composables/types.ts +1 -0
- package/dist/ai-chat.js +63 -1
- package/dist/composables/__tests__/createSocketIOTransport.test.d.ts +9 -0
- package/dist/composables/__tests__/createWebSocketTransport.test.d.ts +9 -0
- package/dist/composables/createSocketIOTransport.d.ts +2 -0
- package/dist/composables/types.d.ts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/vitest.config.d.ts +2 -0
- package/index.ts +1 -0
- package/package.json +8 -3
- package/vite.config.build.ts +1 -0
- package/vitest.config.ts +10 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
6e6ccc4f-850a-43c4-b677-5af5ae931d77
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description Socket.IO transport unit tests
|
|
3
|
+
* @author 阿怪
|
|
4
|
+
* @date 2026/5/7
|
|
5
|
+
* @version v0.0.1
|
|
6
|
+
*
|
|
7
|
+
* 江湖的业务千篇一律,复杂的代码好几百行。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
11
|
+
import { createSocketIOTransport } from '../createSocketIOTransport';
|
|
12
|
+
import type { ServerPushEvent, TransportStatus } from '../types';
|
|
13
|
+
|
|
14
|
+
const listeners: Record<string, Function[]> = {};
|
|
15
|
+
const ioListeners: Record<string, Function[]> = {};
|
|
16
|
+
|
|
17
|
+
const mockSocket = {
|
|
18
|
+
on: vi.fn((event: string, handler: Function) => {
|
|
19
|
+
(listeners[event] ??= []).push(handler);
|
|
20
|
+
}),
|
|
21
|
+
emit: vi.fn(),
|
|
22
|
+
disconnect: vi.fn(),
|
|
23
|
+
io: {
|
|
24
|
+
on: vi.fn((event: string, handler: Function) => {
|
|
25
|
+
(ioListeners[event] ??= []).push(handler);
|
|
26
|
+
}),
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
vi.mock('socket.io-client', () => ({
|
|
31
|
+
io: vi.fn(() => mockSocket),
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
function fireSocketEvent(event: string, ...args: unknown[]) {
|
|
35
|
+
listeners[event]?.forEach(h => h(...args));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function fireIOEvent(event: string, ...args: unknown[]) {
|
|
39
|
+
ioListeners[event]?.forEach(h => h(...args));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
beforeEach(() => {
|
|
43
|
+
vi.clearAllMocks();
|
|
44
|
+
for (const key of Object.keys(listeners)) delete listeners[key];
|
|
45
|
+
for (const key of Object.keys(ioListeners)) delete ioListeners[key];
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe('createSocketIOTransport', () => {
|
|
49
|
+
it('connect emits connecting then connected on socket connect', () => {
|
|
50
|
+
const transport = createSocketIOTransport();
|
|
51
|
+
const statuses: TransportStatus[] = [];
|
|
52
|
+
transport.onStatusChange(s => statuses.push(s));
|
|
53
|
+
|
|
54
|
+
transport.connect('http://localhost:9506');
|
|
55
|
+
expect(statuses).toEqual(['connecting']);
|
|
56
|
+
|
|
57
|
+
fireSocketEvent('connect');
|
|
58
|
+
expect(statuses).toEqual(['connecting', 'connected']);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('disconnect emits disconnected', () => {
|
|
62
|
+
const transport = createSocketIOTransport();
|
|
63
|
+
const statuses: TransportStatus[] = [];
|
|
64
|
+
transport.onStatusChange(s => statuses.push(s));
|
|
65
|
+
|
|
66
|
+
transport.connect('http://localhost:9506');
|
|
67
|
+
transport.disconnect();
|
|
68
|
+
|
|
69
|
+
expect(mockSocket.disconnect).toHaveBeenCalled();
|
|
70
|
+
expect(statuses[statuses.length - 1]).toBe('disconnected');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('reconnect_attempt emits reconnecting', () => {
|
|
74
|
+
const transport = createSocketIOTransport();
|
|
75
|
+
const statuses: TransportStatus[] = [];
|
|
76
|
+
transport.onStatusChange(s => statuses.push(s));
|
|
77
|
+
|
|
78
|
+
transport.connect('http://localhost:9506');
|
|
79
|
+
fireIOEvent('reconnect_attempt');
|
|
80
|
+
|
|
81
|
+
expect(statuses).toContain('reconnecting');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('send emits chat:send with OutgoingMessage', () => {
|
|
85
|
+
const transport = createSocketIOTransport();
|
|
86
|
+
transport.connect('http://localhost:9506');
|
|
87
|
+
|
|
88
|
+
const msg = { conversationId: 'conv-1', content: 'hello', timestamp: Date.now() };
|
|
89
|
+
transport.send(msg);
|
|
90
|
+
|
|
91
|
+
expect(mockSocket.emit).toHaveBeenCalledWith('chat:send', msg);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('receives chat:event and forwards to handlers', () => {
|
|
95
|
+
const transport = createSocketIOTransport();
|
|
96
|
+
const events: ServerPushEvent[] = [];
|
|
97
|
+
transport.onEvent(e => events.push(e));
|
|
98
|
+
|
|
99
|
+
transport.connect('http://localhost:9506');
|
|
100
|
+
|
|
101
|
+
const pushEvent: ServerPushEvent = {
|
|
102
|
+
type: 'fragment_end',
|
|
103
|
+
conversationId: 'conv-1',
|
|
104
|
+
data: {
|
|
105
|
+
messageId: 'msg-1',
|
|
106
|
+
clusterId: 'ac-1',
|
|
107
|
+
role: 'assistant',
|
|
108
|
+
content: 'hello',
|
|
109
|
+
status: 'complete',
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
fireSocketEvent('chat:event', pushEvent);
|
|
113
|
+
|
|
114
|
+
expect(events).toEqual([pushEvent]);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('receives phase_change events', () => {
|
|
118
|
+
const transport = createSocketIOTransport();
|
|
119
|
+
const events: ServerPushEvent[] = [];
|
|
120
|
+
transport.onEvent(e => events.push(e));
|
|
121
|
+
|
|
122
|
+
transport.connect('http://localhost:9506');
|
|
123
|
+
|
|
124
|
+
const pushEvent: ServerPushEvent = {
|
|
125
|
+
type: 'phase_change',
|
|
126
|
+
conversationId: 'conv-1',
|
|
127
|
+
data: { phase: 'thinking' },
|
|
128
|
+
};
|
|
129
|
+
fireSocketEvent('chat:event', pushEvent);
|
|
130
|
+
|
|
131
|
+
expect(events).toHaveLength(1);
|
|
132
|
+
expect(events[0].type).toBe('phase_change');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('passes auth and headers to socket.io options', async () => {
|
|
136
|
+
const { io } = await import('socket.io-client');
|
|
137
|
+
const transport = createSocketIOTransport();
|
|
138
|
+
|
|
139
|
+
transport.connect('http://localhost:9506', {
|
|
140
|
+
auth: { token: 'my-token' },
|
|
141
|
+
headers: { 'X-Custom': 'value' },
|
|
142
|
+
reconnect: true,
|
|
143
|
+
maxReconnectAttempts: 3,
|
|
144
|
+
reconnectInterval: 1000,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
expect(io).toHaveBeenCalledWith('http://localhost:9506', {
|
|
148
|
+
auth: { token: 'my-token' },
|
|
149
|
+
extraHeaders: { 'X-Custom': 'value' },
|
|
150
|
+
reconnection: true,
|
|
151
|
+
reconnectionAttempts: 3,
|
|
152
|
+
reconnectionDelay: 1000,
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('multiple event handlers all receive events', () => {
|
|
157
|
+
const transport = createSocketIOTransport();
|
|
158
|
+
const events1: ServerPushEvent[] = [];
|
|
159
|
+
const events2: ServerPushEvent[] = [];
|
|
160
|
+
transport.onEvent(e => events1.push(e));
|
|
161
|
+
transport.onEvent(e => events2.push(e));
|
|
162
|
+
|
|
163
|
+
transport.connect('http://localhost:9506');
|
|
164
|
+
|
|
165
|
+
const pushEvent: ServerPushEvent = {
|
|
166
|
+
type: 'fragment_end',
|
|
167
|
+
conversationId: 'conv-1',
|
|
168
|
+
data: {
|
|
169
|
+
messageId: 'msg-1',
|
|
170
|
+
clusterId: 'ac-1',
|
|
171
|
+
role: 'assistant',
|
|
172
|
+
content: 'test',
|
|
173
|
+
status: 'complete',
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
fireSocketEvent('chat:event', pushEvent);
|
|
177
|
+
|
|
178
|
+
expect(events1).toHaveLength(1);
|
|
179
|
+
expect(events2).toHaveLength(1);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description WebSocket transport unit tests
|
|
3
|
+
* @author 阿怪
|
|
4
|
+
* @date 2026/5/7
|
|
5
|
+
* @version v0.0.1
|
|
6
|
+
*
|
|
7
|
+
* 江湖的业务千篇一律,复杂的代码好几百行。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
11
|
+
import { createWebSocketTransport } from '../createWebSocketTransport';
|
|
12
|
+
import type { ServerPushEvent, TransportStatus } from '../types';
|
|
13
|
+
|
|
14
|
+
let mockWs: {
|
|
15
|
+
onopen: (() => void) | null;
|
|
16
|
+
onclose: (() => void) | null;
|
|
17
|
+
onerror: (() => void) | null;
|
|
18
|
+
onmessage: ((e: { data: string }) => void) | null;
|
|
19
|
+
send: ReturnType<typeof vi.fn>;
|
|
20
|
+
close: ReturnType<typeof vi.fn>;
|
|
21
|
+
readyState: number;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
mockWs = {
|
|
26
|
+
onopen: null,
|
|
27
|
+
onclose: null,
|
|
28
|
+
onerror: null,
|
|
29
|
+
onmessage: null,
|
|
30
|
+
send: vi.fn(),
|
|
31
|
+
close: vi.fn(),
|
|
32
|
+
readyState: 1,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const MockWebSocket = function () { return mockWs; } as unknown as typeof WebSocket;
|
|
36
|
+
Object.defineProperty(MockWebSocket, 'OPEN', { value: 1 });
|
|
37
|
+
vi.stubGlobal('WebSocket', MockWebSocket);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe('createWebSocketTransport', () => {
|
|
41
|
+
it('connect emits connecting then connected on ws open', () => {
|
|
42
|
+
const transport = createWebSocketTransport();
|
|
43
|
+
const statuses: TransportStatus[] = [];
|
|
44
|
+
transport.onStatusChange(s => statuses.push(s));
|
|
45
|
+
|
|
46
|
+
transport.connect('ws://localhost:9506/chat');
|
|
47
|
+
expect(statuses).toEqual(['connecting']);
|
|
48
|
+
|
|
49
|
+
mockWs.onopen?.();
|
|
50
|
+
expect(statuses).toEqual(['connecting', 'connected']);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('disconnect closes ws and emits disconnected', () => {
|
|
54
|
+
const transport = createWebSocketTransport();
|
|
55
|
+
const statuses: TransportStatus[] = [];
|
|
56
|
+
transport.onStatusChange(s => statuses.push(s));
|
|
57
|
+
|
|
58
|
+
transport.connect('ws://localhost:9506/chat');
|
|
59
|
+
transport.disconnect();
|
|
60
|
+
|
|
61
|
+
expect(mockWs.close).toHaveBeenCalled();
|
|
62
|
+
expect(statuses[statuses.length - 1]).toBe('disconnected');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('send serializes OutgoingMessage as JSON', () => {
|
|
66
|
+
const transport = createWebSocketTransport();
|
|
67
|
+
transport.connect('ws://localhost:9506/chat');
|
|
68
|
+
mockWs.onopen?.();
|
|
69
|
+
|
|
70
|
+
const msg = { conversationId: 'conv-1', content: 'hello', timestamp: 1000 };
|
|
71
|
+
transport.send(msg);
|
|
72
|
+
|
|
73
|
+
expect(mockWs.send).toHaveBeenCalledWith(JSON.stringify(msg));
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('receives messages and forwards parsed ServerPushEvent to handlers', () => {
|
|
77
|
+
const transport = createWebSocketTransport();
|
|
78
|
+
const events: ServerPushEvent[] = [];
|
|
79
|
+
transport.onEvent(e => events.push(e));
|
|
80
|
+
|
|
81
|
+
transport.connect('ws://localhost:9506/chat');
|
|
82
|
+
mockWs.onopen?.();
|
|
83
|
+
|
|
84
|
+
const pushEvent: ServerPushEvent = {
|
|
85
|
+
type: 'fragment_end',
|
|
86
|
+
conversationId: 'conv-1',
|
|
87
|
+
data: {
|
|
88
|
+
messageId: 'msg-1',
|
|
89
|
+
clusterId: 'ac-1',
|
|
90
|
+
role: 'assistant',
|
|
91
|
+
content: 'hello',
|
|
92
|
+
status: 'complete',
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
mockWs.onmessage?.({ data: JSON.stringify(pushEvent) });
|
|
96
|
+
|
|
97
|
+
expect(events).toEqual([pushEvent]);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('ignores non-JSON messages', () => {
|
|
101
|
+
const transport = createWebSocketTransport();
|
|
102
|
+
const events: ServerPushEvent[] = [];
|
|
103
|
+
transport.onEvent(e => events.push(e));
|
|
104
|
+
|
|
105
|
+
transport.connect('ws://localhost:9506/chat');
|
|
106
|
+
mockWs.onopen?.();
|
|
107
|
+
mockWs.onmessage?.({ data: 'not json' });
|
|
108
|
+
|
|
109
|
+
expect(events).toHaveLength(0);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('reconnects on close when reconnect is enabled', () => {
|
|
113
|
+
vi.useFakeTimers();
|
|
114
|
+
const transport = createWebSocketTransport();
|
|
115
|
+
const statuses: TransportStatus[] = [];
|
|
116
|
+
transport.onStatusChange(s => statuses.push(s));
|
|
117
|
+
|
|
118
|
+
transport.connect('ws://localhost:9506/chat', {
|
|
119
|
+
reconnect: true,
|
|
120
|
+
reconnectInterval: 1000,
|
|
121
|
+
maxReconnectAttempts: 3,
|
|
122
|
+
});
|
|
123
|
+
mockWs.onopen?.();
|
|
124
|
+
mockWs.onclose?.();
|
|
125
|
+
|
|
126
|
+
expect(statuses).toContain('reconnecting');
|
|
127
|
+
|
|
128
|
+
vi.advanceTimersByTime(1000);
|
|
129
|
+
// reconnect creates a new WebSocket — verify by checking status went to reconnecting
|
|
130
|
+
expect(statuses).toContain('reconnecting');
|
|
131
|
+
|
|
132
|
+
vi.useRealTimers();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('does not reconnect when reconnect is disabled', () => {
|
|
136
|
+
const transport = createWebSocketTransport();
|
|
137
|
+
const statuses: TransportStatus[] = [];
|
|
138
|
+
transport.onStatusChange(s => statuses.push(s));
|
|
139
|
+
|
|
140
|
+
transport.connect('ws://localhost:9506/chat', { reconnect: false });
|
|
141
|
+
mockWs.onclose?.();
|
|
142
|
+
|
|
143
|
+
expect(statuses).not.toContain('reconnecting');
|
|
144
|
+
});
|
|
145
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description Socket.IO transport — speaks ServerPushEvent protocol over Socket.IO
|
|
3
|
+
* @author 阿怪
|
|
4
|
+
* @date 2026/5/7
|
|
5
|
+
* @version v0.0.1
|
|
6
|
+
*
|
|
7
|
+
* 江湖的业务千篇一律,复杂的代码好几百行。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { io, type Socket } from 'socket.io-client';
|
|
11
|
+
import type {
|
|
12
|
+
ChatTransport,
|
|
13
|
+
OutgoingMessage,
|
|
14
|
+
ServerPushEvent,
|
|
15
|
+
TransportOptions,
|
|
16
|
+
TransportStatus,
|
|
17
|
+
} from './types';
|
|
18
|
+
|
|
19
|
+
export function createSocketIOTransport(): ChatTransport {
|
|
20
|
+
let socket: Socket | null = null;
|
|
21
|
+
|
|
22
|
+
const eventHandlers: Array<(event: ServerPushEvent) => void> = [];
|
|
23
|
+
const statusHandlers: Array<(status: TransportStatus) => void> = [];
|
|
24
|
+
|
|
25
|
+
function emitStatus(status: TransportStatus) {
|
|
26
|
+
statusHandlers.forEach(h => h(status));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function connect(url: string, options: TransportOptions = {}) {
|
|
30
|
+
emitStatus('connecting');
|
|
31
|
+
|
|
32
|
+
socket = io(url, {
|
|
33
|
+
auth: options.auth,
|
|
34
|
+
extraHeaders: options.headers,
|
|
35
|
+
reconnection: options.reconnect ?? true,
|
|
36
|
+
reconnectionAttempts: options.maxReconnectAttempts ?? 5,
|
|
37
|
+
reconnectionDelay: options.reconnectInterval ?? 3000,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
socket.on('connect', () => {
|
|
41
|
+
emitStatus('connected');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
socket.on('disconnect', () => {
|
|
45
|
+
emitStatus('disconnected');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
socket.io.on('reconnect_attempt', () => {
|
|
49
|
+
emitStatus('reconnecting');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
socket.on('chat:event', (event: ServerPushEvent) => {
|
|
53
|
+
eventHandlers.forEach(h => h(event));
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function disconnect() {
|
|
58
|
+
socket?.disconnect();
|
|
59
|
+
socket = null;
|
|
60
|
+
emitStatus('disconnected');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function send(message: OutgoingMessage) {
|
|
64
|
+
socket?.emit('chat:send', message);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function onEvent(handler: (event: ServerPushEvent) => void) {
|
|
68
|
+
eventHandlers.push(handler);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function onStatusChange(handler: (status: TransportStatus) => void) {
|
|
72
|
+
statusHandlers.push(handler);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return { connect, disconnect, send, onEvent, onStatusChange };
|
|
76
|
+
}
|
package/composables/types.ts
CHANGED
|
@@ -77,6 +77,7 @@ export type TransportStatus = 'connecting' | 'connected' | 'disconnected' | 'rec
|
|
|
77
77
|
export interface TransportOptions {
|
|
78
78
|
protocols?: string[];
|
|
79
79
|
headers?: Record<string, string>;
|
|
80
|
+
auth?: Record<string, string>;
|
|
80
81
|
reconnect?: boolean;
|
|
81
82
|
reconnectInterval?: number;
|
|
82
83
|
maxReconnectAttempts?: number;
|
package/dist/ai-chat.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { computed, createTextVNode, createVNode, defineComponent, nextTick, onMounted, onUnmounted, ref, watch } from "vue";
|
|
2
|
+
import { io } from "socket.io-client";
|
|
2
3
|
import KLoading from "kine-ui/components/loading/KLoading.tsx";
|
|
3
4
|
//#region composables/useChat.ts
|
|
4
5
|
/**
|
|
@@ -254,6 +255,67 @@ function createWebSocketTransport() {
|
|
|
254
255
|
};
|
|
255
256
|
}
|
|
256
257
|
//#endregion
|
|
258
|
+
//#region composables/createSocketIOTransport.ts
|
|
259
|
+
/**
|
|
260
|
+
* @description Socket.IO transport — speaks ServerPushEvent protocol over Socket.IO
|
|
261
|
+
* @author 阿怪
|
|
262
|
+
* @date 2026/5/7
|
|
263
|
+
* @version v0.0.1
|
|
264
|
+
*
|
|
265
|
+
* 江湖的业务千篇一律,复杂的代码好几百行。
|
|
266
|
+
*/
|
|
267
|
+
function createSocketIOTransport() {
|
|
268
|
+
let socket = null;
|
|
269
|
+
const eventHandlers = [];
|
|
270
|
+
const statusHandlers = [];
|
|
271
|
+
function emitStatus(status) {
|
|
272
|
+
statusHandlers.forEach((h) => h(status));
|
|
273
|
+
}
|
|
274
|
+
function connect(url, options = {}) {
|
|
275
|
+
emitStatus("connecting");
|
|
276
|
+
socket = io(url, {
|
|
277
|
+
auth: options.auth,
|
|
278
|
+
extraHeaders: options.headers,
|
|
279
|
+
reconnection: options.reconnect ?? true,
|
|
280
|
+
reconnectionAttempts: options.maxReconnectAttempts ?? 5,
|
|
281
|
+
reconnectionDelay: options.reconnectInterval ?? 3e3
|
|
282
|
+
});
|
|
283
|
+
socket.on("connect", () => {
|
|
284
|
+
emitStatus("connected");
|
|
285
|
+
});
|
|
286
|
+
socket.on("disconnect", () => {
|
|
287
|
+
emitStatus("disconnected");
|
|
288
|
+
});
|
|
289
|
+
socket.io.on("reconnect_attempt", () => {
|
|
290
|
+
emitStatus("reconnecting");
|
|
291
|
+
});
|
|
292
|
+
socket.on("chat:event", (event) => {
|
|
293
|
+
eventHandlers.forEach((h) => h(event));
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
function disconnect() {
|
|
297
|
+
socket?.disconnect();
|
|
298
|
+
socket = null;
|
|
299
|
+
emitStatus("disconnected");
|
|
300
|
+
}
|
|
301
|
+
function send(message) {
|
|
302
|
+
socket?.emit("chat:send", message);
|
|
303
|
+
}
|
|
304
|
+
function onEvent(handler) {
|
|
305
|
+
eventHandlers.push(handler);
|
|
306
|
+
}
|
|
307
|
+
function onStatusChange(handler) {
|
|
308
|
+
statusHandlers.push(handler);
|
|
309
|
+
}
|
|
310
|
+
return {
|
|
311
|
+
connect,
|
|
312
|
+
disconnect,
|
|
313
|
+
send,
|
|
314
|
+
onEvent,
|
|
315
|
+
onStatusChange
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
//#endregion
|
|
257
319
|
//#region components/KMessageBubble.tsx
|
|
258
320
|
/**
|
|
259
321
|
* @description Single message bubble — supports streaming content
|
|
@@ -433,4 +495,4 @@ var KChatInput = /* @__PURE__ */ defineComponent({
|
|
|
433
495
|
}
|
|
434
496
|
});
|
|
435
497
|
//#endregion
|
|
436
|
-
export { KChatInput, KChatPanel, KMessageBubble, KMessageCluster, KPhaseIndicator, createWebSocketTransport, useChat, useChatHistory };
|
|
498
|
+
export { KChatInput, KChatPanel, KMessageBubble, KMessageCluster, KPhaseIndicator, createSocketIOTransport, createWebSocketTransport, useChat, useChatHistory };
|
|
@@ -59,6 +59,7 @@ export type TransportStatus = 'connecting' | 'connected' | 'disconnected' | 'rec
|
|
|
59
59
|
export interface TransportOptions {
|
|
60
60
|
protocols?: string[];
|
|
61
61
|
headers?: Record<string, string>;
|
|
62
|
+
auth?: Record<string, string>;
|
|
62
63
|
reconnect?: boolean;
|
|
63
64
|
reconnectInterval?: number;
|
|
64
65
|
maxReconnectAttempts?: number;
|
package/dist/index.d.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
export { useChat } from './composables/useChat';
|
|
10
10
|
export { useChatHistory } from './composables/useChatHistory';
|
|
11
11
|
export { createWebSocketTransport } from './composables/createWebSocketTransport';
|
|
12
|
+
export { createSocketIOTransport } from './composables/createSocketIOTransport';
|
|
12
13
|
export type { MessageRole, MessageStatus, ChatMessage, MessageCluster, ChatPhase, Conversation, ServerPushEvent, FragmentEvent, PhaseChangeEvent, ConversationMetaEvent, ChatTransport, TransportStatus, TransportOptions, OutgoingMessage, UseChatOptions, UseChatReturn, UseChatHistoryOptions, UseChatHistoryReturn, } from './composables/types';
|
|
13
14
|
export { KChatPanel } from './components/KChatPanel';
|
|
14
15
|
export { KMessageCluster } from './components/KMessageCluster';
|
package/index.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
export { useChat } from './composables/useChat';
|
|
11
11
|
export { useChatHistory } from './composables/useChatHistory';
|
|
12
12
|
export { createWebSocketTransport } from './composables/createWebSocketTransport';
|
|
13
|
+
export { createSocketIOTransport } from './composables/createSocketIOTransport';
|
|
13
14
|
|
|
14
15
|
export type {
|
|
15
16
|
MessageRole,
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kine-design/ai-chat",
|
|
3
|
-
"version": "0.0.1-beta.
|
|
3
|
+
"version": "0.0.1-beta.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/ai-chat.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
7
7
|
"dependencies": {
|
|
8
|
+
"socket.io-client": "^4.8.3",
|
|
8
9
|
"vue": "^3.5.30",
|
|
9
|
-
"kine-ui": "0.0.1-beta.
|
|
10
|
+
"kine-ui": "0.0.1-beta.17"
|
|
10
11
|
},
|
|
11
12
|
"publishConfig": {
|
|
12
13
|
"access": "public",
|
|
@@ -14,8 +15,12 @@
|
|
|
14
15
|
"dist"
|
|
15
16
|
]
|
|
16
17
|
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"vitest": "^4.1.0"
|
|
20
|
+
},
|
|
17
21
|
"scripts": {
|
|
18
|
-
"build": "vite build --config vite.config.build.ts"
|
|
22
|
+
"build": "vite build --config vite.config.build.ts",
|
|
23
|
+
"test": "vitest --run"
|
|
19
24
|
},
|
|
20
25
|
"module": "./dist/ai-chat.js",
|
|
21
26
|
"exports": {
|
package/vite.config.build.ts
CHANGED