1
0

use-diagnosis.ts 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. "use client";
  2. import { useState, useCallback, useRef, useEffect } from "react";
  3. import { useQuery, useQueryClient } from "@tanstack/react-query";
  4. import {
  5. checks,
  6. CheckKey,
  7. Status,
  8. DiagnosisResultType,
  9. } from "@/components/diagnosis-result";
  10. import { diagnosisScript } from "@/features/diagnosed/diagnosisScript";
  11. import { vrmDiagnosis } from "@/features/diagnosed/vrmDiagnosis";
  12. import { fetchBackends } from "@/lib/backends";
  13. import type { Agent } from "@/types/agent";
  14. import { CACHE_TTL } from "@/lib/query-client";
  15. import { supabase } from "@/utils/supabase";
  16. // Diagnosis categories used to compute talent score
  17. const talentShowKeys: CheckKey[] = [
  18. "vrm",
  19. "chatbot",
  20. "tts",
  21. "stt",
  22. "vision",
  23. "amicaLife",
  24. ];
  25. const initialResults: DiagnosisResultType = {
  26. vrm: { status: "idle", score: 0 },
  27. chatbot: { status: "idle", score: 0 },
  28. tts: { status: "idle", score: 0 },
  29. stt: { status: "idle", score: 0 },
  30. vision: { status: "idle", score: 0 },
  31. amicaLife: { status: "idle", score: 0 },
  32. overall: "",
  33. };
  34. /**
  35. * Main runner hook
  36. */
  37. export const useDiagnosisRunner = (
  38. agent: Agent,
  39. index: number,
  40. cache_off?: boolean,
  41. ) => {
  42. const queryClient = useQueryClient();
  43. const [checking, setChecking] = useState(false);
  44. const checkingRef = useRef(checking);
  45. useEffect(() => {
  46. checkingRef.current = checking;
  47. }, [checking]);
  48. const [status, setStatus] = useState<string | null>(null);
  49. const [results, setResults] = useState<DiagnosisResultType>(initialResults);
  50. const diagnosisQueryKey = ["diagnosis", agent.agentId];
  51. const {
  52. data: dynamicResults = initialResults,
  53. isStale,
  54. refetch,
  55. } = useDiagnosisQuery(diagnosisQueryKey, agent, !cache_off);
  56. const handleDiagnosis = useCallback(
  57. async (useCache: boolean = true) => {
  58. // Prevent concurrent runs
  59. if (checkingRef.current === true) {
  60. console.log("Already checking, aborting.");
  61. return;
  62. }
  63. setChecking(true);
  64. if (useCache && !isStale) {
  65. setStatus(agent.status);
  66. setResults(dynamicResults);
  67. setChecking(false);
  68. return;
  69. }
  70. try {
  71. const fullConfig = await fetchBackends(agent.agentId, agent.config);
  72. const tempResults: DiagnosisResultType = { ...initialResults };
  73. const update = (
  74. key: keyof DiagnosisResultType,
  75. value: Status | string,
  76. ) => {
  77. if (key === "overall") {
  78. setResults((prev) => ({ ...prev, overall: value as string }));
  79. } else {
  80. tempResults[key as CheckKey] = value as Status;
  81. setResults((prev) => ({ ...prev, [key]: value as Status }));
  82. }
  83. };
  84. await runDiagnosisCheck(update, agent.config, fullConfig, agent.vrmUrl);
  85. const newStatus = (
  86. Object.keys(tempResults) as (keyof DiagnosisResultType)[]
  87. )
  88. .filter((key): key is CheckKey => key !== "overall")
  89. .every((key) => tempResults[key].status === "pass")
  90. ? "active"
  91. : "inactive";
  92. const talentScore =
  93. calculateTalentShowScore(tempResults).toPrecision(4);
  94. tempResults["overall"] = talentScore;
  95. update("overall", talentScore);
  96. setStatus(newStatus);
  97. queryClient.setQueryData(diagnosisQueryKey, tempResults);
  98. const scoreUpdateCache = {
  99. ...extractScoresAndOverall(tempResults),
  100. talentShowScore: talentScore,
  101. agentId: agent.agentId,
  102. };
  103. const { error: agentsUpsertError } = await supabase
  104. .from("agents")
  105. .update({ status: newStatus })
  106. .eq("agentId", agent.agentId);
  107. const { error: backendUpsertError } = await supabase
  108. .from("agent-score")
  109. .upsert(scoreUpdateCache, { onConflict: "agentId" });
  110. if (agentsUpsertError) {
  111. console.error("Failed to upsert agents:", agentsUpsertError);
  112. }
  113. if (backendUpsertError) {
  114. console.error("Failed to upsert agent-score:", backendUpsertError);
  115. }
  116. } catch (err) {
  117. console.error("Diagnosis process failed:", err);
  118. } finally {
  119. setChecking(false);
  120. }
  121. },
  122. [agent, index, queryClient, isStale, dynamicResults],
  123. );
  124. return {
  125. results,
  126. status,
  127. checking,
  128. handleDiagnosis,
  129. };
  130. };
  131. export function extractScoresAndOverall(result: DiagnosisResultType) {
  132. const scores: Record<CheckKey, number> = {} as Record<CheckKey, number>;
  133. for (const key of Object.keys(result) as (keyof DiagnosisResultType)[]) {
  134. if (key === "overall") continue;
  135. scores[key as CheckKey] = result[key as CheckKey].score;
  136. }
  137. return {
  138. ...scores,
  139. };
  140. }
  141. /**
  142. * Custom query hook to manage diagnosis cache
  143. */
  144. function useDiagnosisQuery(queryKey: any[], agent: Agent, enabled: boolean) {
  145. return useQuery<DiagnosisResultType>({
  146. queryKey,
  147. queryFn: async () => {
  148. const fullConfig = await fetchBackends(agent.agentId, agent.config);
  149. const tempResults: DiagnosisResultType = { ...initialResults };
  150. await runDiagnosisCheck(
  151. (key, status) => {
  152. tempResults[key] = status;
  153. },
  154. agent.config,
  155. fullConfig,
  156. agent.vrmUrl,
  157. );
  158. const score = calculateTalentShowScore(tempResults);
  159. tempResults["overall"] = score.toPrecision(4);
  160. return tempResults;
  161. },
  162. staleTime: CACHE_TTL,
  163. enabled,
  164. });
  165. }
  166. /**
  167. * Diagnosis execution
  168. */
  169. export async function runDiagnosisCheck(
  170. update: (key: CheckKey, status: Status) => void,
  171. agentConfig: Record<string, string>,
  172. fullConfig: any,
  173. vrmUrl: string,
  174. ) {
  175. checks.forEach(({ key }) => update(key, { status: "loading", score: 0 }));
  176. await Promise.all(
  177. checks.map(async ({ key }) => {
  178. try {
  179. let metric: Status =
  180. key === "vrm"
  181. ? await vrmDiagnosis(vrmUrl)
  182. : await diagnosisScript(key, agentConfig[key], fullConfig);
  183. update(key, metric);
  184. } catch (err) {
  185. if ((err as DOMException).name === "AbortError") {
  186. console.warn(`Check for ${key} aborted`);
  187. } else {
  188. console.error(`Error during ${key} check:`, err);
  189. }
  190. update(key, { status: "fail", score: 0 });
  191. }
  192. }),
  193. );
  194. }
  195. function calculateTalentShowScore(results: Record<CheckKey, Status>): number {
  196. const maxScorePerCheck = 100;
  197. const earnedScore = talentShowKeys.reduce((sum, key) => {
  198. const score = results[key]?.score || 0;
  199. return sum + score;
  200. }, 0);
  201. return (earnedScore / (talentShowKeys.length * maxScorePerCheck)) * 100;
  202. }