app.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. const messagesEl = document.querySelector("#messages");
  2. const statusEl = document.querySelector("#connection-status");
  3. const chatForm = document.querySelector("#chat-form");
  4. const userMessage = document.querySelector("#user-message");
  5. const systemPrompts = document.querySelector("#system-prompts");
  6. const preMessages = document.querySelector("#pre-messages");
  7. const preMessageTemplate = document.querySelector("#pre-message-template");
  8. const toolList = document.querySelector("#tool-list");
  9. let socket = null;
  10. let activeAssistant = null;
  11. let runStartedAt = 0;
  12. let firstTokenAt = 0;
  13. document.querySelector("#add-system-prompt").addEventListener("click", () => {
  14. const textarea = document.createElement("textarea");
  15. textarea.className = "system-prompt";
  16. textarea.rows = 4;
  17. systemPrompts.append(textarea);
  18. });
  19. document.querySelector("#add-pre-message").addEventListener("click", () => {
  20. preMessages.append(preMessageTemplate.content.cloneNode(true));
  21. });
  22. chatForm.addEventListener("submit", (event) => {
  23. event.preventDefault();
  24. runDebugSession();
  25. });
  26. loadTools();
  27. async function loadTools() {
  28. try {
  29. const response = await fetch("/api/tools");
  30. if (!response.ok) {
  31. throw new Error(`Tool API failed: ${response.status}`);
  32. }
  33. renderTools(await response.json());
  34. } catch (error) {
  35. toolList.textContent = "Failed to load tools";
  36. }
  37. }
  38. function renderTools(tools) {
  39. toolList.textContent = "";
  40. if (!tools.length) {
  41. toolList.textContent = "No tools available";
  42. return;
  43. }
  44. tools.forEach((tool) => {
  45. const label = document.createElement("label");
  46. label.className = "checkbox";
  47. label.title = tool.description || tool.name;
  48. const checkbox = document.createElement("input");
  49. checkbox.type = "checkbox";
  50. checkbox.className = "tool-toggle";
  51. checkbox.value = tool.name;
  52. checkbox.checked = true;
  53. label.append(checkbox, ` ${tool.name}`);
  54. toolList.append(label);
  55. });
  56. }
  57. function runDebugSession() {
  58. closeSocket();
  59. resetRun();
  60. socket = new WebSocket(wsUrl());
  61. socket.addEventListener("open", () => {
  62. statusEl.textContent = "Streaming";
  63. runStartedAt = performance.now();
  64. socket.send(JSON.stringify(buildRequest()));
  65. });
  66. socket.addEventListener("message", (event) => {
  67. handleServerMessage(JSON.parse(event.data));
  68. });
  69. socket.addEventListener("close", () => {
  70. statusEl.textContent = "Idle";
  71. updateElapsed();
  72. });
  73. socket.addEventListener("error", () => {
  74. appendLog("error", "WebSocket connection failed");
  75. });
  76. }
  77. function buildRequest() {
  78. return {
  79. user_message: userMessage.value,
  80. system_prompts: [...document.querySelectorAll(".system-prompt")]
  81. .map((input) => input.value.trim())
  82. .filter(Boolean),
  83. pre_messages: [...document.querySelectorAll(".pre-message")]
  84. .map((row) => ({
  85. role: row.querySelector(".pre-role").value,
  86. content: row.querySelector(".pre-content").value.trim(),
  87. }))
  88. .filter((message) => message.content),
  89. chat_agent: {
  90. model: document.querySelector("#model").value.trim(),
  91. temperature: Number(document.querySelector("#temperature").value),
  92. max_tokens: Number(document.querySelector("#max-tokens").value),
  93. },
  94. event_agent: {
  95. enabled_tools: selectedTools(),
  96. max_event_loops: Number(document.querySelector("#max-event-loops").value),
  97. },
  98. };
  99. }
  100. function selectedTools() {
  101. return [...toolList.querySelectorAll(".tool-toggle:checked")].map(
  102. (input) => input.value,
  103. );
  104. }
  105. function handleServerMessage(message) {
  106. if (message.type === "session_started") {
  107. appendLog("session", "Session started");
  108. return;
  109. }
  110. if (message.type === "message_delta") {
  111. appendAssistantDelta(message.content || "");
  112. return;
  113. }
  114. if (message.type === "usage") {
  115. updateUsage(message.usage || {});
  116. return;
  117. }
  118. if (message.type === "event") {
  119. appendLog("event", `${message.event.name}: ${JSON.stringify(message.event.arguments)}`);
  120. return;
  121. }
  122. if (message.type === "tool_result") {
  123. appendLog("tool", message.message.content);
  124. return;
  125. }
  126. if (message.type === "error") {
  127. appendLog("error", message.message);
  128. return;
  129. }
  130. if (message.type === "done") {
  131. appendLog("session", "Done");
  132. closeSocket();
  133. }
  134. }
  135. function appendAssistantDelta(content) {
  136. if (!firstTokenAt) {
  137. firstTokenAt = performance.now();
  138. document.querySelector("#stat-ttft").textContent = `${Math.round(firstTokenAt - runStartedAt)}ms`;
  139. }
  140. if (!activeAssistant) {
  141. activeAssistant = document.createElement("div");
  142. activeAssistant.className = "message assistant";
  143. messagesEl.append(activeAssistant);
  144. }
  145. activeAssistant.textContent += content;
  146. updateElapsed();
  147. messagesEl.scrollTop = messagesEl.scrollHeight;
  148. }
  149. function appendLog(kind, content) {
  150. const item = document.createElement("div");
  151. item.className = `message ${kind}`;
  152. item.textContent = content;
  153. messagesEl.append(item);
  154. messagesEl.scrollTop = messagesEl.scrollHeight;
  155. }
  156. function updateUsage(usage) {
  157. document.querySelector("#stat-tokens").textContent = usage.total_tokens || 0;
  158. document.querySelector("#stat-cached").textContent = usage.cached_tokens || 0;
  159. }
  160. function updateElapsed() {
  161. if (!runStartedAt) {
  162. return;
  163. }
  164. document.querySelector("#stat-elapsed").textContent = `${Math.round(performance.now() - runStartedAt)}ms`;
  165. }
  166. function resetRun() {
  167. activeAssistant = null;
  168. runStartedAt = 0;
  169. firstTokenAt = 0;
  170. messagesEl.textContent = "";
  171. document.querySelector("#stat-tokens").textContent = "0";
  172. document.querySelector("#stat-cached").textContent = "0";
  173. document.querySelector("#stat-ttft").textContent = "-";
  174. document.querySelector("#stat-elapsed").textContent = "-";
  175. }
  176. function closeSocket() {
  177. if (socket && socket.readyState < WebSocket.CLOSING) {
  178. socket.close();
  179. }
  180. }
  181. function wsUrl() {
  182. const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
  183. return `${protocol}//${window.location.host}/ws/debug`;
  184. }