Skip to content

Commit 8cfdfbf

Browse files
committed
fetch token from parent frame
1 parent 8816e9f commit 8cfdfbf

File tree

9 files changed

+83
-191
lines changed

9 files changed

+83
-191
lines changed

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
PUBLIC_ENABLE_MCP=
2+
PUBLIC_HF_TOKEN=
23
HYPERBOLIC_API_KEY=
34
COHERE_API_KEY=
45
TOGETHER_API_KEY=

src/lib/components/debug-menu.svelte

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,6 @@
4646
showQuotaModal();
4747
},
4848
},
49-
{
50-
label: "Show token modal",
51-
cb: () => {
52-
token.showModal = true;
53-
},
54-
},
5549
{
5650
label: "Test toast",
5751
cb: () => {

src/lib/components/inference-playground/hf-token-modal.svelte

Lines changed: 0 additions & 121 deletions
This file was deleted.

src/lib/components/inference-playground/playground.svelte

Lines changed: 0 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import PlaygroundConversationHeader from "./conversation-header.svelte";
2626
import PlaygroundConversation from "./conversation.svelte";
2727
import GenerationConfig from "./generation-config.svelte";
28-
import HFTokenModal from "./hf-token-modal.svelte";
2928
import MessageTextarea from "./message-textarea.svelte";
3029
import ModelSelectorModal from "./model-selector-modal.svelte";
3130
import ModelSelector from "./model-selector.svelte";
@@ -39,30 +38,8 @@
3938
4039
const systemPromptSupported = $derived(conversations.active.some(c => isSystemPromptSupported(c.model)));
4140
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-
}
5641
</script>
5742

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-
6643
<div
6744
class={[
6845
"motion-safe:animate-fade-in grid h-dvh divide-gray-200 overflow-hidden bg-gray-100/50",
@@ -253,26 +230,6 @@
253230
<IconWaterfall class="text-xs" />
254231
Metrics
255232
</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>
276233
</div>
277234
</div>
278235

src/lib/state/conversations.svelte.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ export class ConversationClass {
202202

203203
genNextMessage = async () => {
204204
if (!token.value) {
205-
token.showModal = true;
205+
token.requestTokenFromParent();
206206
return;
207207
}
208208

@@ -437,7 +437,7 @@ class Conversations {
437437

438438
genNextMessages = async (conv: "left" | "right" | "both" | ConversationClass = "both") => {
439439
if (!token.value) {
440-
token.showModal = true;
440+
token.requestTokenFromParent();
441441
return;
442442
}
443443

src/lib/state/token.svelte.ts

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,28 @@
11
import { safeParse } from "$lib/utils/json.js";
2+
import { PUBLIC_HF_TOKEN } from "$env/static/public";
23
import typia from "typia";
34

45
const key = "hf_token";
56

67
class Token {
78
#value = $state("");
89
writeToLocalStorage = $state(true);
9-
showModal = $state(false);
1010

1111
constructor() {
12+
if (PUBLIC_HF_TOKEN) {
13+
this.#value = PUBLIC_HF_TOKEN;
14+
return;
15+
}
16+
1217
const storedHfToken = localStorage.getItem(key);
1318
const parsed = safeParse(storedHfToken ?? "");
14-
this.value = typia.is<string>(parsed) ? parsed : "";
19+
const storedToken = typia.is<string>(parsed) ? parsed : "";
20+
21+
if (storedToken) {
22+
this.#value = storedToken;
23+
} else {
24+
this.requestTokenFromParent();
25+
}
1526
}
1627

1728
get value() {
@@ -23,12 +34,34 @@ class Token {
2334
localStorage.setItem(key, JSON.stringify(token));
2435
}
2536
this.#value = token;
26-
this.showModal = !token.length;
2737
}
2838

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+
2959
reset = () => {
3060
this.value = "";
3161
localStorage.removeItem(key);
62+
if (!PUBLIC_HF_TOKEN) {
63+
this.requestTokenFromParent();
64+
}
3265
};
3366
}
3467

src/lib/utils/business.svelte.ts

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ export async function handleStreamingResponse(
122122
conversation: ConversationClass | Conversation,
123123
onChunk: (content: string) => void,
124124
abortController: AbortController,
125+
retryCount = 0,
125126
): Promise<void> {
126127
const data = conversation instanceof ConversationClass ? conversation.data : conversation;
127128
const model = conversation.model;
@@ -149,28 +150,37 @@ export async function handleStreamingResponse(
149150
enabledMCPs: getEnabledMCPs(),
150151
};
151152

152-
const reader = await StreamReader.fromFetch("/api/generate", {
153-
method: "POST",
154-
headers: {
155-
"Content-Type": "application/json",
156-
},
157-
body: JSON.stringify(requestBody),
158-
signal: abortController.signal,
159-
});
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+
});
160162

161-
let out = "";
162-
for await (const chunk of reader.read()) {
163-
if (chunk.type === "chunk" && chunk.content) {
164-
out += chunk.content;
165-
onChunk(out);
166-
} else if (chunk.type === "error") {
167-
throw new Error(chunk.error || "Stream error");
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);
168176
}
177+
throw error;
169178
}
170179
}
171180

172181
export async function handleNonStreamingResponse(
173182
conversation: ConversationClass | Conversation,
183+
retryCount = 0,
174184
): Promise<{ message: ChatCompletionOutputMessage; completion_tokens: number }> {
175185
const data = conversation instanceof ConversationClass ? conversation.data : conversation;
176186
const model = conversation.model;
@@ -207,6 +217,10 @@ export async function handleNonStreamingResponse(
207217
});
208218

209219
if (!response.ok) {
220+
if (response.status === 401 && retryCount === 0) {
221+
await token.requestTokenFromParent();
222+
return handleNonStreamingResponse(conversation, retryCount + 1);
223+
}
210224
const error = await response.json();
211225
throw new Error(error.error || "Failed to generate response");
212226
}

src/lib/utils/stream.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ export class StreamReader {
4949
static async fromFetch(url: string, options?: RequestInit): Promise<StreamReader> {
5050
const response = await fetch(url, options);
5151
if (!response.ok) {
52+
if (response.status === 401) {
53+
throw new Error("401 Unauthorized");
54+
}
5255
const error = await response.json();
5356
throw new Error(error.error || "Request failed");
5457
}

src/routes/api/generate/+server.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { createAdapter, type GenerationArgs } from "./adapter.js";
77
import { connectToMCPServers, executeMcpTool, type MCPServerConnection } from "./mcp.js";
88
import type { FinishReason, GenerateRequest } from "./types.js";
99
import { debugLog } from "./utils.js";
10+
import { InferenceClientProviderApiError, InferenceClientHubApiError } from "@huggingface/inference";
1011

1112
type AssistantResponse = { message: ChatCompletionMessage; finish_reason: FinishReason };
1213

@@ -197,6 +198,16 @@ export const POST: RequestHandler = async ({ request }) => {
197198
} catch (error) {
198199
debugLog(JSON.stringify(error, null, 2));
199200
console.error("Generation error:", error);
200-
return json({ error: error instanceof Error ? error.message : "Unknown error occurred" }, { status: 500 });
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 });
201212
}
202213
};

0 commit comments

Comments
 (0)