UNPKG

@charisma-ai/sdk

Version:

Charisma.ai SDK for Javascript (browser)

795 lines (653 loc) 21.8 kB
import fetch from 'isomorphic-unfetch'; import querystring from 'query-string'; import EventEmitter from 'eventemitter3'; import { Client } from 'colyseus.js'; import jwtDecode from 'jwt-decode'; import PQueue from 'p-queue'; function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } const fetchHelper = async (endpoint, options = {}) => { let headers = _extends({ Accept: "application/json" }, options.headers); if (typeof options.method === "string" && options.method.toLowerCase() === "post") { headers = _extends({ "Content-Type": "application/json" }, headers); } const response = await fetch(endpoint, _extends({ mode: "cors" }, options, { headers })); let data = {}; try { data = await response.json(); } catch (err) {} if (!response.ok) { throw new Error(data.error || `Something went wrong calling \`${endpoint}\``); } return data; }; let globalBaseUrl = "https://play.charisma.ai"; const getGlobalBaseUrl = () => globalBaseUrl; const setGlobalBaseUrl = newBaseUrl => { globalBaseUrl = newBaseUrl; }; async function createPlaythroughToken(options, apiOptions) { if (options.version === -1 && options.userToken === undefined && options.apiKey === undefined) { throw new Error("To play the draft version (-1) of a story, an `apiKey` or `userToken` must also be passed."); } let authHeader; if (options.apiKey) { authHeader = `API-Key ${options.apiKey}`; } else if (options.userToken) { authHeader = `Bearer ${options.userToken}`; } try { const { token } = await fetchHelper(`${(apiOptions == null ? void 0 : apiOptions.baseUrl) || globalBaseUrl}/play/token`, { body: JSON.stringify({ storyId: options.storyId, version: options.version }), headers: authHeader ? { Authorization: authHeader } : undefined, method: "POST" }); return token; } catch (err) { throw new Error(`A playthrough token could not be generated: ${err}`); } } async function createConversation(token, apiOptions) { const { conversationId } = await fetchHelper(`${(apiOptions == null ? void 0 : apiOptions.baseUrl) || globalBaseUrl}/play/conversation`, { body: JSON.stringify({}), headers: { Authorization: `Bearer ${token}` }, method: "POST" }); return conversationId; } async function createCharacterConversation(token, characterId, apiOptions) { const { conversationId } = await fetchHelper(`${(apiOptions == null ? void 0 : apiOptions.baseUrl) || globalBaseUrl}/play/conversation/character`, { body: JSON.stringify({ characterId }), headers: { Authorization: `Bearer ${token}` }, method: "POST" }); return conversationId; } async function getMessageHistory(token, conversationId, minEventId, apiOptions) { const query = querystring.stringify({ conversationId, minEventId }); const result = await fetchHelper(`${(apiOptions == null ? void 0 : apiOptions.baseUrl) || globalBaseUrl}/play/message-history?${query}`, { headers: { Authorization: `Bearer ${token}` }, method: "GET" }); return result; } async function getPlaythroughInfo(token, apiOptions) { const result = await fetchHelper(`${(apiOptions == null ? void 0 : apiOptions.baseUrl) || globalBaseUrl}/play/playthrough-info`, { headers: { Authorization: `Bearer ${token}` }, method: "GET" }); return result; } async function setMemory(token, memoryIdOrRecallValue, saveValue, apiOptions) { await fetchHelper(`${(apiOptions == null ? void 0 : apiOptions.baseUrl) || globalBaseUrl}/play/set-memory`, { body: JSON.stringify(_extends({}, typeof memoryIdOrRecallValue === "number" ? { memoryId: memoryIdOrRecallValue } : { memoryRecallValue: memoryIdOrRecallValue }, { saveValue })), headers: { Authorization: `Bearer ${token}` }, method: "POST" }); } async function restartFromEpisodeId(token, episodeId, apiOptions) { await fetchHelper(`${(apiOptions == null ? void 0 : apiOptions.baseUrl) || globalBaseUrl}/play/restart-from-episode`, { body: JSON.stringify({ episodeId }), headers: { Authorization: `Bearer ${token}` }, method: "POST" }); } async function restartFromEpisodeIndex(token, episodeIndex, apiOptions) { await fetchHelper(`${(apiOptions == null ? void 0 : apiOptions.baseUrl) || globalBaseUrl}/play/restart-from-episode`, { body: JSON.stringify({ episodeIndex }), headers: { Authorization: `Bearer ${token}` }, method: "POST" }); } async function restartFromEventId(token, eventId, apiOptions) { await fetchHelper(`${(apiOptions == null ? void 0 : apiOptions.baseUrl) || globalBaseUrl}/play/restart-from-event`, { body: JSON.stringify({ eventId }), headers: { Authorization: `Bearer ${token}` }, method: "POST" }); } var api = { __proto__: null, getGlobalBaseUrl: getGlobalBaseUrl, setGlobalBaseUrl: setGlobalBaseUrl, createPlaythroughToken: createPlaythroughToken, createConversation: createConversation, createCharacterConversation: createCharacterConversation, getMessageHistory: getMessageHistory, getPlaythroughInfo: getPlaythroughInfo, setMemory: setMemory, restartFromEpisodeId: restartFromEpisodeId, restartFromEpisodeIndex: restartFromEpisodeIndex, restartFromEventId: restartFromEventId }; class Conversation extends EventEmitter { constructor(conversationId, playthroughInstance, options) { var _this; super(); _this = this; this.eventQueue = new PQueue(); this.options = {}; this.addIncomingEvent = (eventName, ...eventArgs) => { this.eventQueue.add(() => this.emit(eventName, ...eventArgs)); return true; }; this.start = (event = {}) => { return this.playthroughInstance.addOutgoingEvent("start", _extends({}, this.options, event, { conversationId: this.id })); }; this.reply = event => { return this.playthroughInstance.addOutgoingEvent("reply", _extends({}, this.options, event, { conversationId: this.id })); }; this.tap = () => { return this.playthroughInstance.addOutgoingEvent("tap", _extends({}, this.options, { conversationId: this.id })); }; this.action = event => { return this.playthroughInstance.addOutgoingEvent("action", _extends({}, this.options, event, { conversationId: this.id })); }; this.resume = () => { return this.playthroughInstance.addOutgoingEvent("resume", _extends({}, this.options, { conversationId: this.id })); }; this.setSpeechConfig = speechConfig => { this.options.speechConfig = speechConfig; }; this.reconnect = async function () { if (typeof _this.lastEventId === "string") { _this.eventQueue.pause(); try { const { messages } = await _this.playthroughInstance.getMessageHistory(_this.id, _this.lastEventId); if (messages.length > 0) { _this.emit("playback-start"); messages.forEach(message => { if (typeof BigInt === "undefined") { if (message.timestamp > _this.lastTimestamp) { _this.emit("message", _extends({}, message, { conversationId: _this.id })); } } else if (BigInt(message.eventId) > BigInt(_this.lastEventId)) { _this.emit("message", _extends({}, message, { conversationId: _this.id })); } }); _this.emit("playback-stop"); } } finally { _this.eventQueue.start(); } } }; this.id = conversationId; this.playthroughInstance = playthroughInstance; if (options) { this.options = options; } this.on("message", message => { this.lastEventId = message.eventId; this.lastTimestamp = message.timestamp; }); } } class Playthrough extends EventEmitter { constructor(token, baseUrl) { var _this; super(); _this = this; this.connectionStatus = "disconnected"; this.shouldReconnect = true; this.activeConversations = new Map(); this.joinConversation = (conversationId, options) => { const conversation = new Conversation(conversationId, this, options); if (this.activeConversations.has(conversationId)) { return this.activeConversations.get(conversationId); } this.activeConversations.set(conversationId, conversation); return conversation; }; this.leaveConversation = conversationId => { if (!this.activeConversations.has(conversationId)) { throw new Error(`The conversation with id \`${conversationId}\` has not been joined, so cannot be left.`); } this.activeConversations.delete(conversationId); }; this.getConversation = conversationId => { return this.activeConversations.get(conversationId); }; this.addOutgoingEvent = (eventName, eventData) => { if (this.room) { if (this.connectionStatus === "connected") { this.room.send(eventName, eventData); } else { console.warn(`Event \`${eventName}\` was not sent as the socket was not ready. Wait for the \`connection-status\` event to be called with \`connected\` before sending events.`); } } else { console.log(`Event \`${eventName}\` was not sent as the socket was not initialised. Call \`playthrough.connect()\` to connect the socket.`); } }; this.connect = async function () { const baseUrl = _this.baseUrl || getGlobalBaseUrl(); if (!_this.client) { _this.client = new Client(baseUrl.replace(/^http/, "ws")); } _this.room = await _this.client.joinOrCreate("chat", { playthroughId: _this.playthroughId, token: _this.token }); _this.attachRoomHandlers(_this.room); _this.shouldReconnect = true; }; this.pause = () => { this.addOutgoingEvent("pause"); }; this.play = () => { this.addOutgoingEvent("play"); }; this.attachRoomHandlers = room => { room.onMessage("status", this.onConnected); room.onMessage("problem", this.onProblem); room.onMessage("start-typing", this.onStartTyping); room.onMessage("stop-typing", this.onStopTyping); room.onMessage("message", this.onMessage); room.onMessage("episode-complete", this.onEpisodeComplete); room.onError(this.onError); room.onLeave(async function (code) { room.removeAllListeners(); _this.room = undefined; if (code === 4000 || !_this.shouldReconnect) { _this.onDisconnect(); return; } let roomExpired = false; for (let attempts = 0; attempts < 20; attempts += 1) { if (!roomExpired) { try { var _this$client; _this.onReconnecting(); const newRoom = await ((_this$client = _this.client) == null ? void 0 : _this$client.reconnect(room.id, room.sessionId)); if (newRoom) { _this.attachRoomHandlers(newRoom); _this.room = newRoom; _this.onReconnect(); _this.onConnected(); return; } } catch (err) { if (/room ".*" not found/.test(err.message)) { roomExpired = true; } } } if (roomExpired) { try { var _this$client2; const newRoom = await ((_this$client2 = _this.client) == null ? void 0 : _this$client2.joinOrCreate("chat", { playthroughId: _this.playthroughId, token: _this.token })); if (newRoom) { _this.attachRoomHandlers(newRoom); _this.room = newRoom; _this.onReconnect(); _this.onConnected(); return; } } catch (err2) { console.error("Could not reconnect to a Charisma playthrough.", err2); } } await new Promise(resolve => setTimeout(() => resolve(), 5000 + Math.floor(Math.random() * 1000))); } _this.onDisconnect(); }); }; this.disconnect = () => { this.shouldReconnect = false; if (this.room) { this.room.leave(); } }; this.changeConnectionStatus = newStatus => { if (newStatus !== this.connectionStatus) { this.connectionStatus = newStatus; this.emit("connection-status", newStatus); } }; this.onReconnect = () => { this.activeConversations.forEach(conversation => { conversation.reconnect().catch(err => { console.error(`Something went wrong reconnecting to conversation:`, err); }); }); }; this.onReconnecting = () => { this.changeConnectionStatus("connecting"); }; this.onDisconnect = () => { this.changeConnectionStatus("disconnected"); }; this.onConnected = () => { this.changeConnectionStatus("connected"); }; this.onError = (code, message) => { this.emit("error", { message, code }); }; this.onProblem = problem => { this.emit("problem", problem); }; this.onStartTyping = event => { const conversation = this.activeConversations.get(event.conversationId); if (conversation) { conversation.addIncomingEvent("start-typing", event); } }; this.onStopTyping = event => { const conversation = this.activeConversations.get(event.conversationId); if (conversation) { conversation.addIncomingEvent("stop-typing", event); } }; this.onMessage = event => { const conversation = this.activeConversations.get(event.conversationId); if (conversation) { conversation.addIncomingEvent("message", event); } }; this.onEpisodeComplete = event => { const conversation = this.activeConversations.get(event.conversationId); if (conversation) { conversation.addIncomingEvent("episode-complete", event); } }; this.token = token; const { playthrough_id: playthroughId } = jwtDecode(this.token); this.playthroughId = playthroughId; this.baseUrl = baseUrl; } createConversation() { return createConversation(this.token, { baseUrl: this.baseUrl }); } createCharacterConversation(characterId) { return createCharacterConversation(this.token, characterId, { baseUrl: this.baseUrl }); } getMessageHistory(conversationId, minEventId) { return getMessageHistory(this.token, conversationId, minEventId, { baseUrl: this.baseUrl }); } getPlaythroughInfo() { return getPlaythroughInfo(this.token, { baseUrl: this.baseUrl }); } setMemory(memoryIdOrRecallValue, saveValue) { return setMemory(this.token, memoryIdOrRecallValue, saveValue, { baseUrl: this.baseUrl }); } restartFromEpisodeId(episodeId) { return restartFromEpisodeId(this.token, episodeId, { baseUrl: this.baseUrl }); } restartFromEpisodeIndex(episodeIndex) { return restartFromEpisodeIndex(this.token, episodeIndex, { baseUrl: this.baseUrl }); } restartFromEventId(eventId) { return restartFromEventId(this.token, eventId, { baseUrl: this.baseUrl }); } } const SpeechRecognitionClass = typeof window !== "undefined" ? window.SpeechRecognition || window.webkitSpeechRecognition : undefined; class Microphone extends EventEmitter { constructor() { super(...arguments); this.recognition = SpeechRecognitionClass ? new SpeechRecognitionClass() : undefined; this.isSupported = SpeechRecognitionClass !== undefined; this.startListening = ({ continuous: _continuous = false, interimResults: _interimResults = true, lang: _lang = "en-GB", timeout } = {}) => { if (!this.recognition) { return; } if (this.timeoutId !== undefined) { clearTimeout(this.timeoutId); } const { recognition } = this; recognition.continuous = _continuous; recognition.interimResults = _interimResults; recognition.lang = _lang; recognition.onresult = this.onRecognitionResult; recognition.onstart = () => { this.emit("start"); }; recognition.onend = () => { this.emit("stop"); recognition.start(); }; recognition.onerror = event => { this.emit("error", event.error); }; try { recognition.start(); } catch (err) {} if (timeout !== undefined) { this.timeoutId = window.setTimeout(this.onTimeout, timeout); } }; this.stopListening = ({ waitForLastResult: _waitForLastResult = false } = {}) => { if (this.timeoutId !== undefined) { clearTimeout(this.timeoutId); } const { recognition } = this; if (recognition) { if (!_waitForLastResult) { recognition.onresult = () => undefined; } recognition.onend = () => { this.emit("stop"); }; try { if (_waitForLastResult) { recognition.stop(); } else { recognition.abort(); } } catch (err) {} } }; this.resetTimeout = timeout => { if (this.timeoutId !== undefined) { clearTimeout(this.timeoutId); } this.timeoutId = window.setTimeout(this.onTimeout, timeout); }; this.onTimeout = () => { this.timeoutId = undefined; this.emit("timeout"); this.stopListening(); }; this.onRecognitionResult = event => { if (event.results && event.results[0] && event.results[0][0]) { const message = event.results[0][0].transcript.trim(); if (event.results[0].isFinal === false) { this.emit("recognise-interim", message); } else { this.emit("recognise", message); } } }; } } class Speaker extends EventEmitter { constructor() { var _this; super(...arguments); _this = this; this.currentSources = []; this.getAudioContext = () => { if (this.audioContext) { return this.audioContext; } const AudioContextClass = window.AudioContext || window.webkitAudioContext; if (!AudioContextClass) { throw new Error("AudioContext isn't supported in this browser."); } const audioContext = new AudioContextClass(); this.audioContext = audioContext; return audioContext; }; this.play = async function (audio, options = {}) { if (typeof options === "boolean") { console.warn("Passing a boolean as the second parameter to `speaker.play()` is deprecated, and should be updated to use an `options` object."); options = { interrupt: options ? "all" : "none" }; } const { interrupt = "none", trackId } = options; const audioContext = _this.getAudioContext(); const source = audioContext.createBufferSource(); source.connect(audioContext.destination); source.buffer = await new Promise((resolve, reject) => { audioContext.decodeAudioData(audio, resolve, reject); }); if (audioContext.state !== "running") { return Promise.resolve(); } return new Promise(resolve => { source.onended = () => { resolve(); _this.currentSources = _this.currentSources.filter(currentSource => currentSource.sourceNode !== source); if (_this.currentSources.length === 0) { _this.emit("stop"); } }; if (_this.currentSources.length > 0 && interrupt !== "none") { _this.currentSources.forEach(currentSource => { if (interrupt === "all" || interrupt === "track" && currentSource.trackId === trackId) { currentSource.sourceNode.stop(); } }); } if (_this.currentSources.length === 0) { _this.emit("start"); } _this.currentSources.push({ sourceNode: source, trackId }); source.start(); }); }; } } var ImageLayerResizeMode; (function (ImageLayerResizeMode) { ImageLayerResizeMode["Contain"] = "contain"; ImageLayerResizeMode["Cover"] = "cover"; })(ImageLayerResizeMode || (ImageLayerResizeMode = {})); var AudioTrackBehaviour; (function (AudioTrackBehaviour) { AudioTrackBehaviour["Continue"] = "continue"; AudioTrackBehaviour["Restart"] = "restart"; })(AudioTrackBehaviour || (AudioTrackBehaviour = {})); export { AudioTrackBehaviour, Conversation, ImageLayerResizeMode, Microphone, Playthrough, Speaker, api, createCharacterConversation, createConversation, createPlaythroughToken, getGlobalBaseUrl, getMessageHistory, getPlaythroughInfo, restartFromEpisodeId, restartFromEpisodeIndex, restartFromEventId, setGlobalBaseUrl, setMemory }; //# sourceMappingURL=index.modern.js.map