cozy-search 0.19.0 → 0.19.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.
|
@@ -202,7 +202,7 @@ var CozyAssistantRuntimeProviderInner = function CozyAssistantRuntimeProviderInn
|
|
|
202
202
|
|
|
203
203
|
try {
|
|
204
204
|
if (res.object === 'delta' && res.content !== undefined) {
|
|
205
|
-
streamBridgeRef.current.onDelta(conversationId, res.content);
|
|
205
|
+
streamBridgeRef.current.onDelta(conversationId, res.content, res.position);
|
|
206
206
|
}
|
|
207
207
|
|
|
208
208
|
if (res.object === 'done') {
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
export declare class StreamBridge {
|
|
9
9
|
private streams;
|
|
10
10
|
private cleanupCallback;
|
|
11
|
+
private positionBuffers;
|
|
12
|
+
private nextPositions;
|
|
11
13
|
/**
|
|
12
14
|
* Sets a callback to be invoked when cleanup is called.
|
|
13
15
|
* This allows the provider to mark the current message as cancelled.
|
|
@@ -20,9 +22,10 @@ export declare class StreamBridge {
|
|
|
20
22
|
createStream(conversationId: string): AsyncIterableIterator<string>;
|
|
21
23
|
/**
|
|
22
24
|
* Called when a delta event is received from WebSocket.
|
|
23
|
-
*
|
|
25
|
+
* When a position is provided, chunks are buffered and flushed in order.
|
|
26
|
+
* Without a position, chunks are pushed directly in arrival order.
|
|
24
27
|
*/
|
|
25
|
-
onDelta(conversationId: string, content: string): void;
|
|
28
|
+
onDelta(conversationId: string, content: string, position?: number): void;
|
|
26
29
|
/**
|
|
27
30
|
* Called when a 'done' event is received from WebSocket.
|
|
28
31
|
* Completes the stream for the conversation.
|
|
@@ -25,6 +25,8 @@ var StreamBridge = /*#__PURE__*/function () {
|
|
|
25
25
|
(0, _classCallCheck2.default)(this, StreamBridge);
|
|
26
26
|
(0, _defineProperty2.default)(this, "streams", new Map());
|
|
27
27
|
(0, _defineProperty2.default)(this, "cleanupCallback", null);
|
|
28
|
+
(0, _defineProperty2.default)(this, "positionBuffers", new Map());
|
|
29
|
+
(0, _defineProperty2.default)(this, "nextPositions", new Map());
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
(0, _createClass2.default)(StreamBridge, [{
|
|
@@ -128,17 +130,59 @@ var StreamBridge = /*#__PURE__*/function () {
|
|
|
128
130
|
}
|
|
129
131
|
/**
|
|
130
132
|
* Called when a delta event is received from WebSocket.
|
|
131
|
-
*
|
|
133
|
+
* When a position is provided, chunks are buffered and flushed in order.
|
|
134
|
+
* Without a position, chunks are pushed directly in arrival order.
|
|
132
135
|
*/
|
|
133
136
|
|
|
134
137
|
}, {
|
|
135
138
|
key: "onDelta",
|
|
136
|
-
value: function onDelta(conversationId, content) {
|
|
139
|
+
value: function onDelta(conversationId, content, position) {
|
|
140
|
+
var _this$nextPositions$g;
|
|
141
|
+
|
|
137
142
|
var stream = this.streams.get(conversationId);
|
|
143
|
+
if (!stream) return;
|
|
138
144
|
|
|
139
|
-
if (
|
|
145
|
+
if (position === undefined) {
|
|
146
|
+
stream.push(content);
|
|
147
|
+
return;
|
|
148
|
+
} // Fast path: chunk arrived in order, no buffering needed
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
var nextExpected = (_this$nextPositions$g = this.nextPositions.get(conversationId)) !== null && _this$nextPositions$g !== void 0 ? _this$nextPositions$g : 0;
|
|
152
|
+
|
|
153
|
+
if (position === nextExpected) {
|
|
140
154
|
stream.push(content);
|
|
155
|
+
var next = nextExpected + 1; // Flush any previously buffered chunks that are now contiguous
|
|
156
|
+
|
|
157
|
+
var _buffer = this.positionBuffers.get(conversationId);
|
|
158
|
+
|
|
159
|
+
if (_buffer && _buffer.size > 0) {
|
|
160
|
+
while (_buffer.has(next)) {
|
|
161
|
+
stream.push(_buffer.get(next));
|
|
162
|
+
|
|
163
|
+
_buffer.delete(next);
|
|
164
|
+
|
|
165
|
+
next++;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (_buffer.size === 0) {
|
|
169
|
+
this.positionBuffers.delete(conversationId);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
this.nextPositions.set(conversationId, next);
|
|
174
|
+
return;
|
|
175
|
+
} // Out-of-order: buffer until the gap is filled
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
var buffer = this.positionBuffers.get(conversationId);
|
|
179
|
+
|
|
180
|
+
if (!buffer) {
|
|
181
|
+
buffer = new Map();
|
|
182
|
+
this.positionBuffers.set(conversationId, buffer);
|
|
141
183
|
}
|
|
184
|
+
|
|
185
|
+
buffer.set(position, content);
|
|
142
186
|
}
|
|
143
187
|
/**
|
|
144
188
|
* Called when a 'done' event is received from WebSocket.
|
|
@@ -152,6 +196,8 @@ var StreamBridge = /*#__PURE__*/function () {
|
|
|
152
196
|
|
|
153
197
|
if (stream) {
|
|
154
198
|
stream.complete();
|
|
199
|
+
this.positionBuffers.delete(conversationId);
|
|
200
|
+
this.nextPositions.delete(conversationId);
|
|
155
201
|
}
|
|
156
202
|
}
|
|
157
203
|
/**
|
|
@@ -166,6 +212,8 @@ var StreamBridge = /*#__PURE__*/function () {
|
|
|
166
212
|
|
|
167
213
|
if (stream) {
|
|
168
214
|
stream.error(error);
|
|
215
|
+
this.positionBuffers.delete(conversationId);
|
|
216
|
+
this.nextPositions.delete(conversationId);
|
|
169
217
|
}
|
|
170
218
|
}
|
|
171
219
|
/**
|
|
@@ -185,6 +233,8 @@ var StreamBridge = /*#__PURE__*/function () {
|
|
|
185
233
|
|
|
186
234
|
stream.complete();
|
|
187
235
|
this.streams.delete(conversationId);
|
|
236
|
+
this.positionBuffers.delete(conversationId);
|
|
237
|
+
this.nextPositions.delete(conversationId);
|
|
188
238
|
}
|
|
189
239
|
}
|
|
190
240
|
/**
|
|
@@ -63,6 +63,54 @@ describe('StreamBridge', () => {
|
|
|
63
63
|
expect(result.done).toBe(true)
|
|
64
64
|
})
|
|
65
65
|
|
|
66
|
+
it('should reorder out-of-order deltas when position is provided', async () => {
|
|
67
|
+
const iterator = bridge.createStream('convo_1')
|
|
68
|
+
|
|
69
|
+
// Arrive out of order: position 1 before position 0
|
|
70
|
+
bridge.onDelta('convo_1', 'world!', 1)
|
|
71
|
+
bridge.onDelta('convo_1', 'Hello ', 0)
|
|
72
|
+
|
|
73
|
+
const first = await iterator.next()
|
|
74
|
+
expect(first).toEqual({ value: 'Hello ', done: false })
|
|
75
|
+
|
|
76
|
+
const second = await iterator.next()
|
|
77
|
+
expect(second).toEqual({ value: 'world!', done: false })
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('should flush buffered positions in order when gap is filled', async () => {
|
|
81
|
+
const iterator = bridge.createStream('convo_1')
|
|
82
|
+
|
|
83
|
+
// Positions 2 and 1 arrive before 0
|
|
84
|
+
bridge.onDelta('convo_1', 'c', 2)
|
|
85
|
+
bridge.onDelta('convo_1', 'b', 1)
|
|
86
|
+
// Nothing yielded yet since position 0 is missing
|
|
87
|
+
|
|
88
|
+
bridge.onDelta('convo_1', 'a', 0)
|
|
89
|
+
// Now all three should flush in order
|
|
90
|
+
|
|
91
|
+
const first = await iterator.next()
|
|
92
|
+
expect(first).toEqual({ value: 'a', done: false })
|
|
93
|
+
|
|
94
|
+
const second = await iterator.next()
|
|
95
|
+
expect(second).toEqual({ value: 'b', done: false })
|
|
96
|
+
|
|
97
|
+
const third = await iterator.next()
|
|
98
|
+
expect(third).toEqual({ value: 'c', done: false })
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('should push directly when no position is provided', async () => {
|
|
102
|
+
const iterator = bridge.createStream('convo_1')
|
|
103
|
+
|
|
104
|
+
bridge.onDelta('convo_1', 'Hello ')
|
|
105
|
+
bridge.onDelta('convo_1', 'world!')
|
|
106
|
+
|
|
107
|
+
const first = await iterator.next()
|
|
108
|
+
expect(first).toEqual({ value: 'Hello ', done: false })
|
|
109
|
+
|
|
110
|
+
const second = await iterator.next()
|
|
111
|
+
expect(second).toEqual({ value: 'world!', done: false })
|
|
112
|
+
})
|
|
113
|
+
|
|
66
114
|
it('multiple unresolved next calls should reject to prevent concurrency issues', async () => {
|
|
67
115
|
const iterator = bridge.createStream('convo_1')
|
|
68
116
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cozy-search",
|
|
3
3
|
"description": "UI components about search bar and IA assistant",
|
|
4
|
-
"version": "0.19.
|
|
4
|
+
"version": "0.19.2",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cozy Cloud",
|
|
7
7
|
"url": "https://github.com/cozy"
|
|
@@ -29,21 +29,21 @@
|
|
|
29
29
|
"cozy-bar": "^29.0.0",
|
|
30
30
|
"cozy-client": "^60.21.0",
|
|
31
31
|
"cozy-device-helper": "^4.0.3",
|
|
32
|
-
"cozy-flags": "^4.9.
|
|
33
|
-
"cozy-intent": "^2.31.
|
|
34
|
-
"cozy-logger": "^1.18.
|
|
32
|
+
"cozy-flags": "^4.9.1",
|
|
33
|
+
"cozy-intent": "^2.31.1",
|
|
34
|
+
"cozy-logger": "^1.18.1",
|
|
35
35
|
"cozy-minilog": "^3.10.1",
|
|
36
36
|
"cozy-pouch-link": "^54.0.0",
|
|
37
|
-
"cozy-realtime": "^5.9.
|
|
37
|
+
"cozy-realtime": "^5.9.1",
|
|
38
38
|
"cozy-ui": "^135.6.0",
|
|
39
|
-
"cozy-ui-plus": "^5.3.
|
|
39
|
+
"cozy-ui-plus": "^5.3.1",
|
|
40
40
|
"cross-fetch": "^4.0.0",
|
|
41
|
-
"jest": "
|
|
41
|
+
"jest": "30.3.0",
|
|
42
42
|
"react": "18.2.0",
|
|
43
43
|
"react-dom": "18.2.0",
|
|
44
44
|
"react-router-dom": "6.14.2",
|
|
45
45
|
"stylus": "^0.64.0",
|
|
46
|
-
"twake-i18n": "^0.4.
|
|
46
|
+
"twake-i18n": "^0.4.1",
|
|
47
47
|
"typescript": "5.5.2"
|
|
48
48
|
},
|
|
49
49
|
"files": [
|
|
@@ -94,5 +94,5 @@
|
|
|
94
94
|
"lint": "cd .. && yarn eslint --ext js,jsx,ts packages/cozy-search"
|
|
95
95
|
},
|
|
96
96
|
"types": "dist/index.d.ts",
|
|
97
|
-
"gitHead": "
|
|
97
|
+
"gitHead": "661b14497f4331618699916112fbdb1d5ed2d606"
|
|
98
98
|
}
|