agent-demo.tsx 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. // "use client";
  2. import * as ort from "onnxruntime-web"
  3. ort.env.wasm.wasmPaths = '/_next/static/chunks/'
  4. import type { Agent } from "@/types/agent";
  5. import VRMDemo from "./vrm-demo";
  6. import { useEffect, useState, useContext, useCallback } from "react";
  7. import { Role } from "@/features/chat/messages";
  8. import { ViewerContext } from "@/features/vrmViewer/viewerContext";
  9. import { ChatContext } from "@/features/chat/chatContext";
  10. import { AmicaLifeContext } from "@/features/amicaLife/amicaLifeContext";
  11. import { ChatConfig } from "@/features/chat/chat";
  12. import { fetchBackends } from "@/lib/backends";
  13. import { AssistantText } from "./assistant-text";
  14. import { UserText } from "./user-text";
  15. import { useTranscriber } from "@/hooks/useTranscriber";
  16. import { cleanTranscript } from "@/utils/stringProcessing";
  17. import { handleIdleEvent, TimestampedPrompt } from "@/features/amicaLife/eventHandler";
  18. import { loadImage } from "@/features/diagnosed/visionDiagnosis";
  19. import { loadVRMAnimation } from "@/lib/VRMAnimation/loadVRMAnimation";
  20. import { wait } from "@/utils/wait";
  21. import { stagedPrompt } from "@/lib/prompts";
  22. interface AgentDemoProps {
  23. agent: Agent;
  24. talentShow: boolean;
  25. talentRunning: boolean;
  26. setTalentShow: (show: boolean) => void;
  27. setTalentRunning: (running: boolean) => void;
  28. }
  29. const promptKeys = ["intro", "easy_input", "medium_input", "hard_input"];
  30. const animPaths = ["/animations/greeting.vrma", "/animations/peaceSign.vrma"];
  31. const stepLabels = [
  32. "Intro",
  33. "Easy Input",
  34. "Medium Input",
  35. "Hard Input",
  36. "Image Vision",
  37. "Greeting Animation",
  38. "Subconscious Event",
  39. "Peace Sign Animation",
  40. ];
  41. const TalentSteps = {
  42. INTRO: 0,
  43. EASY_INPUT: 1,
  44. MEDIUM_INPUT: 2,
  45. HARD_INPUT: 3,
  46. IMAGE_VISION: 4,
  47. GREETING_ANIM: 5,
  48. SUBCONSCIOUS: 6,
  49. PEACE_SIGN_ANIM: 7,
  50. } as const;
  51. export function AgentDemo({ agent, talentShow, setTalentShow, talentRunning, setTalentRunning }: AgentDemoProps) {
  52. const { viewer } = useContext(ViewerContext);
  53. const { chat: bot } = useContext(ChatContext);
  54. const { amicaLife } = useContext(AmicaLifeContext);
  55. const transcriber = useTranscriber();
  56. const [currentStep, setCurrentStep] = useState(0);
  57. const [totalSteps, setTotalSteps] = useState(0);
  58. const [talentError, setTalentError] = useState<string | null>(null);
  59. const [config, setConfig] = useState<ChatConfig>();
  60. const [assistantMessage, setAssistantMessage] = useState("");
  61. const [userMessage, setUserMessage] = useState("");
  62. const [shownMessage, setShownMessage] = useState<Role>("system");
  63. const [vrmError, setVrmError] = useState(false);
  64. const [subconciousLogs, setSubconciousLogs] = useState<TimestampedPrompt[]>([]);
  65. const [chatSpeaking, setChatSpeaking] = useState(false);
  66. const [chatProcessing, setChatProcessing] = useState(false);
  67. const [whisperOpenAIOutput, setWhisperOpenAIOutput] = useState<any | null>(null);
  68. const [whisperCppOutput, setWhisperCppOutput] = useState<any | null>(null);
  69. const getRandomPrompts = useCallback((prompts: typeof stagedPrompt) => {
  70. return Object.fromEntries(
  71. Object.entries(prompts).map(([key, values]) => [
  72. key,
  73. values[Math.floor(Math.random() * values.length)],
  74. ])
  75. );
  76. }, []);
  77. const handleTranscriptionResult = useCallback(
  78. (text: string) => {
  79. const cleanText = cleanTranscript(text);
  80. if (cleanText) bot.receiveMessageFromUser(cleanText, false);
  81. },
  82. [bot]
  83. );
  84. // Load config and initialize bot
  85. useEffect(() => {
  86. const loadAndInit = async () => {
  87. const fullConfig = await fetchBackends(agent.agentId, agent.config);
  88. const newConfig: ChatConfig = {
  89. name: agent.name,
  90. tts_backend: agent.config.tts as ChatConfig["tts_backend"],
  91. chatbot_backend: agent.config.chatbot as ChatConfig["chatbot_backend"],
  92. stt_backend: agent.config.stt as ChatConfig["stt_backend"],
  93. vision_backend: agent.config.vision as ChatConfig["vision_backend"],
  94. system_prompt: agent.systemPrompt,
  95. vision_system_prompt: agent.visionSystemPrompt,
  96. chatbot_params: fullConfig[agent.config.chatbot],
  97. tts_params: fullConfig[agent.config.tts],
  98. stt_params: fullConfig[agent.config.stt],
  99. vision_params: fullConfig[agent.config.vision],
  100. amica_life_params: fullConfig[agent.config.amicaLife] as ChatConfig["amica_life_params"],
  101. rvc_params: fullConfig[agent.config.rvc] as unknown as ChatConfig["rvc_params"],
  102. };
  103. setConfig(newConfig);
  104. bot.initialize(
  105. transcriber,
  106. amicaLife,
  107. viewer,
  108. setUserMessage,
  109. setAssistantMessage,
  110. setShownMessage,
  111. setChatProcessing,
  112. setChatSpeaking,
  113. setWhisperOpenAIOutput,
  114. setWhisperCppOutput,
  115. newConfig
  116. );
  117. };
  118. loadAndInit();
  119. }, []);
  120. // Initialize Amica Life
  121. useEffect(() => {
  122. if (!bot.initialized || !config || config.amica_life_params.amica_life_enabled !== "true") return;
  123. amicaLife.initialize(config, viewer, bot, chatSpeaking, setSubconciousLogs);
  124. }, [bot.initialized, config, viewer, amicaLife, chatSpeaking]);
  125. const updateProgress = (step: number) => {
  126. setCurrentStep(step);
  127. };
  128. const runPromptSteps = async (promptSet: { [x: string]: string; }) => {
  129. for (const key of promptKeys) {
  130. updateProgress(TalentSteps[key.toUpperCase() as keyof typeof TalentSteps]);
  131. await bot.runFullInteraction(promptSet[key], false);
  132. }
  133. };
  134. const runVisionStep = async () => {
  135. updateProgress(TalentSteps.IMAGE_VISION);
  136. const imageBase64 = await loadImage("/sample-image.jpeg");
  137. await bot.runFullInteraction(imageBase64, true);
  138. };
  139. const runAnimationSteps = async () => {
  140. for (let i = 0; i < animPaths.length; i++) {
  141. const animStep = i === 0 ? TalentSteps.GREETING_ANIM : TalentSteps.PEACE_SIGN_ANIM;
  142. updateProgress(animStep);
  143. const animation = await loadVRMAnimation(animPaths[i]);
  144. if (animation && viewer.model) {
  145. const duration = await viewer.model.playAnimation(animation, "idle_loop.vrma");
  146. requestAnimationFrame(() => viewer.resetCameraLerp());
  147. await wait(duration * 1000);
  148. }
  149. if (i === 0 && config?.amica_life_params.amica_life_enabled === "true") {
  150. updateProgress(TalentSteps.SUBCONSCIOUS);
  151. await handleIdleEvent(config, { events: "Subconcious" }, amicaLife, bot, viewer);
  152. }
  153. }
  154. };
  155. // Talent show logic
  156. useEffect(() => {
  157. if (!bot.initialized || !config || !talentShow || talentRunning) return;
  158. const isSubconsciousEnabled = config.amica_life_params.amica_life_enabled === "true";
  159. const baseSteps = promptKeys.length + 1 + animPaths.length; // prompts + vision + animations
  160. const total = isSubconsciousEnabled ? baseSteps + 1 : baseSteps;
  161. setTotalSteps(total);
  162. const runTalentShow = async () => {
  163. try {
  164. setTalentRunning(true);
  165. const promptSet = getRandomPrompts(stagedPrompt);
  166. await runPromptSteps(promptSet);
  167. await runVisionStep();
  168. await runAnimationSteps();
  169. } catch (err) {
  170. console.error("Talent show error:", err);
  171. setTalentError("Something went wrong.");
  172. } finally {
  173. setTalentRunning(false);
  174. setTalentShow(false);
  175. }
  176. };
  177. runTalentShow();
  178. }, [talentShow, bot.initialized, config]);
  179. // Consolidated STT handler
  180. useEffect(() => {
  181. const outputs = [transcriber.output?.text, transcriber.output, whisperCppOutput, whisperOpenAIOutput];
  182. for (const o of outputs) {
  183. if (o && typeof o.text === "string") {
  184. handleTranscriptionResult(o.text);
  185. }
  186. }
  187. }, [transcriber.output, whisperCppOutput, whisperOpenAIOutput]);
  188. useEffect(() => {
  189. return () => {
  190. console.log("AgentDemo is unmounting : ",agent.agentId);
  191. bot.clean();
  192. viewer.unloadVRM();
  193. amicaLife.clean();
  194. };
  195. }, []);
  196. return (
  197. <div
  198. className="bg-gray-100 p-8 rounded-lg h-[400px] flex items-center justify-center border border-gray-200 relative"
  199. style={{
  200. backgroundImage: `url(${agent.bgUrl})`,
  201. backgroundSize: "cover",
  202. backgroundPosition: "center",
  203. }}
  204. >
  205. {/* Talent Show Progress Bar */}
  206. {(talentShow && talentRunning) && (
  207. <div className="absolute right-4 top-4 z-20 w-32 text-white text-2xl">
  208. <div className="h-3 bg-gray-200 rounded-full overflow-hidden">
  209. <div
  210. className="h-full bg-gradient-to-r from-blue-500 to-indigo-600 transition-all duration-500 ease-out"
  211. style={{ width: `${(currentStep / totalSteps) * 100}%` }}
  212. ></div>
  213. </div>
  214. <p className="mt-2 text-center text-sm font-semibold text-white">{stepLabels[currentStep]}</p>
  215. </div>
  216. )}
  217. <VRMDemo
  218. vrmUrl={agent.vrmUrl}
  219. onLoaded={() => { }}
  220. onError={() => setVrmError(true)}
  221. />
  222. {shownMessage === "assistant" && (
  223. <AssistantText name={config?.name ?? ""} message={assistantMessage} />
  224. )}
  225. {shownMessage === "user" && <UserText message={userMessage} />}
  226. </div>
  227. );
  228. }