Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
Thomas G. Lopes
commited on
Commit
·
8cfdfbf
1
Parent(s):
8816e9f
fetch token from parent frame
Browse files- .env.example +1 -0
- src/lib/components/debug-menu.svelte +0 -6
- src/lib/components/inference-playground/hf-token-modal.svelte +0 -121
- src/lib/components/inference-playground/playground.svelte +0 -43
- src/lib/state/conversations.svelte.ts +2 -2
- src/lib/state/token.svelte.ts +36 -3
- src/lib/utils/business.svelte.ts +29 -15
- src/lib/utils/stream.ts +3 -0
- src/routes/api/generate/+server.ts +12 -1
.env.example
CHANGED
|
@@ -1,4 +1,5 @@
|
|
| 1 |
PUBLIC_ENABLE_MCP=
|
|
|
|
| 2 |
HYPERBOLIC_API_KEY=
|
| 3 |
COHERE_API_KEY=
|
| 4 |
TOGETHER_API_KEY=
|
|
|
|
| 1 |
PUBLIC_ENABLE_MCP=
|
| 2 |
+
PUBLIC_HF_TOKEN=
|
| 3 |
HYPERBOLIC_API_KEY=
|
| 4 |
COHERE_API_KEY=
|
| 5 |
TOGETHER_API_KEY=
|
src/lib/components/debug-menu.svelte
CHANGED
|
@@ -46,12 +46,6 @@
|
|
| 46 |
showQuotaModal();
|
| 47 |
},
|
| 48 |
},
|
| 49 |
-
{
|
| 50 |
-
label: "Show token modal",
|
| 51 |
-
cb: () => {
|
| 52 |
-
token.showModal = true;
|
| 53 |
-
},
|
| 54 |
-
},
|
| 55 |
{
|
| 56 |
label: "Test toast",
|
| 57 |
cb: () => {
|
|
|
|
| 46 |
showQuotaModal();
|
| 47 |
},
|
| 48 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
{
|
| 50 |
label: "Test toast",
|
| 51 |
cb: () => {
|
src/lib/components/inference-playground/hf-token-modal.svelte
DELETED
|
@@ -1,121 +0,0 @@
|
|
| 1 |
-
<script lang="ts">
|
| 2 |
-
import { createBubbler, preventDefault } from "svelte/legacy";
|
| 3 |
-
|
| 4 |
-
const bubble = createBubbler();
|
| 5 |
-
import { clickOutside } from "$lib/attachments/click-outside.js";
|
| 6 |
-
import { createEventDispatcher, onDestroy, onMount } from "svelte";
|
| 7 |
-
|
| 8 |
-
import IconCross from "~icons/carbon/close";
|
| 9 |
-
import { autofocus } from "$lib/attachments/autofocus.js";
|
| 10 |
-
|
| 11 |
-
interface Props {
|
| 12 |
-
storeLocallyHfToken?: boolean;
|
| 13 |
-
}
|
| 14 |
-
|
| 15 |
-
let { storeLocallyHfToken = $bindable(false) }: Props = $props();
|
| 16 |
-
|
| 17 |
-
let backdropEl = $state<HTMLDivElement>();
|
| 18 |
-
let modalEl = $state<HTMLDivElement>();
|
| 19 |
-
|
| 20 |
-
const dispatch = createEventDispatcher<{ close: void }>();
|
| 21 |
-
|
| 22 |
-
function handleKeydown(event: KeyboardEvent) {
|
| 23 |
-
const { key } = event;
|
| 24 |
-
if (key === "Escape") {
|
| 25 |
-
event.preventDefault();
|
| 26 |
-
dispatch("close");
|
| 27 |
-
}
|
| 28 |
-
}
|
| 29 |
-
|
| 30 |
-
onMount(() => {
|
| 31 |
-
document.getElementById("app")?.setAttribute("inert", "true");
|
| 32 |
-
});
|
| 33 |
-
|
| 34 |
-
onDestroy(() => {
|
| 35 |
-
// remove inert attribute if this is the last modal
|
| 36 |
-
if (document.querySelectorAll('[role="dialog"]:not(#app *)').length === 1) {
|
| 37 |
-
document.getElementById("app")?.removeAttribute("inert");
|
| 38 |
-
}
|
| 39 |
-
});
|
| 40 |
-
</script>
|
| 41 |
-
|
| 42 |
-
<div
|
| 43 |
-
id="default-modal"
|
| 44 |
-
aria-hidden="true"
|
| 45 |
-
class="fixed inset-0 z-50 flex items-center justify-center overflow-hidden bg-black/85"
|
| 46 |
-
bind:this={backdropEl}
|
| 47 |
-
>
|
| 48 |
-
<div
|
| 49 |
-
role="dialog"
|
| 50 |
-
tabindex="-1"
|
| 51 |
-
class="relative max-h-full w-full max-w-xl p-4 outline-hidden"
|
| 52 |
-
bind:this={modalEl}
|
| 53 |
-
onkeydown={handleKeydown}
|
| 54 |
-
{@attach clickOutside(() => dispatch("close"))}
|
| 55 |
-
>
|
| 56 |
-
<form onsubmit={preventDefault(bubble("submit"))} class="relative rounded-lg bg-white shadow-sm dark:bg-gray-900">
|
| 57 |
-
<div class="flex items-center justify-between rounded-t border-b p-4 md:px-5 md:py-4 dark:border-gray-800">
|
| 58 |
-
<h3 class="flex items-center gap-2.5 text-lg font-semibold text-gray-900 dark:text-white">
|
| 59 |
-
<img
|
| 60 |
-
alt="Hugging Face's logo"
|
| 61 |
-
class="w-7"
|
| 62 |
-
src="https://huggingface.co/front/assets/huggingface_logo-noborder.svg"
|
| 63 |
-
/> Add a Hugging Face Token
|
| 64 |
-
</h3>
|
| 65 |
-
<button
|
| 66 |
-
type="button"
|
| 67 |
-
onclick={() => dispatch("close")}
|
| 68 |
-
class="ms-auto inline-flex h-8 w-8 items-center justify-center rounded-lg bg-transparent text-sm text-gray-400 hover:bg-gray-200 hover:text-gray-900 dark:hover:bg-gray-600 dark:hover:text-white"
|
| 69 |
-
>
|
| 70 |
-
<div class="text-xl">
|
| 71 |
-
<IconCross />
|
| 72 |
-
</div>
|
| 73 |
-
<span class="sr-only">Close modal</span>
|
| 74 |
-
</button>
|
| 75 |
-
</div>
|
| 76 |
-
<!-- Modal body -->
|
| 77 |
-
<div class="p-4 md:p-5">
|
| 78 |
-
<p class="mb-5 text-base leading-relaxed text-gray-800 2xl:text-balance dark:text-gray-300">
|
| 79 |
-
You need a free Hugging Face token to use this application. <strong class="font-semibold"
|
| 80 |
-
>Make sure you create a token with Inference API permission.</strong
|
| 81 |
-
><br /> Your token is kept safe by only being used from your browser.
|
| 82 |
-
</p>
|
| 83 |
-
<div>
|
| 84 |
-
<label for="hf-token" class="mb-2 block text-sm font-medium text-gray-900 dark:text-white">
|
| 85 |
-
Hugging Face Token
|
| 86 |
-
</label>
|
| 87 |
-
<input
|
| 88 |
-
required
|
| 89 |
-
placeholder="Enter HF Token"
|
| 90 |
-
type="text"
|
| 91 |
-
id="hf-token"
|
| 92 |
-
name="hf-token"
|
| 93 |
-
class="block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-blue-500 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-blue-500 dark:focus:ring-blue-500"
|
| 94 |
-
{@attach autofocus()}
|
| 95 |
-
/>
|
| 96 |
-
</div>
|
| 97 |
-
<label class="mt-4 flex items-center gap-x-1.5 text-gray-900 dark:text-gray-200">
|
| 98 |
-
<input type="checkbox" bind:checked={storeLocallyHfToken} />
|
| 99 |
-
<p class="text-sm leading-none">Save to local storage for future use</p></label
|
| 100 |
-
>
|
| 101 |
-
</div>
|
| 102 |
-
|
| 103 |
-
<!-- Modal footer -->
|
| 104 |
-
<div class="flex items-center justify-between rounded-b border-t border-gray-200 p-4 md:p-5 dark:border-gray-800">
|
| 105 |
-
<a
|
| 106 |
-
href="https://huggingface.co/settings/tokens/new?ownUserPermissions=inference.serverless.write&tokenType=fineGrained"
|
| 107 |
-
tabindex="-1"
|
| 108 |
-
target="_blank"
|
| 109 |
-
class="rounded-lg border border-gray-200 bg-white px-5 py-2.5 text-sm font-medium text-gray-900 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 focus:outline-hidden dark:border-gray-600 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white dark:focus:ring-gray-700"
|
| 110 |
-
>Create new token</a
|
| 111 |
-
>
|
| 112 |
-
|
| 113 |
-
<button
|
| 114 |
-
type="submit"
|
| 115 |
-
class="rounded-lg bg-black px-5 py-2.5 text-sm font-medium text-white hover:bg-gray-900 focus:ring-4 focus:ring-gray-300 focus:outline-hidden dark:border-gray-700 dark:bg-gray-800 dark:hover:bg-gray-700 dark:focus:ring-gray-700"
|
| 116 |
-
>Submit</button
|
| 117 |
-
>
|
| 118 |
-
</div>
|
| 119 |
-
</form>
|
| 120 |
-
</div>
|
| 121 |
-
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/lib/components/inference-playground/playground.svelte
CHANGED
|
@@ -25,7 +25,6 @@
|
|
| 25 |
import PlaygroundConversationHeader from "./conversation-header.svelte";
|
| 26 |
import PlaygroundConversation from "./conversation.svelte";
|
| 27 |
import GenerationConfig from "./generation-config.svelte";
|
| 28 |
-
import HFTokenModal from "./hf-token-modal.svelte";
|
| 29 |
import MessageTextarea from "./message-textarea.svelte";
|
| 30 |
import ModelSelectorModal from "./model-selector-modal.svelte";
|
| 31 |
import ModelSelector from "./model-selector.svelte";
|
|
@@ -39,30 +38,8 @@
|
|
| 39 |
|
| 40 |
const systemPromptSupported = $derived(conversations.active.some(c => isSystemPromptSupported(c.model)));
|
| 41 |
const compareActive = $derived(conversations.active.length === 2);
|
| 42 |
-
|
| 43 |
-
function handleTokenSubmit(e: Event) {
|
| 44 |
-
const form = e.target as HTMLFormElement;
|
| 45 |
-
const formData = new FormData(form);
|
| 46 |
-
const submittedHfToken = (formData.get("hf-token") as string).trim() ?? "";
|
| 47 |
-
const RE_HF_TOKEN = /\bhf_[a-zA-Z0-9]{34}\b/;
|
| 48 |
-
if (RE_HF_TOKEN.test(submittedHfToken)) {
|
| 49 |
-
token.value = submittedHfToken;
|
| 50 |
-
// TODO: Only submit when previous action was trying to submit
|
| 51 |
-
// submit();
|
| 52 |
-
} else {
|
| 53 |
-
alert("Please provide a valid HF token.");
|
| 54 |
-
}
|
| 55 |
-
}
|
| 56 |
</script>
|
| 57 |
|
| 58 |
-
{#if token.showModal}
|
| 59 |
-
<HFTokenModal
|
| 60 |
-
bind:storeLocallyHfToken={token.writeToLocalStorage}
|
| 61 |
-
on:close={() => (token.showModal = false)}
|
| 62 |
-
on:submit={handleTokenSubmit}
|
| 63 |
-
/>
|
| 64 |
-
{/if}
|
| 65 |
-
|
| 66 |
<div
|
| 67 |
class={[
|
| 68 |
"motion-safe:animate-fade-in grid h-dvh divide-gray-200 overflow-hidden bg-gray-100/50",
|
|
@@ -253,26 +230,6 @@
|
|
| 253 |
<IconWaterfall class="text-xs" />
|
| 254 |
Metrics
|
| 255 |
</a>
|
| 256 |
-
<button
|
| 257 |
-
onclick={token.reset}
|
| 258 |
-
class="flex items-center gap-1 text-sm text-gray-500 underline decoration-gray-300 hover:text-gray-800 dark:text-gray-400 dark:decoration-gray-600 dark:hover:text-gray-200"
|
| 259 |
-
>
|
| 260 |
-
<svg xmlns="http://www.w3.org/2000/svg" class="text-xs" width="1em" height="1em" viewBox="0 0 32 32">
|
| 261 |
-
<path
|
| 262 |
-
fill="currentColor"
|
| 263 |
-
d="M23.216 4H26V2h-7v6h2V5.096A11.96 11.96 0 0 1 28 16c0 6.617-5.383 12-12 12v2c7.72 0 14-6.28 14-14c0-5.009-2.632-9.512-6.784-12"
|
| 264 |
-
/>
|
| 265 |
-
<path fill="currentColor" d="M16 20a1.5 1.5 0 1 0 0 3a1.5 1.5 0 0 0 0-3M15 9h2v9h-2z" /><path
|
| 266 |
-
fill="currentColor"
|
| 267 |
-
d="M16 4V2C8.28 2 2 8.28 2 16c0 4.977 2.607 9.494 6.784 12H6v2h7v-6h-2v2.903A11.97 11.97 0 0 1 4 16C4 9.383 9.383 4 16 4"
|
| 268 |
-
/>
|
| 269 |
-
</svg>
|
| 270 |
-
{#if token.value}
|
| 271 |
-
Reset token
|
| 272 |
-
{:else}
|
| 273 |
-
Set token
|
| 274 |
-
{/if}
|
| 275 |
-
</button>
|
| 276 |
</div>
|
| 277 |
</div>
|
| 278 |
|
|
|
|
| 25 |
import PlaygroundConversationHeader from "./conversation-header.svelte";
|
| 26 |
import PlaygroundConversation from "./conversation.svelte";
|
| 27 |
import GenerationConfig from "./generation-config.svelte";
|
|
|
|
| 28 |
import MessageTextarea from "./message-textarea.svelte";
|
| 29 |
import ModelSelectorModal from "./model-selector-modal.svelte";
|
| 30 |
import ModelSelector from "./model-selector.svelte";
|
|
|
|
| 38 |
|
| 39 |
const systemPromptSupported = $derived(conversations.active.some(c => isSystemPromptSupported(c.model)));
|
| 40 |
const compareActive = $derived(conversations.active.length === 2);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
</script>
|
| 42 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
<div
|
| 44 |
class={[
|
| 45 |
"motion-safe:animate-fade-in grid h-dvh divide-gray-200 overflow-hidden bg-gray-100/50",
|
|
|
|
| 230 |
<IconWaterfall class="text-xs" />
|
| 231 |
Metrics
|
| 232 |
</a>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 233 |
</div>
|
| 234 |
</div>
|
| 235 |
|
src/lib/state/conversations.svelte.ts
CHANGED
|
@@ -202,7 +202,7 @@ export class ConversationClass {
|
|
| 202 |
|
| 203 |
genNextMessage = async () => {
|
| 204 |
if (!token.value) {
|
| 205 |
-
token.
|
| 206 |
return;
|
| 207 |
}
|
| 208 |
|
|
@@ -437,7 +437,7 @@ class Conversations {
|
|
| 437 |
|
| 438 |
genNextMessages = async (conv: "left" | "right" | "both" | ConversationClass = "both") => {
|
| 439 |
if (!token.value) {
|
| 440 |
-
token.
|
| 441 |
return;
|
| 442 |
}
|
| 443 |
|
|
|
|
| 202 |
|
| 203 |
genNextMessage = async () => {
|
| 204 |
if (!token.value) {
|
| 205 |
+
token.requestTokenFromParent();
|
| 206 |
return;
|
| 207 |
}
|
| 208 |
|
|
|
|
| 437 |
|
| 438 |
genNextMessages = async (conv: "left" | "right" | "both" | ConversationClass = "both") => {
|
| 439 |
if (!token.value) {
|
| 440 |
+
token.requestTokenFromParent();
|
| 441 |
return;
|
| 442 |
}
|
| 443 |
|
src/lib/state/token.svelte.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
| 1 |
import { safeParse } from "$lib/utils/json.js";
|
|
|
|
| 2 |
import typia from "typia";
|
| 3 |
|
| 4 |
const key = "hf_token";
|
|
@@ -6,12 +7,22 @@ const key = "hf_token";
|
|
| 6 |
class Token {
|
| 7 |
#value = $state("");
|
| 8 |
writeToLocalStorage = $state(true);
|
| 9 |
-
showModal = $state(false);
|
| 10 |
|
| 11 |
constructor() {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
const storedHfToken = localStorage.getItem(key);
|
| 13 |
const parsed = safeParse(storedHfToken ?? "");
|
| 14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
}
|
| 16 |
|
| 17 |
get value() {
|
|
@@ -23,12 +34,34 @@ class Token {
|
|
| 23 |
localStorage.setItem(key, JSON.stringify(token));
|
| 24 |
}
|
| 25 |
this.#value = token;
|
| 26 |
-
this.showModal = !token.length;
|
| 27 |
}
|
| 28 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
reset = () => {
|
| 30 |
this.value = "";
|
| 31 |
localStorage.removeItem(key);
|
|
|
|
|
|
|
|
|
|
| 32 |
};
|
| 33 |
}
|
| 34 |
|
|
|
|
| 1 |
import { safeParse } from "$lib/utils/json.js";
|
| 2 |
+
import { PUBLIC_HF_TOKEN } from "$env/static/public";
|
| 3 |
import typia from "typia";
|
| 4 |
|
| 5 |
const key = "hf_token";
|
|
|
|
| 7 |
class Token {
|
| 8 |
#value = $state("");
|
| 9 |
writeToLocalStorage = $state(true);
|
|
|
|
| 10 |
|
| 11 |
constructor() {
|
| 12 |
+
if (PUBLIC_HF_TOKEN) {
|
| 13 |
+
this.#value = PUBLIC_HF_TOKEN;
|
| 14 |
+
return;
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
const storedHfToken = localStorage.getItem(key);
|
| 18 |
const parsed = safeParse(storedHfToken ?? "");
|
| 19 |
+
const storedToken = typia.is<string>(parsed) ? parsed : "";
|
| 20 |
+
|
| 21 |
+
if (storedToken) {
|
| 22 |
+
this.#value = storedToken;
|
| 23 |
+
} else {
|
| 24 |
+
this.requestTokenFromParent();
|
| 25 |
+
}
|
| 26 |
}
|
| 27 |
|
| 28 |
get value() {
|
|
|
|
| 34 |
localStorage.setItem(key, JSON.stringify(token));
|
| 35 |
}
|
| 36 |
this.#value = token;
|
|
|
|
| 37 |
}
|
| 38 |
|
| 39 |
+
requestTokenFromParent = (): Promise<void> => {
|
| 40 |
+
if (typeof window === "undefined") return Promise.resolve();
|
| 41 |
+
|
| 42 |
+
return new Promise(resolve => {
|
| 43 |
+
const handleMessage = (event: MessageEvent) => {
|
| 44 |
+
if (event.data.type === "INFERENCE_JWT_RESPONSE") {
|
| 45 |
+
const token = event.data.token;
|
| 46 |
+
if (token && typeof token === "string") {
|
| 47 |
+
this.value = token;
|
| 48 |
+
window.removeEventListener("message", handleMessage);
|
| 49 |
+
resolve();
|
| 50 |
+
}
|
| 51 |
+
}
|
| 52 |
+
};
|
| 53 |
+
|
| 54 |
+
window.addEventListener("message", handleMessage);
|
| 55 |
+
window.parent?.postMessage({ type: "INFERENCE_JWT_REQUEST" }, "*");
|
| 56 |
+
});
|
| 57 |
+
};
|
| 58 |
+
|
| 59 |
reset = () => {
|
| 60 |
this.value = "";
|
| 61 |
localStorage.removeItem(key);
|
| 62 |
+
if (!PUBLIC_HF_TOKEN) {
|
| 63 |
+
this.requestTokenFromParent();
|
| 64 |
+
}
|
| 65 |
};
|
| 66 |
}
|
| 67 |
|
src/lib/utils/business.svelte.ts
CHANGED
|
@@ -122,6 +122,7 @@ export async function handleStreamingResponse(
|
|
| 122 |
conversation: ConversationClass | Conversation,
|
| 123 |
onChunk: (content: string) => void,
|
| 124 |
abortController: AbortController,
|
|
|
|
| 125 |
): Promise<void> {
|
| 126 |
const data = conversation instanceof ConversationClass ? conversation.data : conversation;
|
| 127 |
const model = conversation.model;
|
|
@@ -149,28 +150,37 @@ export async function handleStreamingResponse(
|
|
| 149 |
enabledMCPs: getEnabledMCPs(),
|
| 150 |
};
|
| 151 |
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
|
|
|
| 160 |
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
}
|
|
|
|
| 169 |
}
|
| 170 |
}
|
| 171 |
|
| 172 |
export async function handleNonStreamingResponse(
|
| 173 |
conversation: ConversationClass | Conversation,
|
|
|
|
| 174 |
): Promise<{ message: ChatCompletionOutputMessage; completion_tokens: number }> {
|
| 175 |
const data = conversation instanceof ConversationClass ? conversation.data : conversation;
|
| 176 |
const model = conversation.model;
|
|
@@ -207,6 +217,10 @@ export async function handleNonStreamingResponse(
|
|
| 207 |
});
|
| 208 |
|
| 209 |
if (!response.ok) {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 210 |
const error = await response.json();
|
| 211 |
throw new Error(error.error || "Failed to generate response");
|
| 212 |
}
|
|
|
|
| 122 |
conversation: ConversationClass | Conversation,
|
| 123 |
onChunk: (content: string) => void,
|
| 124 |
abortController: AbortController,
|
| 125 |
+
retryCount = 0,
|
| 126 |
): Promise<void> {
|
| 127 |
const data = conversation instanceof ConversationClass ? conversation.data : conversation;
|
| 128 |
const model = conversation.model;
|
|
|
|
| 150 |
enabledMCPs: getEnabledMCPs(),
|
| 151 |
};
|
| 152 |
|
| 153 |
+
try {
|
| 154 |
+
const reader = await StreamReader.fromFetch("/api/generate", {
|
| 155 |
+
method: "POST",
|
| 156 |
+
headers: {
|
| 157 |
+
"Content-Type": "application/json",
|
| 158 |
+
},
|
| 159 |
+
body: JSON.stringify(requestBody),
|
| 160 |
+
signal: abortController.signal,
|
| 161 |
+
});
|
| 162 |
|
| 163 |
+
let out = "";
|
| 164 |
+
for await (const chunk of reader.read()) {
|
| 165 |
+
if (chunk.type === "chunk" && chunk.content) {
|
| 166 |
+
out += chunk.content;
|
| 167 |
+
onChunk(out);
|
| 168 |
+
} else if (chunk.type === "error") {
|
| 169 |
+
throw new Error(chunk.error || "Stream error");
|
| 170 |
+
}
|
| 171 |
+
}
|
| 172 |
+
} catch (error) {
|
| 173 |
+
if (error instanceof Error && error.message.includes("401") && retryCount === 0) {
|
| 174 |
+
await token.requestTokenFromParent();
|
| 175 |
+
return handleStreamingResponse(conversation, onChunk, abortController, retryCount + 1);
|
| 176 |
}
|
| 177 |
+
throw error;
|
| 178 |
}
|
| 179 |
}
|
| 180 |
|
| 181 |
export async function handleNonStreamingResponse(
|
| 182 |
conversation: ConversationClass | Conversation,
|
| 183 |
+
retryCount = 0,
|
| 184 |
): Promise<{ message: ChatCompletionOutputMessage; completion_tokens: number }> {
|
| 185 |
const data = conversation instanceof ConversationClass ? conversation.data : conversation;
|
| 186 |
const model = conversation.model;
|
|
|
|
| 217 |
});
|
| 218 |
|
| 219 |
if (!response.ok) {
|
| 220 |
+
if (response.status === 401 && retryCount === 0) {
|
| 221 |
+
await token.requestTokenFromParent();
|
| 222 |
+
return handleNonStreamingResponse(conversation, retryCount + 1);
|
| 223 |
+
}
|
| 224 |
const error = await response.json();
|
| 225 |
throw new Error(error.error || "Failed to generate response");
|
| 226 |
}
|
src/lib/utils/stream.ts
CHANGED
|
@@ -49,6 +49,9 @@ export class StreamReader {
|
|
| 49 |
static async fromFetch(url: string, options?: RequestInit): Promise<StreamReader> {
|
| 50 |
const response = await fetch(url, options);
|
| 51 |
if (!response.ok) {
|
|
|
|
|
|
|
|
|
|
| 52 |
const error = await response.json();
|
| 53 |
throw new Error(error.error || "Request failed");
|
| 54 |
}
|
|
|
|
| 49 |
static async fromFetch(url: string, options?: RequestInit): Promise<StreamReader> {
|
| 50 |
const response = await fetch(url, options);
|
| 51 |
if (!response.ok) {
|
| 52 |
+
if (response.status === 401) {
|
| 53 |
+
throw new Error("401 Unauthorized");
|
| 54 |
+
}
|
| 55 |
const error = await response.json();
|
| 56 |
throw new Error(error.error || "Request failed");
|
| 57 |
}
|
src/routes/api/generate/+server.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { createAdapter, type GenerationArgs } from "./adapter.js";
|
|
| 7 |
import { connectToMCPServers, executeMcpTool, type MCPServerConnection } from "./mcp.js";
|
| 8 |
import type { FinishReason, GenerateRequest } from "./types.js";
|
| 9 |
import { debugLog } from "./utils.js";
|
|
|
|
| 10 |
|
| 11 |
type AssistantResponse = { message: ChatCompletionMessage; finish_reason: FinishReason };
|
| 12 |
|
|
@@ -197,6 +198,16 @@ export const POST: RequestHandler = async ({ request }) => {
|
|
| 197 |
} catch (error) {
|
| 198 |
debugLog(JSON.stringify(error, null, 2));
|
| 199 |
console.error("Generation error:", error);
|
| 200 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 201 |
}
|
| 202 |
};
|
|
|
|
| 7 |
import { connectToMCPServers, executeMcpTool, type MCPServerConnection } from "./mcp.js";
|
| 8 |
import type { FinishReason, GenerateRequest } from "./types.js";
|
| 9 |
import { debugLog } from "./utils.js";
|
| 10 |
+
import { InferenceClientProviderApiError, InferenceClientHubApiError } from "@huggingface/inference";
|
| 11 |
|
| 12 |
type AssistantResponse = { message: ChatCompletionMessage; finish_reason: FinishReason };
|
| 13 |
|
|
|
|
| 198 |
} catch (error) {
|
| 199 |
debugLog(JSON.stringify(error, null, 2));
|
| 200 |
console.error("Generation error:", error);
|
| 201 |
+
|
| 202 |
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
| 203 |
+
let status = 500;
|
| 204 |
+
|
| 205 |
+
if (error instanceof InferenceClientProviderApiError || error instanceof InferenceClientHubApiError) {
|
| 206 |
+
status = error.httpResponse.status;
|
| 207 |
+
} else if (error && typeof error === "object" && "status" in error && typeof error.status === "number") {
|
| 208 |
+
status = error.status;
|
| 209 |
+
}
|
| 210 |
+
|
| 211 |
+
return json({ error: errorMessage }, { status });
|
| 212 |
}
|
| 213 |
};
|