UNPKG

cozy-search

Version:

UI components about search bar and IA assistant

278 lines (236 loc) 7.86 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.StreamBridge = void 0; var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); /** * StreamBridge bridges WebSocket events from cozy-realtime to async iterables * that can be consumed by assistant-ui's ChatModelAdapter. * * The bridge receives push-based events from WebSocket and converts them * to a pull-based async iterator pattern. */ var StreamBridge = /*#__PURE__*/function () { function StreamBridge() { (0, _classCallCheck2.default)(this, StreamBridge); (0, _defineProperty2.default)(this, "streams", new Map()); (0, _defineProperty2.default)(this, "cleanupCallback", null); (0, _defineProperty2.default)(this, "positionBuffers", new Map()); (0, _defineProperty2.default)(this, "nextPositions", new Map()); (0, _defineProperty2.default)(this, "sourcesMap", new Map()); } (0, _createClass2.default)(StreamBridge, [{ key: "setCleanupCallback", value: /** * Sets a callback to be invoked when cleanup is called. * This allows the provider to mark the current message as cancelled. */ function setCleanupCallback(callback) { this.cleanupCallback = callback; } /** * Creates a new async iterable stream for a conversation. * The stream will yield string chunks as they arrive via onDelta(). */ }, { key: "createStream", value: function createStream(conversationId) { this.cleanup(conversationId); var queue = []; var resolveNext = null; var rejectNext = null; var isDone = false; var _error = null; var controller = { push: function push(chunk) { if (isDone) return; if (resolveNext) { resolveNext({ value: chunk, done: false }); resolveNext = null; rejectNext = null; } else { queue.push(chunk); } }, complete: function complete() { if (isDone) return; isDone = true; if (resolveNext) { resolveNext({ value: undefined, done: true }); resolveNext = null; rejectNext = null; } }, error: function error(err) { if (isDone) return; isDone = true; _error = err; if (rejectNext) { rejectNext(err); resolveNext = null; rejectNext = null; } }, iterator: (0, _defineProperty2.default)({ next: function next() { return new Promise(function (resolve, reject) { if (_error) { reject(_error); return; } if (queue.length > 0) { resolve({ value: queue.shift(), done: false }); } else if (isDone) { resolve({ value: undefined, done: true }); } else { if (resolveNext) { reject(new Error('StreamBridge: concurrent next() calls are not supported')); return; } resolveNext = resolve; rejectNext = reject; } }); } }, Symbol.asyncIterator, function () { return this; }) }; this.streams.set(conversationId, controller); return controller.iterator; } /** * Called when a delta event is received from WebSocket. * When a position is provided, chunks are buffered and flushed in order. * Without a position, chunks are pushed directly in arrival order. */ }, { key: "onDelta", value: function onDelta(conversationId, content, position) { var _this$nextPositions$g; var stream = this.streams.get(conversationId); if (!stream) return; if (position === undefined) { stream.push(content); return; } // Fast path: chunk arrived in order, no buffering needed var nextExpected = (_this$nextPositions$g = this.nextPositions.get(conversationId)) !== null && _this$nextPositions$g !== void 0 ? _this$nextPositions$g : 0; if (position === nextExpected) { stream.push(content); var next = nextExpected + 1; // Flush any previously buffered chunks that are now contiguous var _buffer = this.positionBuffers.get(conversationId); if (_buffer && _buffer.size > 0) { while (_buffer.has(next)) { stream.push(_buffer.get(next)); _buffer.delete(next); next++; } if (_buffer.size === 0) { this.positionBuffers.delete(conversationId); } } this.nextPositions.set(conversationId, next); return; } // Out-of-order: buffer until the gap is filled var buffer = this.positionBuffers.get(conversationId); if (!buffer) { buffer = new Map(); this.positionBuffers.set(conversationId, buffer); } buffer.set(position, content); } /** * Called when a 'done' event is received from WebSocket. * Completes the stream for the conversation. */ }, { key: "onDone", value: function onDone(conversationId) { var stream = this.streams.get(conversationId); if (stream) { stream.complete(); this.positionBuffers.delete(conversationId); this.nextPositions.delete(conversationId); } } /** * Called when a 'sources' event is received from WebSocket. * Stores sources for the conversation to be retrieved by the adapter. */ }, { key: "onSources", value: function onSources(conversationId, sources) { if (!this.streams.has(conversationId)) return; this.sourcesMap.set(conversationId, sources); } /** * Returns stored sources for a conversation. */ }, { key: "getSources", value: function getSources(conversationId) { return this.sourcesMap.get(conversationId); } /** * Called when an error occurs. * Errors the stream for the conversation. */ }, { key: "onError", value: function onError(conversationId, error) { var stream = this.streams.get(conversationId); if (stream) { stream.error(error); } this.positionBuffers.delete(conversationId); this.nextPositions.delete(conversationId); this.sourcesMap.delete(conversationId); } /** * Cleans up the stream for a conversation. * Should be called when navigating away or on unmount. */ }, { key: "cleanup", value: function cleanup(conversationId) { var stream = this.streams.get(conversationId); if (stream) { if (this.cleanupCallback) { this.cleanupCallback(); } stream.complete(); this.streams.delete(conversationId); } this.positionBuffers.delete(conversationId); this.nextPositions.delete(conversationId); this.sourcesMap.delete(conversationId); } /** * Checks if there's an active stream for a conversation. */ }, { key: "hasStream", value: function hasStream(conversationId) { return this.streams.has(conversationId); } }]); return StreamBridge; }(); exports.StreamBridge = StreamBridge;