nico-martin HF Staff commited on
Commit
b2847f1
·
1 Parent(s): 832660f

small improvements

Browse files
src/chat/Chat.tsx CHANGED
@@ -3,7 +3,7 @@ import { useChatSettings } from "@utils/context/chatSettings/useChatSettings.ts"
3
  import { formatBytes } from "@utils/format.ts";
4
  import { CONVERSATION_STARTERS, MODELS } from "@utils/models.ts";
5
  import { TOOLS } from "@utils/tools.ts";
6
- import { Settings } from "lucide-react";
7
  import {
8
  useEffect,
9
  useMemo,
@@ -48,12 +48,7 @@ export default function Chat({ className = "" }: { className?: string }) {
48
  const newSettingsKey = JSON.stringify(settings);
49
  if (!settings || settingsKey.current === newSettingsKey) return;
50
  settingsKey.current = newSettingsKey;
51
- generator.initializeConversation(
52
- TOOLS.filter((tool) => settings.tools.includes(tool.name)),
53
- settings.temperature,
54
- settings.enableThinking,
55
- settings.systemPrompt
56
- );
57
  }, [settings]);
58
 
59
  useEffect(() => {
@@ -64,6 +59,14 @@ export default function Chat({ className = "" }: { className?: string }) {
64
 
65
  const modelDownloaded = downloadedModels.includes(settings.modelKey);
66
 
 
 
 
 
 
 
 
 
67
  const initializeModel = async () => {
68
  setState(State.INITIALIZING);
69
  setDownloadProgress(0);
@@ -185,7 +188,7 @@ export default function Chat({ className = "" }: { className?: string }) {
185
  </div>
186
  )}
187
  </div>
188
- <div className="fixed right-0 bottom-6 left-0 mx-auto w-full max-w-4xl">
189
  <Card className="mt-auto">
190
  <ChatForm
191
  disabled={!ready}
@@ -206,6 +209,15 @@ export default function Chat({ className = "" }: { className?: string }) {
206
  {settings?.tools.length}/{TOOLS.length} tool
207
  {settings?.tools.length !== 1 ? "s" : ""} active
208
  </Button>
 
 
 
 
 
 
 
 
 
209
  </div>
210
  </div>
211
  </>
 
3
  import { formatBytes } from "@utils/format.ts";
4
  import { CONVERSATION_STARTERS, MODELS } from "@utils/models.ts";
5
  import { TOOLS } from "@utils/tools.ts";
6
+ import { RotateCcw, Settings } from "lucide-react";
7
  import {
8
  useEffect,
9
  useMemo,
 
48
  const newSettingsKey = JSON.stringify(settings);
49
  if (!settings || settingsKey.current === newSettingsKey) return;
50
  settingsKey.current = newSettingsKey;
51
+ initializeConversation();
 
 
 
 
 
52
  }, [settings]);
53
 
54
  useEffect(() => {
 
59
 
60
  const modelDownloaded = downloadedModels.includes(settings.modelKey);
61
 
62
+ const initializeConversation = () =>
63
+ generator.initializeConversation(
64
+ TOOLS.filter((tool) => settings.tools.includes(tool.name)),
65
+ settings.temperature,
66
+ settings.enableThinking,
67
+ settings.systemPrompt
68
+ );
69
+
70
  const initializeModel = async () => {
71
  setState(State.INITIALIZING);
72
  setDownloadProgress(0);
 
188
  </div>
189
  )}
190
  </div>
191
+ <div className="fixed right-0 bottom-6 left-0 mx-auto w-full max-w-4xl space-y-2">
192
  <Card className="mt-auto">
193
  <ChatForm
194
  disabled={!ready}
 
209
  {settings?.tools.length}/{TOOLS.length} tool
210
  {settings?.tools.length !== 1 ? "s" : ""} active
211
  </Button>
212
+ <Button
213
+ iconLeft={<RotateCcw />}
214
+ size="xs"
215
+ variant="ghost"
216
+ color="mono"
217
+ onClick={initializeConversation}
218
+ >
219
+ new conversation
220
+ </Button>
221
  </div>
222
  </div>
223
  </>
src/chat/Message.tsx CHANGED
@@ -1,5 +1,6 @@
1
  import { Modal } from "@theme";
2
  import cn from "@utils/classnames.ts";
 
3
  import { SquareCode, StopCircle } from "lucide-react";
4
  import { useState } from "react";
5
 
@@ -15,25 +16,21 @@ export default function Message({
15
  className?: string;
16
  }) {
17
  const [templateOpen, setTemplateOpen] = useState<boolean>(false);
18
- const formatDuration = (ms: number) => {
19
- if (ms < 1000) {
20
- return `${Math.round(ms)}ms`;
21
- }
22
- return `${(ms / 1000).toFixed(2)}s`;
23
- };
24
 
25
  return (
26
  <div className={cn(className, "group relative flex flex-col gap-1")}>
27
- <Modal
28
- title="Full Template"
29
- isOpen={templateOpen}
30
- onClose={() => setTemplateOpen(false)}
31
- size="xl"
32
- >
33
- <pre className="max-h-[70vh] overflow-auto rounded-lg border border-gray-300 bg-gray-50 p-4 text-sm leading-relaxed text-gray-900 dark:border-gray-600 dark:bg-gray-900 dark:text-gray-100">
34
- {message.template}
35
- </pre>
36
- </Modal>
 
 
37
  {message.content.map((part) =>
38
  part.type === "tool" ? (
39
  <MessageToolCall tool={part} />
@@ -47,18 +44,18 @@ export default function Message({
47
  <span>Response interrupted by the user</span>
48
  </div>
49
  )}
50
- {message.modelUsage && (
51
  <div className="mt-1 inline-flex flex-wrap items-center gap-1.5 self-end rounded-lg bg-gray-200 px-2.5 py-1 text-xs font-medium text-gray-700 opacity-0 transition-opacity group-hover:opacity-100 dark:bg-gray-700 dark:text-gray-300">
52
- <span className="font-semibold">{message.modelUsage.model}</span>
53
  <span className="text-gray-400 dark:text-gray-500">•</span>
54
  <span>
55
- Output: {Math.round(message.modelUsage.outputTps)} tok/s (
56
- {message.modelUsage.outputTokens} tokens in{" "}
57
- {formatDuration(message.modelUsage.outputDurationMs)})
58
  </span>
59
  <span className="text-gray-400 dark:text-gray-500">•</span>
60
  <span title="Input processing time">
61
- Prefill: {formatDuration(message.modelUsage.inputDurationMs)}
62
  </span>
63
  <span className="text-gray-400 dark:text-gray-500">•</span>
64
  <button
 
1
  import { Modal } from "@theme";
2
  import cn from "@utils/classnames.ts";
3
+ import { formatDuration } from "@utils/format.ts";
4
  import { SquareCode, StopCircle } from "lucide-react";
5
  import { useState } from "react";
6
 
 
16
  className?: string;
17
  }) {
18
  const [templateOpen, setTemplateOpen] = useState<boolean>(false);
 
 
 
 
 
 
19
 
20
  return (
21
  <div className={cn(className, "group relative flex flex-col gap-1")}>
22
+ {message.metadata && (
23
+ <Modal
24
+ title="Full Template"
25
+ isOpen={templateOpen}
26
+ onClose={() => setTemplateOpen(false)}
27
+ size="xl"
28
+ >
29
+ <pre className="max-h-[70vh] overflow-auto rounded-lg border border-gray-300 bg-gray-50 p-4 text-sm leading-relaxed text-gray-900 dark:border-gray-600 dark:bg-gray-900 dark:text-gray-100">
30
+ {message.metadata.template}
31
+ </pre>
32
+ </Modal>
33
+ )}
34
  {message.content.map((part) =>
35
  part.type === "tool" ? (
36
  <MessageToolCall tool={part} />
 
44
  <span>Response interrupted by the user</span>
45
  </div>
46
  )}
47
+ {message.metadata && (
48
  <div className="mt-1 inline-flex flex-wrap items-center gap-1.5 self-end rounded-lg bg-gray-200 px-2.5 py-1 text-xs font-medium text-gray-700 opacity-0 transition-opacity group-hover:opacity-100 dark:bg-gray-700 dark:text-gray-300">
49
+ <span className="font-semibold">{message.metadata.model}</span>
50
  <span className="text-gray-400 dark:text-gray-500">•</span>
51
  <span>
52
+ Output: {Math.round(message.metadata.outputTps)} tok/s (
53
+ {message.metadata.outputTokens} tokens in{" "}
54
+ {formatDuration(message.metadata.outputDurationMs)})
55
  </span>
56
  <span className="text-gray-400 dark:text-gray-500">•</span>
57
  <span title="Input processing time">
58
+ Prefill: {formatDuration(message.metadata.inputDurationMs)}
59
  </span>
60
  <span className="text-gray-400 dark:text-gray-500">•</span>
61
  <button
src/chat/MessageToolCall.tsx CHANGED
@@ -1,4 +1,5 @@
1
- import { Wrench } from "lucide-react";
 
2
  import { useState } from "react";
3
 
4
  import type { ChatMessageAssistantTool } from "../textGeneration/types.ts";
@@ -25,11 +26,17 @@ export default function MessageToolCall({
25
  >
26
  <div className="flex items-center justify-between gap-3">
27
  <button
28
- className="flex cursor-pointer items-center gap-2 text-xs text-gray-700 hover:text-gray-900 dark:text-gray-300 dark:hover:text-gray-100"
29
  onClick={() => setExpanded(!expanded)}
30
  >
31
- {isLoading ? <Loader size="xs" /> : <Wrench className="h-3 w-3" />}
32
- {isLoading ? "calling tool" : "called tool"} <b>{tool.name}</b>
 
 
 
 
 
 
33
  </button>
34
  </div>
35
  {expanded && (
 
1
+ import { formatDuration } from "@utils/format.ts";
2
+ import { Timer, Wrench } from "lucide-react";
3
  import { useState } from "react";
4
 
5
  import type { ChatMessageAssistantTool } from "../textGeneration/types.ts";
 
26
  >
27
  <div className="flex items-center justify-between gap-3">
28
  <button
29
+ className="flex w-full cursor-pointer items-center justify-between gap-2 text-xs text-gray-700 hover:text-gray-900 dark:text-gray-300 dark:hover:text-gray-100"
30
  onClick={() => setExpanded(!expanded)}
31
  >
32
+ <span className="flex items-center gap-2">
33
+ {isLoading ? <Loader size="xs" /> : <Wrench className="h-3 w-3" />}
34
+ {isLoading ? "calling tool" : "called tool"} <b>{tool.name}</b>
35
+ </span>
36
+ <span className="flex items-center gap-1 opacity-60">
37
+ <Timer className="h-3 w-3" />
38
+ {formatDuration(tool.time)}
39
+ </span>
40
  </button>
41
  </div>
42
  {expanded && (
src/textGeneration/TextGeneration.ts CHANGED
@@ -11,7 +11,7 @@ import {
11
  type ChatMessageAssistant,
12
  type ChatMessageAssistantResponse,
13
  type ChatMessageAssistantTool,
14
- type ModelUsage,
15
  type Request,
16
  RequestType,
17
  type Response,
@@ -143,9 +143,8 @@ export default class TextGeneration {
143
  ) => {
144
  return new Promise<{
145
  response: string;
146
- modelUsage: ModelUsage;
147
  interrupted: boolean;
148
- template: string;
149
  }>((resolve, reject) => {
150
  if (this.modelKey === null) {
151
  reject("Model not initialized");
@@ -177,9 +176,8 @@ export default class TextGeneration {
177
  this.messages.push({ role: "assistant", content: data.response });
178
  resolve({
179
  response: data.response,
180
- modelUsage: data.modelUsage,
181
  interrupted: data.interrupted,
182
- template: data.template,
183
  });
184
  }
185
  if (data.type === ResponseType.GENERATE_TEXT_CHUNK) {
@@ -219,7 +217,7 @@ export default class TextGeneration {
219
 
220
  this.chatMessages = [...prevChatMessages, assistantMessage];
221
 
222
- const { modelUsage, interrupted, template } = await this.generateText(
223
  prompt,
224
  isUser ? "user" : "tool",
225
  (partialResponse) => {
@@ -233,6 +231,7 @@ export default class TextGeneration {
233
  : ({
234
  type: "tool",
235
  result: null,
 
236
  functionSignature: `${part.name}(${JSON.stringify(
237
  part.arguments
238
  )})`,
@@ -244,8 +243,7 @@ export default class TextGeneration {
244
  );
245
  isUser = false;
246
 
247
- assistantMessage.template = template;
248
- assistantMessage.modelUsage = modelUsage;
249
  assistantMessage.interrupted = interrupted;
250
  this.chatMessages = [...prevChatMessages, assistantMessage];
251
 
@@ -270,7 +268,7 @@ export default class TextGeneration {
270
  )
271
  );
272
 
273
- assistantMessage.modelUsage = modelUsage;
274
  assistantMessage.content = assistantMessage.content.map((message) => {
275
  if (message.type === "tool") {
276
  const toolResponse = toolResponses.find(
@@ -280,6 +278,7 @@ export default class TextGeneration {
280
  return {
281
  ...message,
282
  result: toolResponse.result,
 
283
  };
284
  }
285
  return message;
 
11
  type ChatMessageAssistant,
12
  type ChatMessageAssistantResponse,
13
  type ChatMessageAssistantTool,
14
+ type GenerationMetadata,
15
  type Request,
16
  RequestType,
17
  type Response,
 
143
  ) => {
144
  return new Promise<{
145
  response: string;
146
+ metadata: GenerationMetadata;
147
  interrupted: boolean;
 
148
  }>((resolve, reject) => {
149
  if (this.modelKey === null) {
150
  reject("Model not initialized");
 
176
  this.messages.push({ role: "assistant", content: data.response });
177
  resolve({
178
  response: data.response,
179
+ metadata: data.metadata,
180
  interrupted: data.interrupted,
 
181
  });
182
  }
183
  if (data.type === ResponseType.GENERATE_TEXT_CHUNK) {
 
217
 
218
  this.chatMessages = [...prevChatMessages, assistantMessage];
219
 
220
+ const { interrupted, metadata } = await this.generateText(
221
  prompt,
222
  isUser ? "user" : "tool",
223
  (partialResponse) => {
 
231
  : ({
232
  type: "tool",
233
  result: null,
234
+ time: null,
235
  functionSignature: `${part.name}(${JSON.stringify(
236
  part.arguments
237
  )})`,
 
243
  );
244
  isUser = false;
245
 
246
+ assistantMessage.metadata = metadata;
 
247
  assistantMessage.interrupted = interrupted;
248
  this.chatMessages = [...prevChatMessages, assistantMessage];
249
 
 
268
  )
269
  );
270
 
271
+ assistantMessage.metadata = metadata;
272
  assistantMessage.content = assistantMessage.content.map((message) => {
273
  if (message.type === "tool") {
274
  const toolResponse = toolResponses.find(
 
278
  return {
279
  ...message,
280
  result: toolResponse.result,
281
+ time: toolResponse.time,
282
  };
283
  }
284
  return message;
src/textGeneration/types.ts CHANGED
@@ -58,8 +58,7 @@ interface ResponseGenerateTextChunk {
58
  interface ResponseGenerateTextDone {
59
  type: ResponseType.GENERATE_TEXT_DONE;
60
  response: string;
61
- modelUsage: ModelUsage;
62
- template: string;
63
  interrupted: boolean;
64
  requestId: number;
65
  }
@@ -76,7 +75,7 @@ interface ResponseInitializeModel {
76
  requestId: number;
77
  }
78
 
79
- export interface ModelUsage {
80
  inputDurationMs: number;
81
  outputTokens: number;
82
  outputDurationMs: number;
@@ -84,6 +83,7 @@ export interface ModelUsage {
84
  doneMs: number;
85
  modelKey: keyof typeof MODELS;
86
  model: string;
 
87
  }
88
 
89
  interface ResponseGenerateTextAborted {
@@ -107,8 +107,7 @@ export interface ChatMessageAssistant {
107
  role: "assistant";
108
  content: Array<ChatMessageAssistantResponse | ChatMessageAssistantTool>;
109
  interrupted: boolean;
110
- modelUsage?: ModelUsage;
111
- template?: string;
112
  }
113
 
114
  export interface ChatMessageSystem {
@@ -125,53 +124,10 @@ export interface ChatMessageAssistantTool extends ToolCallPayload {
125
  type: "tool";
126
  functionSignature: string;
127
  result: string;
 
128
  }
129
 
130
  export type ChatMessage =
131
  | ChatMessageUser
132
  | ChatMessageAssistant
133
  | ChatMessageSystem;
134
-
135
- /*
136
- interface ResponseGenerateTextDone {}
137
-
138
- export enum ResponseStatus {
139
- SUCCESS,
140
- ERROR,
141
- STARTED,
142
- }
143
-
144
- export enum BackgroundTasks {
145
- EXTRACT_FEATURES,
146
- INITIALIZE_MODELS,
147
- AGENT_GENERATE_TEXT,
148
- AGENT_GET_MESSAGES,
149
- AGENT_CLEAR,
150
- }
151
-
152
- export enum BackgroundMessages {
153
- DOWNLOAD_PROGRESS,
154
- MESSAGES_UPDATE,
155
- }
156
-
157
- export type Dtype = "fp32" | "fp16" | "q4" | "q4f16";
158
-
159
- export interface ChatMessageUser {
160
- role: "user";
161
- content: string;
162
- }
163
-
164
- export interface ChatMessageTool {
165
- name: string;
166
- functionSignature: string;
167
- id: string;
168
- result: string;
169
- }
170
-
171
- export interface ChatMessageAssistant {
172
- role: "assistant";
173
- content: string;
174
- tools: Array<ChatMessageTool>;
175
- }
176
-
177
- export type ChatMessage = ChatMessageUser | ChatMessageAssistant;*/
 
58
  interface ResponseGenerateTextDone {
59
  type: ResponseType.GENERATE_TEXT_DONE;
60
  response: string;
61
+ metadata: GenerationMetadata;
 
62
  interrupted: boolean;
63
  requestId: number;
64
  }
 
75
  requestId: number;
76
  }
77
 
78
+ export interface GenerationMetadata {
79
  inputDurationMs: number;
80
  outputTokens: number;
81
  outputDurationMs: number;
 
83
  doneMs: number;
84
  modelKey: keyof typeof MODELS;
85
  model: string;
86
+ template: string;
87
  }
88
 
89
  interface ResponseGenerateTextAborted {
 
107
  role: "assistant";
108
  content: Array<ChatMessageAssistantResponse | ChatMessageAssistantTool>;
109
  interrupted: boolean;
110
+ metadata?: GenerationMetadata;
 
111
  }
112
 
113
  export interface ChatMessageSystem {
 
124
  type: "tool";
125
  functionSignature: string;
126
  result: string;
127
+ time: number;
128
  }
129
 
130
  export type ChatMessage =
131
  | ChatMessageUser
132
  | ChatMessageAssistant
133
  | ChatMessageSystem;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/textGeneration/worker/textGenerationWorker.ts CHANGED
@@ -189,7 +189,7 @@ self.onmessage = async ({ data }: MessageEvent<Request>) => {
189
  postMessage({
190
  type: ResponseType.GENERATE_TEXT_DONE,
191
  response,
192
- modelUsage: {
193
  inputDurationMs: firstTokenTime - started,
194
  outputTokens: numTokens,
195
  outputDurationMs: ended - firstTokenTime,
@@ -197,8 +197,8 @@ self.onmessage = async ({ data }: MessageEvent<Request>) => {
197
  doneMs: ended - started,
198
  modelKey: MODEL.modelId,
199
  model: MODEL.title,
 
200
  },
201
- template,
202
  interrupted: stoppingCriteria.interrupted,
203
  requestId,
204
  });
 
189
  postMessage({
190
  type: ResponseType.GENERATE_TEXT_DONE,
191
  response,
192
+ metadata: {
193
  inputDurationMs: firstTokenTime - started,
194
  outputTokens: numTokens,
195
  outputDurationMs: ended - firstTokenTime,
 
197
  doneMs: ended - started,
198
  modelKey: MODEL.modelId,
199
  model: MODEL.title,
200
+ template,
201
  },
 
202
  interrupted: stoppingCriteria.interrupted,
203
  requestId,
204
  });
src/utils/format.ts CHANGED
@@ -74,10 +74,11 @@ export const formatResult = (data: any, indent = 0): string => {
74
  }
75
  };
76
 
77
- export const formatTime = (seconds: number): string => {
78
- const mins = Math.floor(seconds / 60);
79
- const secs = seconds % 60;
80
- return `${mins}:${secs.toString().padStart(2, "0")}`;
 
81
  };
82
 
83
  export const round = (input: number, decimals: number): number => {
 
74
  }
75
  };
76
 
77
+ export const formatDuration = (ms: number) => {
78
+ if (ms < 1000) {
79
+ return `${Math.round(ms)}ms`;
80
+ }
81
+ return `${(ms / 1000).toFixed(2)}s`;
82
  };
83
 
84
  export const round = (input: number, decimals: number): number => {
src/utils/webMcp.ts CHANGED
@@ -199,13 +199,17 @@ export const splitResponse = (
199
  export const executeToolCall = async (
200
  toolCall: ToolCallPayload,
201
  tools: Array<WebMCPTool>
202
- ): Promise<{ id: string; result: string }> => {
 
203
  const toolToUse = tools.find((t) => t.name === toolCall.name);
204
  if (!toolToUse)
205
  throw new Error(`Tool '${toolCall.name}' not found or is disabled.`);
206
 
 
 
207
  return {
208
  id: toolCall.id,
209
- result: await executeWebMCPTool(toolToUse, toolCall.arguments),
 
210
  };
211
  };
 
199
  export const executeToolCall = async (
200
  toolCall: ToolCallPayload,
201
  tools: Array<WebMCPTool>
202
+ ): Promise<{ id: string; result: string; time: number }> => {
203
+ const started = performance.now();
204
  const toolToUse = tools.find((t) => t.name === toolCall.name);
205
  if (!toolToUse)
206
  throw new Error(`Tool '${toolCall.name}' not found or is disabled.`);
207
 
208
+ const result = await executeWebMCPTool(toolToUse, toolCall.arguments);
209
+ const ended = performance.now();
210
  return {
211
  id: toolCall.id,
212
+ result,
213
+ time: ended - started,
214
  };
215
  };