machineuser commited on
Commit
3f534ed
1 Parent(s): e54c25c

Sync widgets demo

Browse files
packages/widgets/src/lib/components/Icons/IconRefresh.svelte ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ export let classNames = "";
3
+ </script>
4
+
5
+ <svg
6
+ class={classNames}
7
+ xmlns="http://www.w3.org/2000/svg"
8
+ xmlns:xlink="http://www.w3.org/1999/xlink"
9
+ aria-hidden="true"
10
+ role="img"
11
+ width="1em"
12
+ height="1em"
13
+ preserveAspectRatio="xMidYMid meet"
14
+ viewBox="0 0 32 32"
15
+ ><path
16
+ d="M25.95 7.65l.005-.004c-.092-.11-.197-.206-.293-.312c-.184-.205-.367-.41-.563-.603c-.139-.136-.286-.262-.43-.391c-.183-.165-.366-.329-.558-.482c-.16-.128-.325-.247-.49-.367c-.192-.14-.385-.277-.585-.406a13.513 13.513 0 0 0-.533-.324q-.308-.179-.625-.341c-.184-.094-.37-.185-.56-.27c-.222-.1-.449-.191-.678-.28c-.19-.072-.378-.145-.571-.208c-.246-.082-.498-.15-.75-.217c-.186-.049-.368-.102-.556-.143c-.29-.063-.587-.107-.883-.15c-.16-.023-.315-.056-.476-.073A12.933 12.933 0 0 0 6 7.703V4H4v8h8v-2H6.811A10.961 10.961 0 0 1 16 5a11.111 11.111 0 0 1 1.189.067c.136.015.268.042.403.061c.25.037.501.075.746.128c.16.035.315.08.472.121c.213.057.425.114.633.183c.164.054.325.116.486.178c.193.074.384.15.57.235c.162.072.32.15.477.23q.268.136.526.286c.153.09.305.18.453.276c.168.11.33.224.492.342c.14.102.282.203.417.312c.162.13.316.268.47.406c.123.11.248.217.365.332c.167.164.323.338.479.512A10.993 10.993 0 1 1 5 16H3a13 13 0 1 0 22.95-8.35z"
17
+ fill="currentColor"
18
+ /></svg
19
+ >
packages/widgets/src/lib/components/InferenceWidget/shared/WidgetExamples/WidgetExamples.svelte CHANGED
@@ -12,6 +12,7 @@
12
 
13
  export let isLoading = false;
14
  export let callApiOnMount: WidgetProps["callApiOnMount"];
 
15
  export let exampleQueryParams: WidgetExampleAttribute[] = [];
16
  export let applyWidgetExample: (sample: TWidgetExample, opts?: ExampleRunOpts) => void;
17
 
@@ -117,7 +118,7 @@
117
 
118
  <svelte:window on:click={onClick} />
119
 
120
- <div class="ml-auto flex gap-x-1">
121
  <!-- Example Groups -->
122
  {#if exampleGroups.length > 1}
123
  <WidgetExamplesGroup
 
12
 
13
  export let isLoading = false;
14
  export let callApiOnMount: WidgetProps["callApiOnMount"];
15
+ export let classNames: string;
16
  export let exampleQueryParams: WidgetExampleAttribute[] = [];
17
  export let applyWidgetExample: (sample: TWidgetExample, opts?: ExampleRunOpts) => void;
18
 
 
118
 
119
  <svelte:window on:click={onClick} />
120
 
121
+ <div class={classNames}>
122
  <!-- Example Groups -->
123
  {#if exampleGroups.length > 1}
124
  <WidgetExamplesGroup
packages/widgets/src/lib/components/InferenceWidget/shared/WidgetHeader/WidgetHeader.svelte CHANGED
@@ -1,13 +1,17 @@
1
  <script lang="ts" generics="TWidgetExample extends WidgetExample">
 
 
2
  import { updateWidgetState } from "../../stores.js";
3
  import { TASKS_DATA } from "@huggingface/tasks";
4
  import type { WidgetExample, WidgetExampleAttribute } from "@huggingface/tasks";
5
  import type { WidgetProps, ExampleRunOpts } from "../types.js";
6
  import { getPipelineTask } from "../../../../utils/ViewUtils.js";
7
  import IconInfo from "../../..//Icons/IconInfo.svelte";
 
8
  import IconLightning from "../../..//Icons/IconLightning.svelte";
9
  import PipelineTag from "../../../PipelineTag/PipelineTag.svelte";
10
  import WidgetExamples from "../WidgetExamples/WidgetExamples.svelte";
 
11
 
12
  export let model: WidgetProps["model"];
13
  export let noTitle = false;
@@ -18,7 +22,9 @@
18
  export let validateExample: ((sample: WidgetExample) => sample is TWidgetExample) | undefined = undefined;
19
  export let callApiOnMount: WidgetProps["callApiOnMount"] = false;
20
  export let exampleQueryParams: WidgetExampleAttribute[] = [];
 
21
 
 
22
  const pipeline = model?.pipeline_tag;
23
 
24
  $: task = pipeline ? getPipelineTask(pipeline) : undefined;
@@ -62,7 +68,7 @@
62
  {/if}
63
  {/if}
64
  </div>
65
- <div class="mb-0.5 flex w-full max-w-full flex-wrap items-center justify-between text-sm text-gray-500">
66
  {#if pipeline && task}
67
  <div class="flex gap-4 items-center mb-1.5">
68
  <a
@@ -75,7 +81,21 @@
75
  </div>
76
  {/if}
77
 
78
- {#if validExamples.length && applyWidgetExample}
79
- <WidgetExamples {validExamples} {isLoading} {applyWidgetExample} {callApiOnMount} {exampleQueryParams} />
80
- {/if}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  </div>
 
1
  <script lang="ts" generics="TWidgetExample extends WidgetExample">
2
+ import { fade } from "svelte/transition";
3
+
4
  import { updateWidgetState } from "../../stores.js";
5
  import { TASKS_DATA } from "@huggingface/tasks";
6
  import type { WidgetExample, WidgetExampleAttribute } from "@huggingface/tasks";
7
  import type { WidgetProps, ExampleRunOpts } from "../types.js";
8
  import { getPipelineTask } from "../../../../utils/ViewUtils.js";
9
  import IconInfo from "../../..//Icons/IconInfo.svelte";
10
+ import IconRefresh from "../../..//Icons/IconRefresh.svelte";
11
  import IconLightning from "../../..//Icons/IconLightning.svelte";
12
  import PipelineTag from "../../../PipelineTag/PipelineTag.svelte";
13
  import WidgetExamples from "../WidgetExamples/WidgetExamples.svelte";
14
+ import { createEventDispatcher } from "svelte";
15
 
16
  export let model: WidgetProps["model"];
17
  export let noTitle = false;
 
22
  export let validateExample: ((sample: WidgetExample) => sample is TWidgetExample) | undefined = undefined;
23
  export let callApiOnMount: WidgetProps["callApiOnMount"] = false;
24
  export let exampleQueryParams: WidgetExampleAttribute[] = [];
25
+ export let showReset = false;
26
 
27
+ const dispatch = createEventDispatcher<{ reset: void }>();
28
  const pipeline = model?.pipeline_tag;
29
 
30
  $: task = pipeline ? getPipelineTask(pipeline) : undefined;
 
68
  {/if}
69
  {/if}
70
  </div>
71
+ <div class="mb-0.5 flex w-full max-w-full flex-wrap items-center text-sm text-gray-500">
72
  {#if pipeline && task}
73
  <div class="flex gap-4 items-center mb-1.5">
74
  <a
 
81
  </div>
82
  {/if}
83
 
84
+ <div class="flex gap-2 ml-auto">
85
+ {#if showReset && !isDisabled}
86
+ <button class="flex items-center mb-1.5 text-gray-400" on:click={() => dispatch("reset")} transition:fade>
87
+ <IconRefresh />
88
+ </button>
89
+ {/if}
90
+ {#if validExamples.length && applyWidgetExample}
91
+ <WidgetExamples
92
+ classNames="flex gap-x-1 peer:"
93
+ {validExamples}
94
+ {isLoading}
95
+ {applyWidgetExample}
96
+ {callApiOnMount}
97
+ {exampleQueryParams}
98
+ />
99
+ {/if}
100
+ </div>
101
  </div>
packages/widgets/src/lib/components/InferenceWidget/shared/WidgetOutputConvo/WidgetOutputConvo.svelte CHANGED
@@ -4,11 +4,13 @@
4
  import { isFullyScrolled, scrollToMax } from "../../../../utils/ViewUtils.js";
5
  import WidgetOutputConvoBubble from "../WidgetOuputConvoBubble/WidgetOutputConvoBubble.svelte";
6
  import type { ChatMessage } from "@huggingface/tasks";
 
7
 
8
  export let modelId: string;
9
  export let messages: ChatMessage[];
10
 
11
  let wrapperEl: HTMLElement;
 
12
 
13
  afterUpdate(() => {
14
  if (wrapperEl && !isFullyScrolled(wrapperEl)) {
@@ -17,7 +19,10 @@
17
  });
18
  </script>
19
 
20
- <div bind:this={wrapperEl} class="h-64 overflow-y-auto rounded-t-lg border border-b-0 leading-tight">
 
 
 
21
  <div class="p-3 pt-6 text-center text-sm text-gray-400 text-balance">
22
  Input a message to start chatting with
23
  <strong>{modelId}</strong>.
 
4
  import { isFullyScrolled, scrollToMax } from "../../../../utils/ViewUtils.js";
5
  import WidgetOutputConvoBubble from "../WidgetOuputConvoBubble/WidgetOutputConvoBubble.svelte";
6
  import type { ChatMessage } from "@huggingface/tasks";
7
+ import { widgetStates } from "../../stores.js";
8
 
9
  export let modelId: string;
10
  export let messages: ChatMessage[];
11
 
12
  let wrapperEl: HTMLElement;
13
+ $: isMaximized = $widgetStates?.[modelId]?.isMaximized;
14
 
15
  afterUpdate(() => {
16
  if (wrapperEl && !isFullyScrolled(wrapperEl)) {
 
19
  });
20
  </script>
21
 
22
+ <div
23
+ bind:this={wrapperEl}
24
+ class="overflow-y-auto rounded-t-lg border border-b-0 leading-tight {isMaximized ? 'flex-1' : 'h-64'}"
25
+ >
26
  <div class="p-3 pt-6 text-center text-sm text-gray-400 text-balance">
27
  Input a message to start chatting with
28
  <strong>{modelId}</strong>.
packages/widgets/src/lib/components/InferenceWidget/shared/WidgetQuickInput/WidgetQuickInput.svelte CHANGED
@@ -18,6 +18,7 @@
18
  required={true}
19
  type="text"
20
  disabled={isLoading || isDisabled}
 
21
  />
22
  <WidgetSubmitBtn
23
  classNames="rounded-l-none border-l-0 {flatTop ? 'rounded-t-none' : ''}"
 
18
  required={true}
19
  type="text"
20
  disabled={isLoading || isDisabled}
21
+ autocomplete="off"
22
  />
23
  <WidgetSubmitBtn
24
  classNames="rounded-l-none border-l-0 {flatTop ? 'rounded-t-none' : ''}"
packages/widgets/src/lib/components/InferenceWidget/shared/inputValidation.ts CHANGED
@@ -14,7 +14,7 @@ import type {
14
  WidgetExampleZeroShotTextInput,
15
  } from "@huggingface/tasks";
16
 
17
- function isObject(arg: unknown): arg is Record<string, unknown> {
18
  return !!arg && arg?.constructor === Object;
19
  }
20
  function isStrArray(arg: unknown): arg is string[] {
 
14
  WidgetExampleZeroShotTextInput,
15
  } from "@huggingface/tasks";
16
 
17
+ export function isObject(arg: unknown): arg is Record<string, unknown> {
18
  return !!arg && arg?.constructor === Object;
19
  }
20
  function isStrArray(arg: unknown): arg is string[] {
packages/widgets/src/lib/components/InferenceWidget/widgets/ConversationalWidget/ConversationalWidget.svelte CHANGED
@@ -22,9 +22,8 @@
22
  import { addInferenceParameters, updateUrl } from "../../shared/helpers.js";
23
  import { widgetStates, getTgiSupportedModels } from "../../stores.js";
24
  import type { Writable } from "svelte/store";
25
- import { isChatInput, isTextInput } from "../../shared/inputValidation.js";
26
  import { isValidOutputText } from "../../shared/outputValidation.js";
27
- import WidgetExamples from "../../shared/WidgetExamples/WidgetExamples.svelte";
28
 
29
  export let apiToken: WidgetProps["apiToken"];
30
  export let apiUrl: WidgetProps["apiUrl"];
@@ -49,6 +48,7 @@
49
  let compiledTemplate: Template;
50
  let tokenizerConfig: TokenizerConfig;
51
  let inferenceClient: HfInference | undefined = undefined;
 
52
 
53
  // Check config and compile template
54
  onMount(() => {
@@ -146,7 +146,10 @@
146
  };
147
  addInferenceParameters(input, model);
148
 
 
 
149
  text = "";
 
150
  try {
151
  if ($tgiSupportedModels?.has(model.id)) {
152
  console.debug("Starting text generation using the TGI streaming API");
@@ -155,11 +158,14 @@
155
  content: "",
156
  } satisfies ChatMessage;
157
  const previousMessages = [...messages];
158
- const tokenStream = inferenceClient.textGenerationStream({
159
- ...input,
160
- model: model.id,
161
- accessToken: apiToken,
162
- });
 
 
 
163
  for await (const newToken of tokenStream) {
164
  if (newToken.token.special) continue;
165
  newMessage.content = newMessage.content + newToken.token.text;
@@ -171,13 +177,20 @@
171
  input.parameters.max_new_tokens = 100;
172
  const output = await inferenceClient.textGeneration(
173
  { ...input, model: model.id, accessToken: apiToken },
174
- { includeCredentials, dont_load_model: !withModelLoading }
175
  );
176
  messages = [...messages, { role: "assistant", content: output.generated_text }];
177
  await tick();
178
  }
179
  } catch (e) {
180
- error = `Something went wrong while requesting the Inference API: "${(e as Error).message}"`;
 
 
 
 
 
 
 
181
  }
182
  }
183
 
@@ -218,10 +231,28 @@
218
  function validateExample(sample: WidgetExample): sample is Example {
219
  return (isTextInput(sample) || isChatInput(sample)) && (!sample.output || isValidOutputText(sample.output));
220
  }
 
 
 
 
 
 
 
 
221
  </script>
222
 
223
  <WidgetWrapper {apiUrl} {includeCredentials} {model} let:WidgetInfo let:WidgetHeader let:WidgetFooter>
224
- <WidgetHeader {noTitle} {model} {isLoading} {isDisabled} {callApiOnMount} {applyWidgetExample} {validateExample} />
 
 
 
 
 
 
 
 
 
 
225
  <WidgetOutputConvo modelId={model.id} {messages} />
226
 
227
  <WidgetQuickInput
 
22
  import { addInferenceParameters, updateUrl } from "../../shared/helpers.js";
23
  import { widgetStates, getTgiSupportedModels } from "../../stores.js";
24
  import type { Writable } from "svelte/store";
25
+ import { isChatInput, isObject, isTextInput } from "../../shared/inputValidation.js";
26
  import { isValidOutputText } from "../../shared/outputValidation.js";
 
27
 
28
  export let apiToken: WidgetProps["apiToken"];
29
  export let apiUrl: WidgetProps["apiUrl"];
 
48
  let compiledTemplate: Template;
49
  let tokenizerConfig: TokenizerConfig;
50
  let inferenceClient: HfInference | undefined = undefined;
51
+ let abort: AbortController | undefined = undefined;
52
 
53
  // Check config and compile template
54
  onMount(() => {
 
146
  };
147
  addInferenceParameters(input, model);
148
 
149
+ isLoading = true;
150
+ abort = new AbortController();
151
  text = "";
152
+ error = "";
153
  try {
154
  if ($tgiSupportedModels?.has(model.id)) {
155
  console.debug("Starting text generation using the TGI streaming API");
 
158
  content: "",
159
  } satisfies ChatMessage;
160
  const previousMessages = [...messages];
161
+ const tokenStream = inferenceClient.textGenerationStream(
162
+ {
163
+ ...input,
164
+ model: model.id,
165
+ accessToken: apiToken,
166
+ },
167
+ { signal: abort?.signal }
168
+ );
169
  for await (const newToken of tokenStream) {
170
  if (newToken.token.special) continue;
171
  newMessage.content = newMessage.content + newToken.token.text;
 
177
  input.parameters.max_new_tokens = 100;
178
  const output = await inferenceClient.textGeneration(
179
  { ...input, model: model.id, accessToken: apiToken },
180
+ { includeCredentials, dont_load_model: !withModelLoading, signal: abort?.signal }
181
  );
182
  messages = [...messages, { role: "assistant", content: output.generated_text }];
183
  await tick();
184
  }
185
  } catch (e) {
186
+ if (!!e && typeof e === "object" && "message" in e && typeof e.message === "string") {
187
+ error = e.message;
188
+ } else {
189
+ error = `Something went wrong with the request.`;
190
+ }
191
+ } finally {
192
+ isLoading = false;
193
+ abort = undefined;
194
  }
195
  }
196
 
 
231
  function validateExample(sample: WidgetExample): sample is Example {
232
  return (isTextInput(sample) || isChatInput(sample)) && (!sample.output || isValidOutputText(sample.output));
233
  }
234
+
235
+ async function clearConversation() {
236
+ error = "";
237
+ abort?.abort();
238
+ messages = [];
239
+ text = "";
240
+ await tick();
241
+ }
242
  </script>
243
 
244
  <WidgetWrapper {apiUrl} {includeCredentials} {model} let:WidgetInfo let:WidgetHeader let:WidgetFooter>
245
+ <WidgetHeader
246
+ {noTitle}
247
+ {model}
248
+ {isLoading}
249
+ {isDisabled}
250
+ {callApiOnMount}
251
+ {applyWidgetExample}
252
+ {validateExample}
253
+ on:reset={clearConversation}
254
+ showReset={!!messages.length}
255
+ />
256
  <WidgetOutputConvo modelId={model.id} {messages} />
257
 
258
  <WidgetQuickInput
packages/widgets/src/routes/+page.svelte CHANGED
@@ -31,19 +31,21 @@
31
 
32
  const models: ModelData[] = [
33
  {
34
- id: "HuggingFaceH4/zephyr-7b-beta",
35
  pipeline_tag: "text-generation",
36
  tags: ["conversational"],
37
  inference: InferenceDisplayability.Yes,
38
  config: {
 
 
39
  tokenizer_config: {
40
- bos_token: "<s>",
41
  chat_template:
42
- "{% for message in messages %}\n{% if message['role'] == 'user' %}\n{{ '<|user|>\n' + message['content'] + eos_token }}\n{% elif message['role'] == 'system' %}\n{{ '<|system|>\n' + message['content'] + eos_token }}\n{% elif message['role'] == 'assistant' %}\n{{ '<|assistant|>\n' + message['content'] + eos_token }}\n{% endif %}\n{% if loop.last and add_generation_prompt %}\n{{ '<|assistant|>' }}\n{% endif %}\n{% endfor %}",
 
 
43
  eos_token: "</s>",
44
- pad_token: "</s>",
45
  unk_token: "<unk>",
46
- use_default_system_prompt: true,
47
  },
48
  },
49
  widgetData: [
 
31
 
32
  const models: ModelData[] = [
33
  {
34
+ id: "mistralai/Mistral-7B-Instruct-v0.2",
35
  pipeline_tag: "text-generation",
36
  tags: ["conversational"],
37
  inference: InferenceDisplayability.Yes,
38
  config: {
39
+ architectures: ["MistralForCausalLM"],
40
+ model_type: "mistral",
41
  tokenizer_config: {
 
42
  chat_template:
43
+ "{{ bos_token }}{% for message in messages %}{% if (message['role'] == 'user') != (loop.index0 % 2 == 0) %}{{ raise_exception('Conversation roles must alternate user/assistant/user/assistant/...') }}{% endif %}{% if message['role'] == 'user' %}{{ '[INST] ' + message['content'] + ' [/INST]' }}{% elif message['role'] == 'assistant' %}{{ message['content'] + eos_token}}{% else %}{{ raise_exception('Only user and assistant roles are supported!') }}{% endif %}{% endfor %}",
44
+ use_default_system_prompt: false,
45
+ bos_token: "<s>",
46
  eos_token: "</s>",
 
47
  unk_token: "<unk>",
48
+ pad_token: undefined,
49
  },
50
  },
51
  widgetData: [