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

added stats and template

Browse files
.idea/vcs.xml CHANGED
@@ -3,6 +3,5 @@
3
  <component name="VcsDirectoryMappings">
4
  <mapping directory="" vcs="Git" />
5
  <mapping directory="$PROJECT_DIR$" vcs="Git" />
6
- <mapping directory="$PROJECT_DIR$/transformers.js-v4/transformers.js" vcs="Git" />
7
  </component>
8
  </project>
 
3
  <component name="VcsDirectoryMappings">
4
  <mapping directory="" vcs="Git" />
5
  <mapping directory="$PROJECT_DIR$" vcs="Git" />
 
6
  </component>
7
  </project>
src/App.tsx CHANGED
@@ -8,10 +8,10 @@ export default function App() {
8
  return (
9
  <ThemeContextProvider>
10
  <ChatSettingsContextProvider>
11
- <div className="mx-auto flex h-screen w-full max-w-4xl flex-col">
12
- <Header className="" />
13
- <Chat className="flex-grow" />
14
  </div>
 
15
  </ChatSettingsContextProvider>
16
  </ThemeContextProvider>
17
  );
 
8
  return (
9
  <ThemeContextProvider>
10
  <ChatSettingsContextProvider>
11
+ <div className="fixed top-0 right-0 left-0 mx-auto w-full max-w-4xl">
12
+ <Header className="mx-auto w-full" />
 
13
  </div>
14
+ <Chat />
15
  </ChatSettingsContextProvider>
16
  </ThemeContextProvider>
17
  );
src/chat/Chat.tsx CHANGED
@@ -1,7 +1,7 @@
1
  import { Button, Card, Slider } from "@theme";
2
  import { useChatSettings } from "@utils/context/chatSettings/useChatSettings.ts";
3
  import { formatBytes } from "@utils/format.ts";
4
- import { MODELS } from "@utils/models.ts";
5
  import { TOOLS } from "@utils/tools.ts";
6
  import { Settings } from "lucide-react";
7
  import {
@@ -13,10 +13,13 @@ import {
13
  } from "react";
14
 
15
  import TextGeneration from "../textGeneration/TextGeneration.ts";
 
 
 
 
16
  import cn from "../utils/classnames.ts";
17
  import ChatForm from "./ChatForm.tsx";
18
- import MessageContent from "./MessageContent.tsx";
19
- import MessageToolCall from "./MessageToolCall.tsx";
20
 
21
  enum State {
22
  IDLE,
@@ -31,6 +34,8 @@ export default function Chat({ className = "" }: { className?: string }) {
31
  const [downloadProgress, setDownloadProgress] = useState<number>(0);
32
  const [state, setState] = useState<State>(State.IDLE);
33
  const settingsKey = useRef<string>(null);
 
 
34
 
35
  const generator = useMemo(() => new TextGeneration(), []);
36
  const getSnapshot = () => generator.chatMessages;
@@ -51,6 +56,10 @@ export default function Chat({ className = "" }: { className?: string }) {
51
  );
52
  }, [settings]);
53
 
 
 
 
 
54
  if (!settings) return;
55
 
56
  const modelDownloaded = downloadedModels.includes(settings.modelKey);
@@ -78,11 +87,18 @@ export default function Chat({ className = "" }: { className?: string }) {
78
  const model = MODELS[settings.modelKey];
79
  const ready: boolean = state === State.READY || modelDownloaded;
80
 
 
 
81
  return (
82
- <div className={cn(className, "flex flex-col gap-4 px-4 py-8")}>
83
- {state === State.IDLE && !modelDownloaded ? (
84
- <div className="flex h-full items-center justify-center">
85
- <Card className="flex max-w-[500px] flex-col gap-4">
 
 
 
 
 
86
  <p>
87
  You are about to load <b>{model.title}</b>.<br />
88
  Once downloaded, the model ({formatBytes(model.size)}) will be
@@ -98,26 +114,58 @@ export default function Chat({ className = "" }: { className?: string }) {
98
  Download Model ({formatBytes(model.size)})
99
  </Button>
100
  </Card>
101
- </div>
102
- ) : state === State.INITIALIZING ? (
103
- <div className="flex h-full items-center justify-center">
104
- <div className="flex h-full w-full max-w-[400px] flex-col items-center justify-center gap-2">
105
- <p className="flex w-full items-center justify-between">
106
- <span>
107
- {modelDownloaded
108
- ? "initializing the model..."
109
- : "downloading the model..."}
110
- </span>
111
- <span>{downloadProgress.toFixed(2)}%</span>
112
- </p>
113
- <Slider width={Math.round(downloadProgress)} />
114
  </div>
115
- </div>
116
- ) : (
117
- <div className="flex flex-col gap-4">
118
- {messages
119
- .filter(({ role }) => role !== "system")
120
- .map((message, index) => (
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
  <div
122
  className={cn("w-3/4", {
123
  "self-end": message.role === "user",
@@ -125,45 +173,41 @@ export default function Chat({ className = "" }: { className?: string }) {
125
  key={index}
126
  >
127
  {message.role === "user" ? (
128
- <Card>
129
- <p>{message.content}</p>
130
  </Card>
131
  ) : message.role === "assistant" ? (
132
- <div className="space-y-1">
133
- {message.content.map((part) =>
134
- part.type === "tool" ? (
135
- <MessageToolCall tool={part} />
136
- ) : (
137
- <MessageContent content={part.content} />
138
- )
139
- )}
140
- </div>
141
  ) : null}
142
  </div>
143
  ))}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
  </div>
145
- )}
146
- <Card className="mt-auto">
147
- <ChatForm
148
- disabled={!ready}
149
- onSubmit={(prompt) => generate(prompt)}
150
- isGenerating={state === State.GENERATING}
151
- onAbort={() => generator.abort()}
152
- />
153
- </Card>
154
- <div className="flex justify-between dark:border-gray-700">
155
- <Button
156
- iconLeft={<Settings />}
157
- size="xs"
158
- variant="ghost"
159
- color="mono"
160
- onClick={openSettingsModal}
161
- >
162
- {settings?.modelKey ? MODELS[settings.modelKey].title : ""} |{" "}
163
- {settings?.tools.length}/{TOOLS.length} tool
164
- {settings?.tools.length !== 1 ? "s" : ""} active
165
- </Button>
166
  </div>
167
- </div>
168
  );
169
  }
 
1
  import { Button, Card, Slider } from "@theme";
2
  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 {
 
13
  } from "react";
14
 
15
  import TextGeneration from "../textGeneration/TextGeneration.ts";
16
+ import type {
17
+ ChatMessageAssistant,
18
+ ChatMessageUser,
19
+ } from "../textGeneration/types.ts";
20
  import cn from "../utils/classnames.ts";
21
  import ChatForm from "./ChatForm.tsx";
22
+ import Message from "./Message.tsx";
 
23
 
24
  enum State {
25
  IDLE,
 
34
  const [downloadProgress, setDownloadProgress] = useState<number>(0);
35
  const [state, setState] = useState<State>(State.IDLE);
36
  const settingsKey = useRef<string>(null);
37
+ const [starterCategory, setStarterCategory] = useState<number>(0);
38
+ const messagesEndRef = useRef<HTMLDivElement>(null);
39
 
40
  const generator = useMemo(() => new TextGeneration(), []);
41
  const getSnapshot = () => generator.chatMessages;
 
56
  );
57
  }, [settings]);
58
 
59
+ useEffect(() => {
60
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
61
+ }, [messages]);
62
+
63
  if (!settings) return;
64
 
65
  const modelDownloaded = downloadedModels.includes(settings.modelKey);
 
87
  const model = MODELS[settings.modelKey];
88
  const ready: boolean = state === State.READY || modelDownloaded;
89
 
90
+ const conversationMessages = messages.filter(({ role }) => role !== "system");
91
+
92
  return (
93
+ <>
94
+ <div
95
+ className={cn(
96
+ className,
97
+ "mx-auto min-h-screen w-full max-w-4xl px-4 pt-24 pb-36"
98
+ )}
99
+ >
100
+ {state === State.IDLE && !modelDownloaded ? (
101
+ <Card className="mt-26 flex max-w-[500px] flex-col gap-4 self-center justify-self-center">
102
  <p>
103
  You are about to load <b>{model.title}</b>.<br />
104
  Once downloaded, the model ({formatBytes(model.size)}) will be
 
114
  Download Model ({formatBytes(model.size)})
115
  </Button>
116
  </Card>
117
+ ) : state === State.INITIALIZING ? (
118
+ <div className="mt-46 flex h-full items-center justify-center">
119
+ <div className="flex h-full w-full max-w-[400px] flex-col items-center justify-center gap-2">
120
+ <p className="flex w-full items-center justify-between">
121
+ <span>
122
+ {modelDownloaded
123
+ ? "initializing the model..."
124
+ : "downloading the model..."}
125
+ </span>
126
+ <span>{downloadProgress.toFixed(2)}%</span>
127
+ </p>
128
+ <Slider width={Math.round(downloadProgress)} />
129
+ </div>
130
  </div>
131
+ ) : conversationMessages.length === 0 ? (
132
+ <div className="mt-26 flex h-full items-center justify-center">
133
+ <div className="flex flex-col gap-8">
134
+ <h2 className="text-2xl font-bold">How can I help you?</h2>
135
+ <div className="flex gap-4">
136
+ {CONVERSATION_STARTERS.map((starter, index) => (
137
+ <Button
138
+ iconLeft={starter.icon}
139
+ key={index}
140
+ onClick={() => setStarterCategory(index)}
141
+ variant={index === starterCategory ? "solid" : "outline"}
142
+ >
143
+ {starter.category}
144
+ </Button>
145
+ ))}
146
+ </div>
147
+ <div className="gap- flex flex-col border-t border-gray-300 dark:border-gray-700">
148
+ {CONVERSATION_STARTERS[starterCategory].prompts.map(
149
+ (prompt, index) => (
150
+ <div className="border-b border-gray-300 py-1 dark:border-gray-700">
151
+ <Button
152
+ key={index}
153
+ onClick={() => generate(prompt)}
154
+ variant="ghost"
155
+ color="mono"
156
+ className="justify-start"
157
+ >
158
+ {prompt}
159
+ </Button>
160
+ </div>
161
+ )
162
+ )}
163
+ </div>
164
+ </div>
165
+ </div>
166
+ ) : (
167
+ <div className="flex flex-col gap-4">
168
+ {conversationMessages.map((message, index) => (
169
  <div
170
  className={cn("w-3/4", {
171
  "self-end": message.role === "user",
 
173
  key={index}
174
  >
175
  {message.role === "user" ? (
176
+ <Card className="mt-2 mb-12">
177
+ <p>{(message as ChatMessageUser).content}</p>
178
  </Card>
179
  ) : message.role === "assistant" ? (
180
+ <Message message={message as ChatMessageAssistant} />
 
 
 
 
 
 
 
 
181
  ) : null}
182
  </div>
183
  ))}
184
+ <div ref={messagesEndRef} />
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}
192
+ onSubmit={(prompt) => generate(prompt)}
193
+ isGenerating={state === State.GENERATING}
194
+ onAbort={() => generator.abort()}
195
+ />
196
+ </Card>
197
+ <div className="flex justify-between dark:border-gray-700">
198
+ <Button
199
+ iconLeft={<Settings />}
200
+ size="xs"
201
+ variant="ghost"
202
+ color="mono"
203
+ onClick={openSettingsModal}
204
+ >
205
+ {settings?.modelKey ? MODELS[settings.modelKey].title : ""} |{" "}
206
+ {settings?.tools.length}/{TOOLS.length} tool
207
+ {settings?.tools.length !== 1 ? "s" : ""} active
208
+ </Button>
209
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
  </div>
211
+ </>
212
  );
213
  }
src/chat/Message.tsx ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
6
+ import type { ChatMessageAssistant } from "../textGeneration/types.ts";
7
+ import MessageContent from "./MessageContent.tsx";
8
+ import MessageToolCall from "./MessageToolCall.tsx";
9
+
10
+ export default function Message({
11
+ message,
12
+ className = "",
13
+ }: {
14
+ message: ChatMessageAssistant;
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} />
40
+ ) : (
41
+ <MessageContent content={part.content} />
42
+ )
43
+ )}
44
+ {message.interrupted && (
45
+ <div className="mt-2 flex items-center gap-2 rounded-md border border-amber-300 bg-amber-50 px-3 py-2 text-sm text-amber-800 dark:border-amber-700 dark:bg-amber-950 dark:text-amber-200">
46
+ <StopCircle className="size-4 shrink-0" />
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
65
+ className="flex items-center gap-1 rounded-md px-1.5 py-0.5 transition-colors hover:bg-gray-300 hover:text-gray-900 dark:hover:bg-gray-600 dark:hover:text-gray-100"
66
+ onClick={() => setTemplateOpen(true)}
67
+ >
68
+ <SquareCode className="size-3 -translate-y-1/20" />
69
+ <span>Template</span>
70
+ </button>
71
+ </div>
72
+ )}
73
+ </div>
74
+ );
75
+ }
src/chat/MessageContent.tsx CHANGED
@@ -95,12 +95,15 @@ export default function MessageContent({ content }: { content: string }) {
95
  className="-ml-2 opacity-50"
96
  loading={isThinking}
97
  iconLeft={<Lightbulb />}
 
98
  >
99
  {isThinking ? "Thinking for" : "Thought for"}{" "}
100
  {thinkingTime.toFixed(1)}s ({showThinking ? "Hide" : "Show"})
101
  </Button>
102
  {showThinking && (
103
- <p className="max-w-none text-sm">{parsed.thinkContent}</p>
 
 
104
  )}
105
  </div>
106
  {parsed.afterContent && (
 
95
  className="-ml-2 opacity-50"
96
  loading={isThinking}
97
  iconLeft={<Lightbulb />}
98
+ notDisabledWhileLoading
99
  >
100
  {isThinking ? "Thinking for" : "Thought for"}{" "}
101
  {thinkingTime.toFixed(1)}s ({showThinking ? "Hide" : "Show"})
102
  </Button>
103
  {showThinking && (
104
+ <p className="my-4 max-w-none border-l-1 border-gray-300 pl-4 text-sm font-medium text-gray-600 dark:border-gray-700 dark:text-gray-400">
105
+ {parsed.thinkContent}
106
+ </p>
107
  )}
108
  </div>
109
  {parsed.afterContent && (
src/index.css CHANGED
@@ -23,7 +23,7 @@
23
  }
24
 
25
  body {
26
- background-color: white;
27
  color: black;
28
  --shiny-width: 50px;
29
  }
 
23
  }
24
 
25
  body {
26
+ background-color: #f6f6f6;
27
  color: black;
28
  --shiny-width: 50px;
29
  }
src/textGeneration/TextGeneration.ts CHANGED
@@ -145,6 +145,7 @@ export default class TextGeneration {
145
  response: string;
146
  modelUsage: ModelUsage;
147
  interrupted: boolean;
 
148
  }>((resolve, reject) => {
149
  if (this.modelKey === null) {
150
  reject("Model not initialized");
@@ -162,7 +163,6 @@ export default class TextGeneration {
162
 
163
  const requestId = this.requestId++;
164
  this.messages = [...this.messages, { role, content: prompt }];
165
- this.messages.push({ role: "assistant", content: "" });
166
 
167
  let response = "";
168
 
@@ -174,10 +174,12 @@ export default class TextGeneration {
174
  }
175
  if (data.type === ResponseType.GENERATE_TEXT_DONE) {
176
  this.removeWorkerEventListener(listener);
 
177
  resolve({
178
  response: data.response,
179
  modelUsage: data.modelUsage,
180
  interrupted: data.interrupted,
 
181
  });
182
  }
183
  if (data.type === ResponseType.GENERATE_TEXT_CHUNK) {
@@ -217,7 +219,7 @@ export default class TextGeneration {
217
 
218
  this.chatMessages = [...prevChatMessages, assistantMessage];
219
 
220
- const { modelUsage, interrupted } = await this.generateText(
221
  prompt,
222
  isUser ? "user" : "tool",
223
  (partialResponse) => {
@@ -242,6 +244,7 @@ export default class TextGeneration {
242
  );
243
  isUser = false;
244
 
 
245
  assistantMessage.modelUsage = modelUsage;
246
  assistantMessage.interrupted = interrupted;
247
  this.chatMessages = [...prevChatMessages, assistantMessage];
 
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");
 
163
 
164
  const requestId = this.requestId++;
165
  this.messages = [...this.messages, { role, content: prompt }];
 
166
 
167
  let response = "";
168
 
 
174
  }
175
  if (data.type === ResponseType.GENERATE_TEXT_DONE) {
176
  this.removeWorkerEventListener(listener);
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
 
220
  this.chatMessages = [...prevChatMessages, assistantMessage];
221
 
222
+ const { modelUsage, interrupted, template } = await this.generateText(
223
  prompt,
224
  isUser ? "user" : "tool",
225
  (partialResponse) => {
 
244
  );
245
  isUser = false;
246
 
247
+ assistantMessage.template = template;
248
  assistantMessage.modelUsage = modelUsage;
249
  assistantMessage.interrupted = interrupted;
250
  this.chatMessages = [...prevChatMessages, assistantMessage];
src/textGeneration/types.ts CHANGED
@@ -59,6 +59,7 @@ interface ResponseGenerateTextDone {
59
  type: ResponseType.GENERATE_TEXT_DONE;
60
  response: string;
61
  modelUsage: ModelUsage;
 
62
  interrupted: boolean;
63
  requestId: number;
64
  }
@@ -107,6 +108,7 @@ export interface ChatMessageAssistant {
107
  content: Array<ChatMessageAssistantResponse | ChatMessageAssistantTool>;
108
  interrupted: boolean;
109
  modelUsage?: ModelUsage;
 
110
  }
111
 
112
  export interface ChatMessageSystem {
 
59
  type: ResponseType.GENERATE_TEXT_DONE;
60
  response: string;
61
  modelUsage: ModelUsage;
62
+ template: string;
63
  interrupted: boolean;
64
  requestId: number;
65
  }
 
108
  content: Array<ChatMessageAssistantResponse | ChatMessageAssistantTool>;
109
  interrupted: boolean;
110
  modelUsage?: ModelUsage;
111
+ template?: string;
112
  }
113
 
114
  export interface ChatMessageSystem {
src/textGeneration/worker/textGenerationWorker.ts CHANGED
@@ -7,9 +7,9 @@ import {
7
  Tensor,
8
  TextStreamer,
9
  } from "@huggingface/transformers";
 
10
  import { calculateDownloadProgress } from "../../utils/calculateDownloadProgress.ts";
11
  import { MODELS } from "../../utils/models.ts";
12
-
13
  import {
14
  type Request,
15
  RequestType,
@@ -100,7 +100,6 @@ self.onmessage = async ({ data }: MessageEvent<Request>) => {
100
  stoppingCriteria = new InterruptableStoppingCriteria();
101
 
102
  const { messages, tools, requestId } = data;
103
- console.log(messages, tools);
104
  const { tokenizer, model } = await getTextGenerationPipeline(data.modelKey);
105
  if (!stoppingCriteria) {
106
  stoppingCriteria = new InterruptableStoppingCriteria();
@@ -170,6 +169,10 @@ self.onmessage = async ({ data }: MessageEvent<Request>) => {
170
  }
171
  )[0];
172
 
 
 
 
 
173
  cache = {
174
  pastKeyValues: past_key_values,
175
  key:
@@ -183,20 +186,19 @@ self.onmessage = async ({ data }: MessageEvent<Request>) => {
183
  ]),
184
  };
185
 
186
- console.log("response", response);
187
-
188
  postMessage({
189
  type: ResponseType.GENERATE_TEXT_DONE,
190
  response,
191
  modelUsage: {
192
- inputDurationMs: (firstTokenTime || 0) - started,
193
  outputTokens: numTokens,
194
- outputDurationMs: performance.now() - ended,
195
  outputTps: tps,
196
  doneMs: ended - started,
197
  modelKey: MODEL.modelId,
198
  model: MODEL.title,
199
  },
 
200
  interrupted: stoppingCriteria.interrupted,
201
  requestId,
202
  });
 
7
  Tensor,
8
  TextStreamer,
9
  } from "@huggingface/transformers";
10
+
11
  import { calculateDownloadProgress } from "../../utils/calculateDownloadProgress.ts";
12
  import { MODELS } from "../../utils/models.ts";
 
13
  import {
14
  type Request,
15
  RequestType,
 
100
  stoppingCriteria = new InterruptableStoppingCriteria();
101
 
102
  const { messages, tools, requestId } = data;
 
103
  const { tokenizer, model } = await getTextGenerationPipeline(data.modelKey);
104
  if (!stoppingCriteria) {
105
  stoppingCriteria = new InterruptableStoppingCriteria();
 
169
  }
170
  )[0];
171
 
172
+ const template = tokenizer.batch_decode(sequences, {
173
+ skip_special_tokens: false,
174
+ })[0];
175
+
176
  cache = {
177
  pastKeyValues: past_key_values,
178
  key:
 
186
  ]),
187
  };
188
 
 
 
189
  postMessage({
190
  type: ResponseType.GENERATE_TEXT_DONE,
191
  response,
192
  modelUsage: {
193
+ inputDurationMs: firstTokenTime - started,
194
  outputTokens: numTokens,
195
+ outputDurationMs: ended - firstTokenTime,
196
  outputTps: tps,
197
  doneMs: ended - started,
198
  modelKey: MODEL.modelId,
199
  model: MODEL.title,
200
  },
201
+ template,
202
  interrupted: stoppingCriteria.interrupted,
203
  requestId,
204
  });
src/theme/button/Button.tsx CHANGED
@@ -17,6 +17,7 @@ interface BaseButtonProps {
17
  iconRight?: ReactNode;
18
  disabled?: boolean;
19
  loading?: boolean;
 
20
  shiny?: boolean;
21
  }
22
 
@@ -104,6 +105,7 @@ export default function Button({
104
  iconRight,
105
  disabled = false,
106
  loading = false,
 
107
  shiny = false,
108
  ...props
109
  }: ButtonProps) {
@@ -119,6 +121,8 @@ export default function Button({
119
  className
120
  );
121
 
 
 
122
  const renderIcon = (icon: ReactNode) => {
123
  if (isValidElement(icon)) {
124
  return cloneElement(icon as any, {
@@ -144,7 +148,7 @@ export default function Button({
144
  className={baseClasses}
145
  target={props.target}
146
  rel={props.rel}
147
- aria-disabled={disabled || loading}
148
  >
149
  {content}
150
  </a>
@@ -155,7 +159,7 @@ export default function Button({
155
  <button
156
  type={(props as ButtonAsButton).type || "button"}
157
  onClick={(props as ButtonAsButton).onClick}
158
- disabled={disabled || loading}
159
  className={baseClasses}
160
  >
161
  {content}
 
17
  iconRight?: ReactNode;
18
  disabled?: boolean;
19
  loading?: boolean;
20
+ notDisabledWhileLoading?: boolean;
21
  shiny?: boolean;
22
  }
23
 
 
105
  iconRight,
106
  disabled = false,
107
  loading = false,
108
+ notDisabledWhileLoading = false,
109
  shiny = false,
110
  ...props
111
  }: ButtonProps) {
 
121
  className
122
  );
123
 
124
+ const isDisabled = disabled || (loading && !notDisabledWhileLoading);
125
+
126
  const renderIcon = (icon: ReactNode) => {
127
  if (isValidElement(icon)) {
128
  return cloneElement(icon as any, {
 
148
  className={baseClasses}
149
  target={props.target}
150
  rel={props.rel}
151
+ aria-disabled={isDisabled}
152
  >
153
  {content}
154
  </a>
 
159
  <button
160
  type={(props as ButtonAsButton).type || "button"}
161
  onClick={(props as ButtonAsButton).onClick}
162
+ disabled={isDisabled}
163
  className={baseClasses}
164
  >
165
  {content}
src/theme/misc/Card.tsx CHANGED
@@ -12,7 +12,7 @@ export default function Card({
12
  <div
13
  className={cn(
14
  className,
15
- "rounded-lg border border-gray-200 bg-white p-4 dark:border-gray-700 dark:bg-gray-800"
16
  )}
17
  >
18
  {children}
 
12
  <div
13
  className={cn(
14
  className,
15
+ "rounded-lg border border-gray-300 bg-gray-100 p-4 shadow-md dark:border-gray-700 dark:bg-gray-800"
16
  )}
17
  >
18
  {children}
src/utils/models.ts CHANGED
@@ -1,3 +1,6 @@
 
 
 
1
  export interface Model {
2
  modelId: string;
3
  title: string;
@@ -135,3 +138,36 @@ export const MODELS: Record<string, Model> = {
135
  export type ModelKey = keyof typeof MODELS;
136
 
137
  export const DEFAULT_SYSTEM_PROMPT = "You are a helpful AI Assistant";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { CircleQuestionMark, Pen, Sparkles } from "lucide-react";
2
+ import { createElement } from "react";
3
+
4
  export interface Model {
5
  modelId: string;
6
  title: string;
 
138
  export type ModelKey = keyof typeof MODELS;
139
 
140
  export const DEFAULT_SYSTEM_PROMPT = "You are a helpful AI Assistant";
141
+
142
+ export const CONVERSATION_STARTERS = [
143
+ {
144
+ category: "Getting Started",
145
+ icon: createElement(Pen),
146
+ prompts: [
147
+ "Tell me something about yourself. Who are you and who trained you?",
148
+ "What can you help me with today?",
149
+ "Explain how you work in simple terms",
150
+ "What are your strengths and limitations?",
151
+ ],
152
+ },
153
+ {
154
+ category: "Creative Writing",
155
+ icon: createElement(Sparkles),
156
+ prompts: [
157
+ "Write a short story about a robot discovering emotions",
158
+ "Create a character profile for a friendly neighborhood baker",
159
+ "Give me 3 creative writing prompts for short stories",
160
+ "Write a dialogue between two friends planning a road trip",
161
+ ],
162
+ },
163
+ {
164
+ category: "Everyday Help",
165
+ icon: createElement(CircleQuestionMark),
166
+ prompts: [
167
+ "Help me write a polite email to reschedule a meeting",
168
+ "Give me 5 tips for staying productive while working from home",
169
+ "Suggest some healthy meal ideas for busy weekdays",
170
+ "How do I organize my daily tasks more effectively?",
171
+ ],
172
+ },
173
+ ];