Spaces:
Paused
Paused
Restore full project to commit 3f1dfe42
#8
by bep40 - opened
- README.md +13 -16
- index.html +2 -56
- index.ts +73 -2
- src/app.js +1 -1
README.md
CHANGED
|
@@ -1,23 +1,19 @@
|
|
| 1 |
---
|
| 2 |
-
title: Gemma Avatar
|
| 3 |
emoji: 🗣️
|
| 4 |
colorFrom: indigo
|
| 5 |
colorTo: purple
|
| 6 |
sdk: docker
|
| 7 |
app_port: 7860
|
| 8 |
pinned: false
|
| 9 |
-
thumbnail: https://huggingface.co/spaces/
|
| 10 |
-
short_description:
|
| 11 |
models:
|
| 12 |
- google/gemma-4-31B-it
|
| 13 |
- nvidia/parakeet-tdt-1.1b
|
| 14 |
- Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice
|
| 15 |
tags:
|
| 16 |
-
-
|
| 17 |
-
- gemma-4
|
| 18 |
-
- ai-avatar
|
| 19 |
-
- lip-sync
|
| 20 |
-
- voice-chat
|
| 21 |
---
|
| 22 |
|
| 23 |
# Gemma Avatar
|
|
@@ -72,9 +68,9 @@ bun run dev # direct mode: paste a ws:/
|
|
| 72 |
|
| 73 |
Open http://localhost:3000 and tap **Start talking**.
|
| 74 |
|
| 75 |
-
`?fakemic=1` starts a session with a silent synthetic mic (useful for testing
|
| 76 |
the full loop without a microphone — trigger a reply from the console with
|
| 77 |
-
`getClient().requestResponse()`).
|
| 78 |
|
| 79 |
## Layout
|
| 80 |
|
|
@@ -83,20 +79,21 @@ index.ts Bun server: HTML import + /api/session proxy + stati
|
|
| 83 |
index.html App shell (avatar hero, caption, subtitles, settings)
|
| 84 |
src/app.js Session wiring, tool executor, UI state
|
| 85 |
src/avatar.js AvatarStage: TalkingHead + HeadAudio + choreography
|
| 86 |
-
src/s2s/s2s-ws-client.js Realtime WS client
|
|
|
|
| 87 |
src/s2s/codec.js PCM/base64 + transcript helpers (vendored, unchanged)
|
| 88 |
src/vendor/headaudio.min.mjs HeadAudio node class (bundled)
|
| 89 |
-
public/worklets/ mic-capture + audio-playback AudioWorklets
|
| 90 |
-
public/vendor/ HeadAudio worklet processor + viseme model
|
| 91 |
-
public/avatars/brunette.glb Default avatar (Ready Player Me; CC BY-NC 4.0)
|
| 92 |
```
|
| 93 |
|
| 94 |
## Notes
|
| 95 |
|
| 96 |
- The avatar GLB must have a Mixamo-compatible rig plus ARKit and Oculus-viseme
|
| 97 |
blend shapes. Ready Player Me avatars work with
|
| 98 |
-
`?morphTargets=ARKit,Oculus%20Visemes` on the GLB URL.
|
| 99 |
-
- TalkingHead owns the AudioContext; the s2s client is handed `head.audioCtx`
|
| 100 |
and never closes it.
|
| 101 |
- Everything animation-related runs on requestAnimationFrame — a backgrounded
|
| 102 |
tab freezes the avatar (audio keeps playing).
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Gemma Avatar
|
| 3 |
emoji: 🗣️
|
| 4 |
colorFrom: indigo
|
| 5 |
colorTo: purple
|
| 6 |
sdk: docker
|
| 7 |
app_port: 7860
|
| 8 |
pinned: false
|
| 9 |
+
thumbnail: https://huggingface.co/spaces/victor/gemma-avatar/resolve/main/thumbnail.webp
|
| 10 |
+
short_description: Talk to Gemma 4 face to face, with a 3D lip-synced avatar
|
| 11 |
models:
|
| 12 |
- google/gemma-4-31B-it
|
| 13 |
- nvidia/parakeet-tdt-1.1b
|
| 14 |
- Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice
|
| 15 |
tags:
|
| 16 |
+
- ml-intern
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
---
|
| 18 |
|
| 19 |
# Gemma Avatar
|
|
|
|
| 68 |
|
| 69 |
Open http://localhost:3000 and tap **Start talking**.
|
| 70 |
|
| 71 |
+
\`?fakemic=1\` starts a session with a silent synthetic mic (useful for testing
|
| 72 |
the full loop without a microphone — trigger a reply from the console with
|
| 73 |
+
\`getClient().requestResponse()\`).
|
| 74 |
|
| 75 |
## Layout
|
| 76 |
|
|
|
|
| 79 |
index.html App shell (avatar hero, caption, subtitles, settings)
|
| 80 |
src/app.js Session wiring, tool executor, UI state
|
| 81 |
src/avatar.js AvatarStage: TalkingHead + HeadAudio + choreography
|
| 82 |
+
src/s2s/s2s-ws-client.js Realtime WS client (vendored from the Space; orb removed,
|
| 83 |
+
injectable output node + worklet base URL + shared-ctx close)
|
| 84 |
src/s2s/codec.js PCM/base64 + transcript helpers (vendored, unchanged)
|
| 85 |
src/vendor/headaudio.min.mjs HeadAudio node class (bundled)
|
| 86 |
+
public/worklets/ mic-capture + audio-playback AudioWorklets (vendored, unchanged)
|
| 87 |
+
public/vendor/ HeadAudio worklet processor + viseme model (runtime-loaded)
|
| 88 |
+
public/avatars/brunette.glb Default avatar (Ready Player Me; CC BY-NC 4.0 — non-commercial)
|
| 89 |
```
|
| 90 |
|
| 91 |
## Notes
|
| 92 |
|
| 93 |
- The avatar GLB must have a Mixamo-compatible rig plus ARKit and Oculus-viseme
|
| 94 |
blend shapes. Ready Player Me avatars work with
|
| 95 |
+
\`?morphTargets=ARKit,Oculus%20Visemes\` on the GLB URL.
|
| 96 |
+
- TalkingHead owns the AudioContext; the s2s client is handed \`head.audioCtx\`
|
| 97 |
and never closes it.
|
| 98 |
- Everything animation-related runs on requestAnimationFrame — a backgrounded
|
| 99 |
tab freezes the avatar (audio keeps playing).
|
index.html
CHANGED
|
@@ -3,61 +3,7 @@
|
|
| 3 |
<head>
|
| 4 |
<meta charset="utf-8" />
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
| 6 |
-
|
| 7 |
-
<!-- Page Title (keyword-rich, action-oriented) -->
|
| 8 |
-
<title>Gemma Avatar + V.AI STUDIO -- Real-Time 3D Chat & 8000+ Products</title>
|
| 9 |
-
|
| 10 |
-
<!-- SEO: Canonical URL -->
|
| 11 |
-
<link rel="canonical" href="https://huggingface.co/spaces/bep40/gemma-avatar" />
|
| 12 |
-
|
| 13 |
-
<!-- SEO: Meta Description (155 chars, unique, actionable) -->
|
| 14 |
-
<meta name="description" content="Gemma Avatar + V.AI STUDIO: Talk to Gemma 4 with a 3D talking-head avatar and real-time lip-sync. Search 8000+ kitchen appliances & smart locks from Malloca, Eurogold, Grob, Canzy, Demax." />
|
| 15 |
-
|
| 16 |
-
<!-- SEO: Keywords -->
|
| 17 |
-
<meta name="keywords" content="Gemma Avatar, V.AI STUDIO, 3D avatar, lip-sync, talking head, voice AI, Gemma 4, real-time chat, kitchen appliances, smart locks, Malloca, Eurogold, Grob, Canzy, Demax" />
|
| 18 |
-
|
| 19 |
-
<!-- SEO: Author & Publisher -->
|
| 20 |
-
<meta name="author" content="bep40" />
|
| 21 |
-
<meta name="publisher" content="Hugging Face" />
|
| 22 |
-
|
| 23 |
-
<!-- Open Graph (Facebook, LinkedIn, Discord, WhatsApp) -->
|
| 24 |
-
<meta property="og:type" content="website" />
|
| 25 |
-
<meta property="og:locale" content="en_US" />
|
| 26 |
-
<meta property="og:locale:alternate" content="vi_VN" />
|
| 27 |
-
<meta property="og:site_name" content="Hugging Face Spaces" />
|
| 28 |
-
<meta property="og:title" content="Gemma Avatar + V.AI STUDIO -- 3D Talking Avatar & 8000+ Products" />
|
| 29 |
-
<meta property="og:description" content="Talk to Gemma 4 face-to-face with a 3D talking-head avatar and real-time lip-sync. Search 8000+ kitchen appliances & smart locks from Malloca, Eurogold, Grob, Canzy, Demax." />
|
| 30 |
-
<meta property="og:url" content="https://huggingface.co/spaces/bep40/gemma-avatar" />
|
| 31 |
-
<meta property="og:image" content="https://huggingface.co/spaces/bep40/gemma-avatar/resolve/main/thumbnail.webp" />
|
| 32 |
-
<meta property="og:image:width" content="1200" />
|
| 33 |
-
<meta property="og:image:height" content="630" />
|
| 34 |
-
<meta property="og:image:alt" content="Gemma Avatar + V.AI STUDIO -- Real-Time 3D Talking Avatar and 8000+ Product Search" />
|
| 35 |
-
<meta property="og:image:type" content="image/webp" />
|
| 36 |
-
|
| 37 |
-
<!-- Twitter Card (summary_large_image) -->
|
| 38 |
-
<meta name="twitter:card" content="summary_large_image" />
|
| 39 |
-
<meta name="twitter:site" content="@huggingface" />
|
| 40 |
-
<meta name="twitter:creator" content="@bep40" />
|
| 41 |
-
<meta name="twitter:title" content="Gemma Avatar + V.AI STUDIO -- 3D Talking Avatar & 8000+ Products" />
|
| 42 |
-
<meta name="twitter:description" content="Talk to Gemma 4 face-to-face with a 3D talking-head avatar and real-time lip-sync. Search 8000+ kitchen appliances & smart locks." />
|
| 43 |
-
<meta name="twitter:image" content="https://huggingface.co/spaces/bep40/gemma-avatar/resolve/main/thumbnail.webp" />
|
| 44 |
-
<meta name="twitter:image:alt" content="Gemma Avatar + V.AI STUDIO -- Real-Time 3D Talking Avatar and 8000+ Product Search" />
|
| 45 |
-
|
| 46 |
-
<!-- SEO: Robots -->
|
| 47 |
-
<meta name="robots" content="index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1" />
|
| 48 |
-
|
| 49 |
-
<!-- SEO: Language -->
|
| 50 |
-
<link rel="alternate" hreflang="en" href="https://huggingface.co/spaces/bep40/gemma-avatar" />
|
| 51 |
-
<link rel="alternate" hreflang="vi" href="https://huggingface.co/spaces/bep40/gemma-avatar" />
|
| 52 |
-
<meta name="language" content="English, Vietnamese" />
|
| 53 |
-
|
| 54 |
-
<!-- SEO: Theme & App -->
|
| 55 |
-
<meta name="theme-color" content="#4f46e5" />
|
| 56 |
-
<meta name="msapplication-TileColor" content="#4f46e5" />
|
| 57 |
-
<meta name="apple-mobile-web-app-title" content="Gemma Avatar" />
|
| 58 |
-
<meta name="apple-mobile-web-app-capable" content="yes" />
|
| 59 |
-
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
| 60 |
-
|
| 61 |
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
| 62 |
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
| 63 |
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Geist+Mono:wght@500&display=swap" rel="stylesheet" />
|
|
@@ -77,7 +23,7 @@
|
|
| 77 |
<button id="settings-btn" class="icon-btn" aria-label="Settings" title="Settings">
|
| 78 |
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
| 79 |
<circle cx="12" cy="12" r="3"></circle>
|
| 80 |
-
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06
|
| 81 |
</svg>
|
| 82 |
</button>
|
| 83 |
</header>
|
|
|
|
| 3 |
<head>
|
| 4 |
<meta charset="utf-8" />
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
| 6 |
+
<title>Gemma Avatar + V.AI STUDIO - Chat & Voice</title>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
| 8 |
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
| 9 |
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Geist+Mono:wght@500&display=swap" rel="stylesheet" />
|
|
|
|
| 23 |
<button id="settings-btn" class="icon-btn" aria-label="Settings" title="Settings">
|
| 24 |
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
| 25 |
<circle cx="12" cy="12" r="3"></circle>
|
| 26 |
+
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06-.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06-.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
|
| 27 |
</svg>
|
| 28 |
</button>
|
| 29 |
</header>
|
index.ts
CHANGED
|
@@ -7,12 +7,13 @@
|
|
| 7 |
import index from "./index.html";
|
| 8 |
import { readdir } from "fs/promises";
|
| 9 |
import { join } from "path";
|
| 10 |
-
import { existsSync } from "fs";
|
| 11 |
|
| 12 |
const LOAD_BALANCER_URL = (Bun.env.LOAD_BALANCER_URL ?? "").trim().replace(/\/$/, "");
|
| 13 |
const SESSION_PROXY_URL = (Bun.env.SESSION_PROXY_URL ?? "https://smolagents-hf-realtime-voice.hf.space/api").trim().replace(/\/$/, "");
|
| 14 |
const UPSTREAM = LOAD_BALANCER_URL || SESSION_PROXY_URL;
|
| 15 |
const PORT = Number(Bun.env.PORT ?? 7860);
|
|
|
|
| 16 |
|
| 17 |
// ── JSON file from dataset (33.5MB, 23.4K rows) — kept for backend API compat ──
|
| 18 |
const JSON_URL =
|
|
@@ -146,10 +147,80 @@ async function sessionHandler(req: Request): Promise<Response> {
|
|
| 146 |
}
|
| 147 |
}
|
| 148 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
const server = Bun.serve({
|
| 150 |
port: PORT,
|
| 151 |
routes: {
|
| 152 |
-
"/":
|
| 153 |
"/api/config": { GET: () => Response.json({ lb: Boolean(UPSTREAM), allowDirect: !UPSTREAM }) },
|
| 154 |
"/api/avatars": { GET: async () => Response.json({ avatars: await listAvatars() }) },
|
| 155 |
"/api/vaix/products": { GET: async (req: Request) => {
|
|
|
|
| 7 |
import index from "./index.html";
|
| 8 |
import { readdir } from "fs/promises";
|
| 9 |
import { join } from "path";
|
| 10 |
+
import { existsSync, readFileSync } from "fs";
|
| 11 |
|
| 12 |
const LOAD_BALANCER_URL = (Bun.env.LOAD_BALANCER_URL ?? "").trim().replace(/\/$/, "");
|
| 13 |
const SESSION_PROXY_URL = (Bun.env.SESSION_PROXY_URL ?? "https://smolagents-hf-realtime-voice.hf.space/api").trim().replace(/\/$/, "");
|
| 14 |
const UPSTREAM = LOAD_BALANCER_URL || SESSION_PROXY_URL;
|
| 15 |
const PORT = Number(Bun.env.PORT ?? 7860);
|
| 16 |
+
const INDEX_HTML = readFileSync(join(import.meta.dir, "index.html"), "utf-8");
|
| 17 |
|
| 18 |
// ── JSON file from dataset (33.5MB, 23.4K rows) — kept for backend API compat ──
|
| 19 |
const JSON_URL =
|
|
|
|
| 147 |
}
|
| 148 |
}
|
| 149 |
|
| 150 |
+
// ── OG tags for product share links ──
|
| 151 |
+
function escapeHtml(s: string): string {
|
| 152 |
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
function truncate(s: string, n: number): string {
|
| 156 |
+
return s ? s.slice(0, n) : "";
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
function findProductInCache(query: string): any | null {
|
| 160 |
+
if (!VAIX_PRODUCTS?.length) return null;
|
| 161 |
+
const q = query.trim().toLowerCase();
|
| 162 |
+
for (const p of VAIX_PRODUCTS) {
|
| 163 |
+
const name = (p.n || p.name || "").toLowerCase();
|
| 164 |
+
const sku = (p.sku || "").toLowerCase();
|
| 165 |
+
const slug = (p.slug || "").toLowerCase();
|
| 166 |
+
if (name === q || sku === q || slug === q) return p;
|
| 167 |
+
if (name.includes(q) || slug.includes(q)) return p;
|
| 168 |
+
}
|
| 169 |
+
return null;
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
function injectProductOGTags(html: string, product: any): string {
|
| 173 |
+
const name = escapeHtml(product.n || product.name || "Sản phẩm");
|
| 174 |
+
const brand = escapeHtml(product.brand || "");
|
| 175 |
+
const price = product.pn ? Number(product.pn).toLocaleString("vi-VN") + "₫" : "Liên hệ";
|
| 176 |
+
const image = product.i || (Array.isArray(product.imgs) ? product.imgs[0] : "") || "";
|
| 177 |
+
const desc = escapeHtml(truncate(product.sum || product.desc || name, 300));
|
| 178 |
+
const mod = escapeHtml(product.mod || product.model || "");
|
| 179 |
+
const specs = product.specs && typeof product.specs === "object"
|
| 180 |
+
? Object.entries(product.specs).slice(0, 6).map(([k, v]) => `${k}: ${v}`).join(" | ")
|
| 181 |
+
: "";
|
| 182 |
+
const fullDesc = desc + (mod ? ` | Model: ${mod}` : "") + (specs ? ` | ${specs}` : "");
|
| 183 |
+
|
| 184 |
+
const ogTitle = `${name}${brand ? " - " + brand : ""} | ${price}`;
|
| 185 |
+
const ogImage = image || "https://huggingface.co/spaces/bep40/gemma-avatar/resolve/main/thumbnail.webp";
|
| 186 |
+
|
| 187 |
+
const ogTags = `
|
| 188 |
+
<meta property="og:title" content="${ogTitle}" />
|
| 189 |
+
<meta property="og:description" content="${truncate(fullDesc, 400)}" />
|
| 190 |
+
<meta property="og:image" content="${ogImage}" />
|
| 191 |
+
<meta property="og:image:width" content="600" />
|
| 192 |
+
<meta property="og:image:height" content="600" />
|
| 193 |
+
<meta property="og:type" content="website" />
|
| 194 |
+
<meta property="og:site_name" content="Gemma Avatar + V.AI STUDIO" />
|
| 195 |
+
<meta name="twitter:card" content="summary_large_image" />
|
| 196 |
+
<meta name="twitter:title" content="${ogTitle}" />
|
| 197 |
+
<meta name="twitter:description" content="${truncate(fullDesc, 400)}" />
|
| 198 |
+
<meta name="twitter:image" content="${ogImage}" />
|
| 199 |
+
`;
|
| 200 |
+
|
| 201 |
+
return html.replace("</head>", ogTags + "\n </head>");
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
function serveIndex(req: Request): Response {
|
| 205 |
+
const url = new URL(req.url);
|
| 206 |
+
const productSlug = url.searchParams.get("product");
|
| 207 |
+
if (productSlug && VAIX_PRODUCTS?.length) {
|
| 208 |
+
const p = findProductInCache(productSlug);
|
| 209 |
+
if (p) {
|
| 210 |
+
return new Response(injectProductOGTags(INDEX_HTML, p), {
|
| 211 |
+
headers: { "Content-Type": "text/html; charset=utf-8" },
|
| 212 |
+
});
|
| 213 |
+
}
|
| 214 |
+
}
|
| 215 |
+
return new Response(INDEX_HTML, {
|
| 216 |
+
headers: { "Content-Type": "text/html; charset=utf-8" },
|
| 217 |
+
});
|
| 218 |
+
}
|
| 219 |
+
|
| 220 |
const server = Bun.serve({
|
| 221 |
port: PORT,
|
| 222 |
routes: {
|
| 223 |
+
"/": serveIndex,
|
| 224 |
"/api/config": { GET: () => Response.json({ lb: Boolean(UPSTREAM), allowDirect: !UPSTREAM }) },
|
| 225 |
"/api/avatars": { GET: async () => Response.json({ avatars: await listAvatars() }) },
|
| 226 |
"/api/vaix/products": { GET: async (req: Request) => {
|
src/app.js
CHANGED
|
@@ -28,7 +28,7 @@ const DEFAULT_INSTRUCTIONS = [
|
|
| 28 |
"NEVER guess facts. Use search_web/wikipedia. Get datetime first. Never mention tools.",
|
| 29 |
].join(" ");
|
| 30 |
|
| 31 |
-
const GREETING_INSTRUCTIONS = "You are
|
| 32 |
|
| 33 |
const STORAGE_KEYS = { voice:"avatar.voice", avatar:"avatar.model", instructions:"avatar.instructions", directUrl:"avatar.directUrl", subtitles:"avatar.subtitles" };
|
| 34 |
|
|
|
|
| 28 |
"NEVER guess facts. Use search_web/wikipedia. Get datetime first. Never mention tools.",
|
| 29 |
].join(" ");
|
| 30 |
|
| 31 |
+
const GREETING_INSTRUCTIONS = "You are V, trợ lý AI đến từ V.AI STUDIO. Say exactly: 'Xin chào! Em là V, trợ lý AI đến từ V.AI STUDIO. Em có thể giúp anh chị tìm kiếm sản phẩm, trò chuyện, tra cứu thông tin. Anh chị nhấn nút tròn để nói chuyện hoặc nhấn icon chat để nhắn tin với em nhé!' Then end. No tools.";
|
| 32 |
|
| 33 |
const STORAGE_KEYS = { voice:"avatar.voice", avatar:"avatar.model", instructions:"avatar.instructions", directUrl:"avatar.directUrl", subtitles:"avatar.subtitles" };
|
| 34 |
|