@scoopika/react
Version:
Build interactive AI-powered React applications around AI agents
327 lines (323 loc) • 9.99 kB
JavaScript
// src/state.ts
import { AgentClient } from "@scoopika/client";
import { useState } from "react";
// src/utils/sleep.ts
function sleep(ms) {
if (typeof ms !== "number") {
ms = 0;
}
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
var sleep_default = sleep;
// src/state.ts
var setupRequest = (session_id, inputs, run_id, user_id) => {
const req = {
role: "user",
at: Date.now(),
session_id,
run_id: run_id || crypto.randomUUID(),
user_id,
request: inputs,
resolved_message: "PLACEHOLDER"
};
return req;
};
var agentPlaceholder = ({
session_id,
run_id,
audio,
tool_calls,
content
}) => {
const placeholder = {
role: "model",
at: Date.now(),
session_id,
run_id,
response: {
run_id,
session_id,
audio,
tool_calls,
content
}
};
return placeholder;
};
var sortedMessages = (messages) => messages.sort((a, b) => a.at - b.at);
function useChatState(agent, state_options) {
if (typeof agent === "string") {
agent = new AgentClient(agent);
}
const [agentInstance] = useState(agent);
const [session, setSession] = useState(
(state_options == null ? void 0 : state_options.session_id) ?? "session_" + crypto.randomUUID()
);
const [status, setStatus] = useState();
const [generating, setGenerating] = useState(false);
const [loading, setLoading] = useState(false);
const [messages, setMessages] = useState((state_options == null ? void 0 : state_options.messages) || []);
const [streamPlaceholder, setStreamPlaceholder] = useState(void 0);
const newRequest = async ({
inputs = {},
options,
hooks
} = {}) => {
try {
while (generating || loading) {
await sleep_default(5);
}
setLoading(true);
options = { ...options || {} };
options.session_id = session;
const request = setupRequest(session, inputs, options == null ? void 0 : options.run_id);
let run_id = request.run_id;
setStreamPlaceholder(
agentPlaceholder({
session_id: session,
run_id,
audio: [],
content: "",
tool_calls: []
})
);
setMessages((prev) => [...sortedMessages(prev), request]);
setStatus("Thinking...");
const all_hooks = {
...hooks || {},
onStart: (info) => {
run_id = info.run_id;
if (state_options == null ? void 0 : state_options.scroll) state_options.scroll();
},
onToken: (token) => {
if (loading) setLoading(false);
if (!generating) setGenerating(true);
if (status) setStatus(void 0);
setStreamPlaceholder((prev) => {
if (!prev) return;
return {
...prev,
response: {
...prev.response,
content: prev.response.content + token
}
};
});
if (hooks == null ? void 0 : hooks.onToken) hooks.onToken(token);
if (state_options == null ? void 0 : state_options.scroll) state_options.scroll();
},
onToolCall: (call) => {
setStatus(`Talking with ${call.function.name}`);
if (hooks == null ? void 0 : hooks.onToolCall) hooks.onToolCall(call);
},
onToolResult: (res) => {
setStatus(void 0);
setStreamPlaceholder((prev) => {
if (!prev) return;
return {
...prev,
response: {
...prev.response,
tools_calls: [...prev.response.tool_calls, res]
}
};
});
if (hooks == null ? void 0 : hooks.onToolResult) hooks.onToolResult(res);
},
onModelResponse: async (response2) => {
setStatus(void 0);
setStreamPlaceholder(void 0);
if (response2.error === null) {
setMessages((prev) => sortedMessages(
[...prev, agentPlaceholder(response2.data)]
));
}
if (hooks == null ? void 0 : hooks.onModelResponse) hooks.onModelResponse(response2);
}
};
const response = await agentInstance.run({
inputs,
options,
hooks: all_hooks
});
return response;
} catch (err) {
const err_string = typeof err === "string" ? err : typeof (err == null ? void 0 : err.msg) === "string" ? err.msg : JSON.stringify(err);
return { data: null, error: err_string };
} finally {
setStatus(void 0);
setLoading(false);
setGenerating(false);
if (state_options == null ? void 0 : state_options.scroll) state_options.scroll();
}
};
return {
generating,
loading,
status,
streamPlaceholder,
messages,
setMessages,
newRequest,
agent,
session,
setSession
};
}
// src/voice_state.ts
import {
RunVoicePlayer,
VoiceRecorder,
VoiceVisualizer
} from "@scoopika/client";
import { useEffect, useState as useState2 } from "react";
function useVoiceChatState(agent, state_options = {}) {
const chatState = useChatState(agent, state_options);
const [agentVoicePlayer, setAgentVoicePlayer] = useState2(null);
const [voiceRecorder, setVoiceRecorder] = useState2(
null
);
const [voicePlaying, setVoicePlaying] = useState2(false);
const [visualizer, setVisualizer] = useState2(null);
const [recorderState, setRecorderState] = useState2("stopped");
const [agentVoicePaused, setPlayerPaused] = useState2(false);
const [working, setWorking] = useState2(false);
const [recognizedText, setRecognizedText] = useState2();
const [supportRecognition, setSupportRecognition] = useState2(
null
);
const pauseAgentVoice = () => {
if (!agentVoicePlayer) return;
agentVoicePlayer.pause();
setPlayerPaused(true);
};
const resumeAgentVoice = () => {
if (!agentVoicePlayer) return;
agentVoicePlayer.resume();
setPlayerPaused(false);
};
const updateRecognizedText = (text) => {
if (voiceRecorder) voiceRecorder.stop();
if (voiceRecorder) voiceRecorder.text = text;
setRecognizedText(text);
};
useEffect(() => {
setVoiceRecorder(
new VoiceRecorder({
onStateChange: (state) => setRecorderState(state),
onText: (text) => setRecognizedText(text),
allowInBrowserRecognition: state_options == null ? void 0 : state_options.allowInBrowserSpeechRecognition
})
);
if (voiceRecorder) {
setSupportRecognition(voiceRecorder.recognition !== null);
}
const visualize = state_options == null ? void 0 : state_options.agent_voice;
if (visualize == null ? void 0 : visualize.canvas) {
setVisualizer(
new VoiceVisualizer(
visualize.audio,
visualize.canvas,
visualize.wave_color
)
);
}
}, []);
const newRequest = async ({
inputs,
options,
hooks
} = {}) => {
try {
if (agentVoicePlayer == null ? void 0 : agentVoicePlayer.started) {
agentVoicePlayer.pause();
setVoicePlaying(false);
setPlayerPaused(false);
}
let player = null;
if (voiceRecorder == null ? void 0 : voiceRecorder.started) voiceRecorder.stop();
inputs = inputs || {};
if (voiceRecorder == null ? void 0 : voiceRecorder.started) {
const recorderInputs = voiceRecorder ? await voiceRecorder.asRunInput() : null;
let message = (inputs == null ? void 0 : inputs.message) || "";
const audio = (inputs == null ? void 0 : inputs.audio) || [];
if (recorderInputs == null ? void 0 : recorderInputs.message) {
if (message.length > 0) message += "\n";
message += recorderInputs.message;
}
if (recorderInputs == null ? void 0 : recorderInputs.audio) {
audio.push(...recorderInputs.audio);
}
inputs = { ...inputs || {}, message, audio };
}
setWorking(true);
if (voiceRecorder == null ? void 0 : voiceRecorder.started) await voiceRecorder.finish();
options = { voice: true, ...options || {} };
if (state_options == null ? void 0 : state_options.agent_voice) {
player = new RunVoicePlayer(state_options.agent_voice.audio);
setAgentVoicePlayer(player);
}
if (visualizer) visualizer.getReady();
const all_hooks = {
...hooks || {},
onAudio: (stream) => {
if ((state_options == null ? void 0 : state_options.auto_play_audio) !== false) {
if (!(player == null ? void 0 : player.paused)) {
setPlayerPaused(false);
}
setVoicePlaying(true);
if (player) player.queue(stream);
}
if (hooks == null ? void 0 : hooks.onAudio) hooks.onAudio(stream);
}
};
const response = await chatState.newRequest({
inputs,
options,
hooks: all_hooks
});
setWorking(false);
if (player && (response == null ? void 0 : response.error) === null)
await player.finish(response.data.audio.length);
setVoicePlaying(false);
return response;
} catch (err) {
const err_string = typeof err === "string" ? err : typeof (err == null ? void 0 : err.msg) === "string" ? err.msg : JSON.stringify(err);
return { data: null, error: err_string };
} finally {
setWorking(false);
setVoicePlaying(false);
}
};
return {
...chatState,
voiceRecorder,
setVoiceRecorder,
voicePlaying,
setVoicePlaying,
agentVoicePlayer,
setAgentVoicePlayer,
newRequest,
visualizer,
setVisualizer,
recorderState,
setRecorderState,
recognizedText,
setRecognizedText,
supportRecognition,
setSupportRecognition,
updateRecognizedText,
working,
setWorking,
pauseAgentVoice,
resumeAgentVoice,
agentVoicePaused,
setPlayerPaused
};
}
export {
useChatState,
useVoiceChatState
};