refactor ai stuff
This commit is contained in:
parent
bc8f56fc54
commit
f2bd344c5b
5 changed files with 300 additions and 243 deletions
|
@ -1,62 +1,10 @@
|
||||||
import React, { useRef, useState } from "react";
|
import React from "react";
|
||||||
import { MLCEngine } from "@mlc-ai/web-llm";
|
import { useUuidGenerator } from "./useUuidGenerator";
|
||||||
// Try to import the type if available
|
import { UuidOutput } from "./UuidOutput";
|
||||||
// import type { ChatCompletionMessageParam } from "@mlc-ai/web-llm";
|
import { UuidControls } from "./UuidControls";
|
||||||
|
import { UuidFooter } from "./UuidFooter";
|
||||||
// Qwen2.5-0.5B-Instruct-q4f16_1-MLC is the required model
|
|
||||||
const MODEL_ID = "Qwen2.5-0.5B-Instruct-q4f16_1-MLC";
|
|
||||||
|
|
||||||
const UUID_PROMPT = `Your task is to generate one random UUID version 4 (UUIDv4) and output it. Follow these rules exactly:
|
|
||||||
|
|
||||||
1. Output only the UUIDv4 — no explanation, no steps, no formatting, no words.
|
|
||||||
2. A UUIDv4 is a 36-character string with 5 sections separated by hyphens.
|
|
||||||
3. The format is: 8-4-4-4-12 hexadecimal characters (0-9, a-f). Example shape:
|
|
||||||
xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
|
|
||||||
- The 13th character must be "4" (UUIDv4).
|
|
||||||
- The 17th character must be one of "8", "9", "a", or "b".
|
|
||||||
|
|
||||||
Return only a valid random UUIDv4 string. Do not say anything else.`;
|
|
||||||
|
|
||||||
// Helper: filter and format UUID as it streams
|
// Helper: filter and format UUID as it streams
|
||||||
function filterAndFormatUUID(raw: string) {
|
|
||||||
// Only allow 0-9, a-f, and hyphens, lowercase
|
|
||||||
let filtered = raw.toLowerCase().replace(/[^0-9a-f-]/g, "");
|
|
||||||
// Remove extra hyphens
|
|
||||||
filtered = filtered.replace(/-{2,}/g, "-");
|
|
||||||
// Remove leading/trailing hyphens
|
|
||||||
filtered = filtered.replace(/^[-]+|[-]+$/g, "");
|
|
||||||
// Remove all hyphens to reformat
|
|
||||||
const hex = filtered.replace(/-/g, "");
|
|
||||||
// Format as 8-4-4-4-12
|
|
||||||
let uuid = "";
|
|
||||||
const sections = [8, 4, 4, 4, 12];
|
|
||||||
let i = 0;
|
|
||||||
for (const len of sections) {
|
|
||||||
if (hex.length < i + len) break;
|
|
||||||
uuid += hex.slice(i, i + len);
|
|
||||||
i += len;
|
|
||||||
if (uuid.length < 36) uuid += "-";
|
|
||||||
}
|
|
||||||
// Remove trailing hyphen if incomplete
|
|
||||||
uuid = uuid.replace(/-$/, "");
|
|
||||||
// Is it a complete UUIDv4?
|
|
||||||
const isComplete =
|
|
||||||
uuid.length === 36 &&
|
|
||||||
["8", "9", "a", "b"].includes(uuid[19]);
|
|
||||||
return { uuid, isComplete };
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatUUIDPartial(uuid: string) {
|
|
||||||
// Add subtle color for missing chars
|
|
||||||
const missing = 36 - uuid.length;
|
|
||||||
if (missing <= 0) return uuid;
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{uuid}
|
|
||||||
<span style={{ opacity: 0.3 }}>{"_".repeat(missing)}</span>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const MODERN_CARD_STYLE: React.CSSProperties = {
|
const MODERN_CARD_STYLE: React.CSSProperties = {
|
||||||
maxWidth: 420,
|
maxWidth: 420,
|
||||||
|
@ -73,36 +21,6 @@ const MODERN_CARD_STYLE: React.CSSProperties = {
|
||||||
backdropFilter: "blur(8px)",
|
backdropFilter: "blur(8px)",
|
||||||
border: "1.5px solid rgba(100,108,255,0.10)",
|
border: "1.5px solid rgba(100,108,255,0.10)",
|
||||||
};
|
};
|
||||||
const MODERN_BUTTON_STYLE: React.CSSProperties = {
|
|
||||||
marginTop: 32,
|
|
||||||
padding: "1em 2.5em",
|
|
||||||
fontSize: 20,
|
|
||||||
borderRadius: 12,
|
|
||||||
border: "none",
|
|
||||||
background: "linear-gradient(90deg, #646cff 0%, #7f53ff 100%)",
|
|
||||||
color: "#fff",
|
|
||||||
fontWeight: 700,
|
|
||||||
cursor: "pointer",
|
|
||||||
boxShadow: "0 2px 12px #646cff33",
|
|
||||||
transition: "background 0.2s, box-shadow 0.2s",
|
|
||||||
minWidth: 200,
|
|
||||||
letterSpacing: 1,
|
|
||||||
};
|
|
||||||
const MODERN_OUTPUT_STYLE: React.CSSProperties = {
|
|
||||||
fontFamily: "JetBrains Mono, Fira Mono, Menlo, monospace",
|
|
||||||
fontSize: 28,
|
|
||||||
letterSpacing: 2,
|
|
||||||
margin: "2.5rem 0 0.5rem 0",
|
|
||||||
minHeight: 38,
|
|
||||||
wordBreak: "break-all",
|
|
||||||
textAlign: "center",
|
|
||||||
color: "#fff",
|
|
||||||
background: "rgba(40,40,60,0.18)",
|
|
||||||
borderRadius: 10,
|
|
||||||
padding: "0.7em 0.5em",
|
|
||||||
boxShadow: "0 1px 4px #0002",
|
|
||||||
border: "1px solid #23234a33",
|
|
||||||
};
|
|
||||||
const MODERN_PROGRESS_STYLE: React.CSSProperties = {
|
const MODERN_PROGRESS_STYLE: React.CSSProperties = {
|
||||||
width: "100%",
|
width: "100%",
|
||||||
margin: "1.5rem 0 0.5rem 0",
|
margin: "1.5rem 0 0.5rem 0",
|
||||||
|
@ -119,120 +37,18 @@ const MODERN_ERROR_STYLE: React.CSSProperties = {
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
letterSpacing: 0.2,
|
letterSpacing: 0.2,
|
||||||
};
|
};
|
||||||
const MODERN_FOOTER_STYLE: React.CSSProperties = {
|
|
||||||
marginTop: 40,
|
|
||||||
color: "#b3b3d1",
|
|
||||||
fontSize: 15,
|
|
||||||
textAlign: "center",
|
|
||||||
opacity: 0.85,
|
|
||||||
lineHeight: 1.7,
|
|
||||||
};
|
|
||||||
|
|
||||||
const EMOJI_HEADER = "🤖✨ UUIDv4 AI Generator ✨🔑";
|
const EMOJI_HEADER = "🤖✨ UUIDv4 AI Generator ✨🔑";
|
||||||
const EMOJI_FOOTER = "🧠 All AI runs 100% in your browser.";
|
|
||||||
|
|
||||||
const App: React.FC = () => {
|
const App: React.FC = () => {
|
||||||
const engineRef = useRef<MLCEngine | null>(null);
|
const {
|
||||||
const abortRef = useRef<AbortController | null>(null);
|
output,
|
||||||
const [output, setOutput] = useState("");
|
loading,
|
||||||
const [loading, setLoading] = useState(false);
|
progress,
|
||||||
const [progress, setProgress] = useState<string | null>(null);
|
error,
|
||||||
const [error, setError] = useState<string | null>(null);
|
handleGenerate,
|
||||||
|
handleAbort,
|
||||||
// Model loader with progress
|
} = useUuidGenerator();
|
||||||
const loadModel = async () => {
|
|
||||||
setProgress("Loading model...");
|
|
||||||
setError(null);
|
|
||||||
|
|
||||||
if (!engineRef.current) {
|
|
||||||
engineRef.current = null;
|
|
||||||
}
|
|
||||||
engineRef.current = new MLCEngine({
|
|
||||||
initProgressCallback: (p: any) => {
|
|
||||||
if (p && typeof p === "object" && "progress" in p) {
|
|
||||||
setProgress(
|
|
||||||
`Downloading model: ${Math.round((p.progress || 0) * 100)}%`
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
setProgress("Initializing model...");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
await engineRef.current.reload(MODEL_ID);
|
|
||||||
setProgress(null);
|
|
||||||
} catch (e: any) {
|
|
||||||
setError("Model load failed: " + (e?.message || e.toString()));
|
|
||||||
setProgress(null);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Main generation handler
|
|
||||||
const handleGenerate = async () => {
|
|
||||||
setError(null);
|
|
||||||
setOutput("");
|
|
||||||
setLoading(true);
|
|
||||||
setProgress(null);
|
|
||||||
// Abort any previous
|
|
||||||
if (abortRef.current) abortRef.current.abort();
|
|
||||||
abortRef.current = new AbortController();
|
|
||||||
try {
|
|
||||||
await loadModel();
|
|
||||||
const engine = engineRef.current!;
|
|
||||||
const messages = [
|
|
||||||
{
|
|
||||||
role: "user",
|
|
||||||
content: UUID_PROMPT,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
let buffer = "";
|
|
||||||
let done = false;
|
|
||||||
// Streaming
|
|
||||||
const chunks = await engine.chat.completions.create({
|
|
||||||
messages: messages as any, // Type assertion to bypass linter error, structure matches docs
|
|
||||||
temperature: 1,
|
|
||||||
stream: true,
|
|
||||||
stream_options: { include_usage: false },
|
|
||||||
});
|
|
||||||
for await (const chunk of chunks) {
|
|
||||||
if (done) break;
|
|
||||||
const delta = chunk.choices[0]?.delta.content || "";
|
|
||||||
buffer += delta;
|
|
||||||
const { uuid, isComplete } = filterAndFormatUUID(buffer);
|
|
||||||
setOutput(uuid);
|
|
||||||
if (isComplete) {
|
|
||||||
abortRef.current.abort();
|
|
||||||
done = true;
|
|
||||||
setLoading(false);
|
|
||||||
setProgress(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: any) {
|
|
||||||
if (abortRef.current) abortRef.current.abort();
|
|
||||||
setLoading(false);
|
|
||||||
setProgress(null);
|
|
||||||
if (e?.name === "AbortError") {
|
|
||||||
// Ignore abort
|
|
||||||
} else {
|
|
||||||
setError("Generation failed: " + (e?.message || e.toString()));
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (abortRef.current) abortRef.current.abort();
|
|
||||||
setLoading(false);
|
|
||||||
setProgress(null);
|
|
||||||
}
|
|
||||||
if (abortRef.current) abortRef.current.abort();
|
|
||||||
setLoading(false);
|
|
||||||
setProgress(null);
|
|
||||||
};
|
|
||||||
|
|
||||||
// On unmount, abort any running
|
|
||||||
React.useEffect(() => {
|
|
||||||
return () => {
|
|
||||||
if (abortRef.current) abortRef.current.abort();
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
@ -269,54 +85,15 @@ const App: React.FC = () => {
|
||||||
>
|
>
|
||||||
{EMOJI_HEADER}
|
{EMOJI_HEADER}
|
||||||
</h1>
|
</h1>
|
||||||
<div style={MODERN_OUTPUT_STYLE}>
|
<UuidOutput output={output} />
|
||||||
{output ? (
|
|
||||||
formatUUIDPartial(output)
|
|
||||||
) : (
|
|
||||||
<span style={{ opacity: 0.25 }}>
|
|
||||||
xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{progress && <div style={MODERN_PROGRESS_STYLE}>{progress}</div>}
|
{progress && <div style={MODERN_PROGRESS_STYLE}>{progress}</div>}
|
||||||
<button
|
<UuidControls
|
||||||
style={{
|
loading={loading}
|
||||||
...MODERN_BUTTON_STYLE,
|
onGenerate={handleGenerate}
|
||||||
opacity: loading ? 0.6 : 1,
|
onAbort={handleAbort}
|
||||||
pointerEvents: loading ? "none" : "auto",
|
/>
|
||||||
}}
|
|
||||||
onClick={handleGenerate}
|
|
||||||
disabled={loading}
|
|
||||||
>
|
|
||||||
{loading ? "Generating..." : "Generate UUIDv4"}
|
|
||||||
</button>
|
|
||||||
{error && <div style={MODERN_ERROR_STYLE}>{error}</div>}
|
{error && <div style={MODERN_ERROR_STYLE}>{error}</div>}
|
||||||
<div style={MODERN_FOOTER_STYLE}>
|
<UuidFooter />
|
||||||
<span>
|
|
||||||
Powered by
|
|
||||||
{" "}
|
|
||||||
<a
|
|
||||||
href="https://github.com/mlc-ai/web-llm"
|
|
||||||
style={{ color: "#646cff", textDecoration: "underline dotted" }}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
WebLLM
|
|
||||||
</a>
|
|
||||||
{" & Qwen2.5-0.5B-Instruct"}
|
|
||||||
<br />
|
|
||||||
{EMOJI_FOOTER}
|
|
||||||
<br />
|
|
||||||
<a
|
|
||||||
href="https://git.hannover.ccc.de/lubiana/uuid"
|
|
||||||
style={{ color: "#646cff", textDecoration: "underline dotted" }}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
Source code
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
52
uuid-browser-2-app/src/UuidControls.tsx
Normal file
52
uuid-browser-2-app/src/UuidControls.tsx
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const MODERN_BUTTON_STYLE: React.CSSProperties = {
|
||||||
|
marginTop: 32,
|
||||||
|
padding: "1em 2.5em",
|
||||||
|
fontSize: 20,
|
||||||
|
borderRadius: 12,
|
||||||
|
border: "none",
|
||||||
|
background: "linear-gradient(90deg, #646cff 0%, #7f53ff 100%)",
|
||||||
|
color: "#fff",
|
||||||
|
fontWeight: 700,
|
||||||
|
cursor: "pointer",
|
||||||
|
boxShadow: "0 2px 12px #646cff33",
|
||||||
|
transition: "background 0.2s, box-shadow 0.2s",
|
||||||
|
minWidth: 200,
|
||||||
|
letterSpacing: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function UuidControls({ loading, onGenerate, onAbort }: {
|
||||||
|
loading: boolean;
|
||||||
|
onGenerate: () => void;
|
||||||
|
onAbort: () => void;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
style={{
|
||||||
|
...MODERN_BUTTON_STYLE,
|
||||||
|
opacity: loading ? 0.6 : 1,
|
||||||
|
pointerEvents: loading ? "none" : "auto",
|
||||||
|
}}
|
||||||
|
onClick={onGenerate}
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
{loading ? "Generating..." : "Generate UUIDv4"}
|
||||||
|
</button>
|
||||||
|
{loading && (
|
||||||
|
<button
|
||||||
|
style={{
|
||||||
|
...MODERN_BUTTON_STYLE,
|
||||||
|
background: "#ff4d6d",
|
||||||
|
marginTop: 12,
|
||||||
|
minWidth: 120,
|
||||||
|
}}
|
||||||
|
onClick={onAbort}
|
||||||
|
>
|
||||||
|
Abort
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
43
uuid-browser-2-app/src/UuidFooter.tsx
Normal file
43
uuid-browser-2-app/src/UuidFooter.tsx
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const MODERN_FOOTER_STYLE: React.CSSProperties = {
|
||||||
|
marginTop: 40,
|
||||||
|
color: "#b3b3d1",
|
||||||
|
fontSize: 15,
|
||||||
|
textAlign: "center",
|
||||||
|
opacity: 0.85,
|
||||||
|
lineHeight: 1.7,
|
||||||
|
};
|
||||||
|
|
||||||
|
const EMOJI_FOOTER = "\uD83E\uDDE0 All AI runs 100% in your browser.";
|
||||||
|
|
||||||
|
export function UuidFooter() {
|
||||||
|
return (
|
||||||
|
<div style={MODERN_FOOTER_STYLE}>
|
||||||
|
<span>
|
||||||
|
Powered by
|
||||||
|
{" "}
|
||||||
|
<a
|
||||||
|
href="https://github.com/mlc-ai/web-llm"
|
||||||
|
style={{ color: "#646cff", textDecoration: "underline dotted" }}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
WebLLM
|
||||||
|
</a>
|
||||||
|
{" & Qwen2.5-0.5B-Instruct"}
|
||||||
|
<br />
|
||||||
|
{EMOJI_FOOTER}
|
||||||
|
<br />
|
||||||
|
<a
|
||||||
|
href="https://git.hannover.ccc.de/lubiana/uuid"
|
||||||
|
style={{ color: "#646cff", textDecoration: "underline dotted" }}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
Source code
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
36
uuid-browser-2-app/src/UuidOutput.tsx
Normal file
36
uuid-browser-2-app/src/UuidOutput.tsx
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const MODERN_OUTPUT_STYLE: React.CSSProperties = {
|
||||||
|
fontFamily: "JetBrains Mono, Fira Mono, Menlo, monospace",
|
||||||
|
fontSize: 28,
|
||||||
|
letterSpacing: 2,
|
||||||
|
margin: "2.5rem 0 0.5rem 0",
|
||||||
|
minHeight: 38,
|
||||||
|
wordBreak: "break-all",
|
||||||
|
textAlign: "center",
|
||||||
|
color: "#fff",
|
||||||
|
background: "rgba(40,40,60,0.18)",
|
||||||
|
borderRadius: 10,
|
||||||
|
padding: "0.7em 0.5em",
|
||||||
|
boxShadow: "0 1px 4px #0002",
|
||||||
|
border: "1px solid #23234a33",
|
||||||
|
};
|
||||||
|
|
||||||
|
export function UuidOutput({ output }: { output: string }) {
|
||||||
|
// Add subtle color for missing chars
|
||||||
|
const missing = 36 - output.length;
|
||||||
|
return (
|
||||||
|
<div style={MODERN_OUTPUT_STYLE}>
|
||||||
|
{output ? (
|
||||||
|
<>
|
||||||
|
{output}
|
||||||
|
{missing > 0 && <span style={{ opacity: 0.3 }}>{"_".repeat(missing)}</span>}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<span style={{ opacity: 0.25 }}>
|
||||||
|
xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
149
uuid-browser-2-app/src/useUuidGenerator.ts
Normal file
149
uuid-browser-2-app/src/useUuidGenerator.ts
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
import { useRef, useState, useEffect } from "react";
|
||||||
|
import { MLCEngine } from "@mlc-ai/web-llm";
|
||||||
|
|
||||||
|
const MODEL_ID = "Qwen2.5-0.5B-Instruct-q4f16_1-MLC";
|
||||||
|
|
||||||
|
const UUID_PROMPT = `Your task is to generate one random UUID version 4 (UUIDv4) and output it. Follow these rules exactly:
|
||||||
|
|
||||||
|
1. Output only the UUIDv4 — no explanation, no steps, no formatting, no words.
|
||||||
|
2. A UUIDv4 is a 36-character string with 5 sections separated by hyphens.
|
||||||
|
3. The format is: 8-4-4-4-12 hexadecimal characters (0-9, a-f). Example shape:
|
||||||
|
xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
|
||||||
|
- The 13th character must be "4" (UUIDv4).
|
||||||
|
- The 17th character must be one of "8", "9", "a", or "b".
|
||||||
|
|
||||||
|
Return only a valid random UUIDv4 string. Do not say anything else.`;
|
||||||
|
|
||||||
|
function filterAndFormatUUID(raw: string) {
|
||||||
|
let filtered = raw.toLowerCase().replace(/[^0-9a-f-]/g, "");
|
||||||
|
filtered = filtered.replace(/-{2,}/g, "-");
|
||||||
|
filtered = filtered.replace(/^[-]+|[-]+$/g, "");
|
||||||
|
const hex = filtered.replace(/-/g, "");
|
||||||
|
let uuid = "";
|
||||||
|
const sections = [8, 4, 4, 4, 12];
|
||||||
|
let i = 0;
|
||||||
|
for (const len of sections) {
|
||||||
|
if (hex.length < i + len) break;
|
||||||
|
uuid += hex.slice(i, i + len);
|
||||||
|
i += len;
|
||||||
|
if (uuid.length < 36) uuid += "-";
|
||||||
|
}
|
||||||
|
uuid = uuid.replace(/-$/, "");
|
||||||
|
const isComplete =
|
||||||
|
uuid.length === 36 &&
|
||||||
|
["8", "9", "a", "b"].includes(uuid[19]);
|
||||||
|
return { uuid, isComplete };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useUuidGenerator() {
|
||||||
|
const engineRef = useRef<MLCEngine | null>(null);
|
||||||
|
const abortRef = useRef<AbortController | null>(null);
|
||||||
|
const [output, setOutput] = useState("");
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [progress, setProgress] = useState<string | null>(null);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const loadModel = async () => {
|
||||||
|
setProgress("Loading model...");
|
||||||
|
setError(null);
|
||||||
|
if (!engineRef.current) {
|
||||||
|
engineRef.current = null;
|
||||||
|
}
|
||||||
|
engineRef.current = new MLCEngine({
|
||||||
|
initProgressCallback: (p: any) => {
|
||||||
|
if (p && typeof p === "object" && "progress" in p) {
|
||||||
|
setProgress(
|
||||||
|
`Downloading model: ${Math.round((p.progress || 0) * 100)}%`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
setProgress("Initializing model...");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
await engineRef.current.reload(MODEL_ID);
|
||||||
|
setProgress(null);
|
||||||
|
} catch (e: any) {
|
||||||
|
setError("Model load failed: " + (e?.message || e.toString()));
|
||||||
|
setProgress(null);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleGenerate = async () => {
|
||||||
|
setError(null);
|
||||||
|
setOutput("");
|
||||||
|
setLoading(true);
|
||||||
|
setProgress(null);
|
||||||
|
if (abortRef.current) abortRef.current.abort();
|
||||||
|
abortRef.current = new AbortController();
|
||||||
|
try {
|
||||||
|
await loadModel();
|
||||||
|
const engine = engineRef.current!;
|
||||||
|
const messages = [
|
||||||
|
{
|
||||||
|
role: "user",
|
||||||
|
content: UUID_PROMPT,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
let buffer = "";
|
||||||
|
let done = false;
|
||||||
|
const chunks = await engine.chat.completions.create({
|
||||||
|
messages: messages as any,
|
||||||
|
temperature: 1,
|
||||||
|
stream: true,
|
||||||
|
stream_options: { include_usage: false },
|
||||||
|
});
|
||||||
|
for await (const chunk of chunks) {
|
||||||
|
if (done) break;
|
||||||
|
const delta = chunk.choices[0]?.delta.content || "";
|
||||||
|
buffer += delta;
|
||||||
|
const { uuid, isComplete } = filterAndFormatUUID(buffer);
|
||||||
|
setOutput(uuid);
|
||||||
|
if (isComplete) {
|
||||||
|
abortRef.current.abort();
|
||||||
|
done = true;
|
||||||
|
setLoading(false);
|
||||||
|
setProgress(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
if (abortRef.current) abortRef.current.abort();
|
||||||
|
setLoading(false);
|
||||||
|
setProgress(null);
|
||||||
|
if (e?.name === "AbortError") {
|
||||||
|
// Ignore abort
|
||||||
|
} else {
|
||||||
|
setError("Generation failed: " + (e?.message || e.toString()));
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (abortRef.current) abortRef.current.abort();
|
||||||
|
setLoading(false);
|
||||||
|
setProgress(null);
|
||||||
|
}
|
||||||
|
if (abortRef.current) abortRef.current.abort();
|
||||||
|
setLoading(false);
|
||||||
|
setProgress(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAbort = () => {
|
||||||
|
if (abortRef.current) abortRef.current.abort();
|
||||||
|
setLoading(false);
|
||||||
|
setProgress(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (abortRef.current) abortRef.current.abort();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
output,
|
||||||
|
loading,
|
||||||
|
progress,
|
||||||
|
error,
|
||||||
|
handleGenerate,
|
||||||
|
handleAbort,
|
||||||
|
};
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue