veryfront 0.1.13 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/cli/app/data/slug-words.d.ts.map +1 -1
- package/esm/cli/app/data/slug-words.js +225 -90
- package/esm/cli/app/operations/project-creation.js +4 -3
- package/esm/cli/app/shell.js +1 -1
- package/esm/cli/app/utils.d.ts +5 -4
- package/esm/cli/app/utils.d.ts.map +1 -1
- package/esm/cli/app/utils.js +0 -23
- package/esm/cli/app/views/dashboard.d.ts +1 -1
- package/esm/cli/app/views/dashboard.d.ts.map +1 -1
- package/esm/cli/app/views/dashboard.js +22 -4
- package/esm/cli/auth/callback-server.d.ts.map +1 -1
- package/esm/cli/auth/callback-server.js +3 -2
- package/esm/cli/commands/dev/handler.d.ts.map +1 -1
- package/esm/cli/commands/dev/handler.js +2 -0
- package/esm/cli/commands/init/init-command.d.ts.map +1 -1
- package/esm/cli/commands/init/init-command.js +20 -3
- package/esm/cli/commands/init/interactive-wizard.d.ts +3 -2
- package/esm/cli/commands/init/interactive-wizard.d.ts.map +1 -1
- package/esm/cli/commands/init/interactive-wizard.js +55 -27
- package/esm/cli/mcp/remote-file-tools.d.ts +0 -6
- package/esm/cli/mcp/remote-file-tools.d.ts.map +1 -1
- package/esm/cli/mcp/remote-file-tools.js +37 -15
- package/esm/cli/shared/reserve-slug.d.ts.map +1 -1
- package/esm/cli/shared/reserve-slug.js +8 -3
- package/esm/cli/utils/env-prompt.d.ts.map +1 -1
- package/esm/cli/utils/env-prompt.js +3 -0
- package/esm/deno.d.ts +2 -1
- package/esm/deno.js +8 -4
- package/esm/src/agent/chat-handler.d.ts +4 -3
- package/esm/src/agent/chat-handler.d.ts.map +1 -1
- package/esm/src/agent/chat-handler.js +55 -4
- package/esm/src/agent/react/index.d.ts +1 -1
- package/esm/src/agent/react/index.d.ts.map +1 -1
- package/esm/src/agent/react/use-chat/browser-inference/browser-engine.d.ts +18 -0
- package/esm/src/agent/react/use-chat/browser-inference/browser-engine.d.ts.map +1 -0
- package/esm/src/agent/react/use-chat/browser-inference/browser-engine.js +54 -0
- package/esm/src/agent/react/use-chat/browser-inference/types.d.ts +43 -0
- package/esm/src/agent/react/use-chat/browser-inference/types.d.ts.map +1 -0
- package/esm/src/agent/react/use-chat/browser-inference/types.js +4 -0
- package/esm/src/agent/react/use-chat/browser-inference/worker-client.d.ts +23 -0
- package/esm/src/agent/react/use-chat/browser-inference/worker-client.d.ts.map +1 -0
- package/esm/src/agent/react/use-chat/browser-inference/worker-client.js +67 -0
- package/esm/src/agent/react/use-chat/browser-inference/worker-script.d.ts +8 -0
- package/esm/src/agent/react/use-chat/browser-inference/worker-script.d.ts.map +1 -0
- package/esm/src/agent/react/use-chat/browser-inference/worker-script.js +97 -0
- package/esm/src/agent/react/use-chat/index.d.ts +1 -1
- package/esm/src/agent/react/use-chat/index.d.ts.map +1 -1
- package/esm/src/agent/react/use-chat/types.d.ts +12 -0
- package/esm/src/agent/react/use-chat/types.d.ts.map +1 -1
- package/esm/src/agent/react/use-chat/use-chat.d.ts.map +1 -1
- package/esm/src/agent/react/use-chat/use-chat.js +120 -6
- package/esm/src/agent/runtime/index.d.ts.map +1 -1
- package/esm/src/agent/runtime/index.js +59 -7
- package/esm/src/build/production-build/templates.d.ts +2 -2
- package/esm/src/build/production-build/templates.d.ts.map +1 -1
- package/esm/src/build/production-build/templates.js +2 -68
- package/esm/src/chat/index.d.ts +1 -1
- package/esm/src/chat/index.d.ts.map +1 -1
- package/esm/src/errors/veryfront-error.d.ts +3 -0
- package/esm/src/errors/veryfront-error.d.ts.map +1 -1
- package/esm/src/platform/adapters/runtime/deno/adapter.d.ts.map +1 -1
- package/esm/src/platform/adapters/runtime/deno/adapter.js +5 -1
- package/esm/src/platform/compat/http/deno-server.d.ts.map +1 -1
- package/esm/src/platform/compat/http/deno-server.js +3 -2
- package/esm/src/provider/index.d.ts +1 -1
- package/esm/src/provider/index.d.ts.map +1 -1
- package/esm/src/provider/index.js +1 -1
- package/esm/src/provider/local/ai-sdk-adapter.d.ts +19 -0
- package/esm/src/provider/local/ai-sdk-adapter.d.ts.map +1 -0
- package/esm/src/provider/local/ai-sdk-adapter.js +164 -0
- package/esm/src/provider/local/env.d.ts +10 -0
- package/esm/src/provider/local/env.d.ts.map +1 -0
- package/esm/src/provider/local/env.js +23 -0
- package/esm/src/provider/local/local-engine.d.ts +61 -0
- package/esm/src/provider/local/local-engine.d.ts.map +1 -0
- package/esm/src/provider/local/local-engine.js +211 -0
- package/esm/src/provider/local/model-catalog.d.ts +30 -0
- package/esm/src/provider/local/model-catalog.d.ts.map +1 -0
- package/esm/src/provider/local/model-catalog.js +58 -0
- package/esm/src/provider/model-registry.d.ts +14 -0
- package/esm/src/provider/model-registry.d.ts.map +1 -1
- package/esm/src/provider/model-registry.js +58 -2
- package/esm/src/proxy/main.js +34 -6
- package/esm/src/proxy/server-resolver.d.ts +23 -0
- package/esm/src/proxy/server-resolver.d.ts.map +1 -0
- package/esm/src/proxy/server-resolver.js +124 -0
- package/esm/src/react/components/ai/chat/components/inference-badge.d.ts +8 -0
- package/esm/src/react/components/ai/chat/components/inference-badge.d.ts.map +1 -0
- package/esm/src/react/components/ai/chat/components/inference-badge.js +36 -0
- package/esm/src/react/components/ai/chat/components/upgrade-cta.d.ts +7 -0
- package/esm/src/react/components/ai/chat/components/upgrade-cta.d.ts.map +1 -0
- package/esm/src/react/components/ai/chat/components/upgrade-cta.js +33 -0
- package/esm/src/react/components/ai/chat/index.d.ts +7 -1
- package/esm/src/react/components/ai/chat/index.d.ts.map +1 -1
- package/esm/src/react/components/ai/chat/index.js +16 -4
- package/package.json +5 -1
- package/src/cli/app/data/slug-words.ts +225 -90
- package/src/cli/app/operations/project-creation.ts +3 -3
- package/src/cli/app/shell.ts +1 -1
- package/src/cli/app/utils.ts +0 -30
- package/src/cli/app/views/dashboard.ts +27 -4
- package/src/cli/auth/callback-server.ts +3 -2
- package/src/cli/commands/dev/handler.ts +2 -0
- package/src/cli/commands/init/init-command.ts +30 -3
- package/src/cli/commands/init/interactive-wizard.ts +62 -34
- package/src/cli/mcp/remote-file-tools.ts +50 -15
- package/src/cli/shared/reserve-slug.ts +9 -2
- package/src/cli/utils/env-prompt.ts +3 -0
- package/src/deno.js +8 -4
- package/src/src/agent/chat-handler.ts +57 -4
- package/src/src/agent/react/index.ts +2 -0
- package/src/src/agent/react/use-chat/browser-inference/browser-engine.ts +81 -0
- package/src/src/agent/react/use-chat/browser-inference/types.ts +52 -0
- package/src/src/agent/react/use-chat/browser-inference/worker-client.ts +89 -0
- package/src/src/agent/react/use-chat/browser-inference/worker-script.ts +98 -0
- package/src/src/agent/react/use-chat/index.ts +2 -0
- package/src/src/agent/react/use-chat/types.ts +20 -0
- package/src/src/agent/react/use-chat/use-chat.ts +148 -8
- package/src/src/agent/runtime/index.ts +72 -6
- package/src/src/build/production-build/templates.ts +2 -68
- package/src/src/chat/index.ts +2 -0
- package/src/src/errors/veryfront-error.ts +2 -1
- package/src/src/platform/adapters/runtime/deno/adapter.ts +5 -1
- package/src/src/platform/compat/http/deno-server.ts +3 -1
- package/src/src/provider/index.ts +1 -0
- package/src/src/provider/local/ai-sdk-adapter.ts +207 -0
- package/src/src/provider/local/env.ts +26 -0
- package/src/src/provider/local/local-engine.ts +288 -0
- package/src/src/provider/local/model-catalog.ts +73 -0
- package/src/src/provider/model-registry.ts +66 -2
- package/src/src/proxy/main.ts +41 -6
- package/src/src/proxy/server-resolver.ts +151 -0
- package/src/src/react/components/ai/chat/components/inference-badge.tsx +48 -0
- package/src/src/react/components/ai/chat/components/upgrade-cta.tsx +56 -0
- package/src/src/react/components/ai/chat/index.tsx +43 -6
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dedicated Server Resolver
|
|
3
|
+
*
|
|
4
|
+
* Resolves an environment ID to a dedicated server hostname.
|
|
5
|
+
* Used by the proxy to route traffic to dedicated servers instead of the shared pool.
|
|
6
|
+
*
|
|
7
|
+
* Caches results in memory with a short TTL to avoid hitting the API on every request.
|
|
8
|
+
* A null result (no dedicated server) is also cached to prevent repeated lookups.
|
|
9
|
+
*/
|
|
10
|
+
import * as dntShim from "../../_dnt.shims.js";
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
import { proxyLogger } from "./logger.js";
|
|
14
|
+
import { unrefTimer } from "../platform/compat/process.js";
|
|
15
|
+
|
|
16
|
+
const logger = proxyLogger.child({ module: "server-resolver" });
|
|
17
|
+
|
|
18
|
+
interface DedicatedServer {
|
|
19
|
+
id: string;
|
|
20
|
+
short_id: string;
|
|
21
|
+
hostname: string;
|
|
22
|
+
status: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface CacheEntry {
|
|
26
|
+
server: DedicatedServer | null;
|
|
27
|
+
expiresAt: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Thrown when the API call fails (network error, non-OK status, parse error). */
|
|
31
|
+
class ServerResolverError extends Error {
|
|
32
|
+
constructor(message: string, options?: { cause?: unknown }) {
|
|
33
|
+
super(message, options);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export class ServerResolver {
|
|
38
|
+
private cache = new Map<string, CacheEntry>();
|
|
39
|
+
private pending = new Map<string, Promise<DedicatedServer | null>>();
|
|
40
|
+
private cleanupTimer: ReturnType<typeof dntShim.setInterval> | null = null;
|
|
41
|
+
|
|
42
|
+
constructor(
|
|
43
|
+
private apiInternalUrl: string,
|
|
44
|
+
private apiUser: string,
|
|
45
|
+
private apiPass: string,
|
|
46
|
+
private cacheTtlMs: number = 30_000,
|
|
47
|
+
) {
|
|
48
|
+
// Cleanup expired entries every 60s
|
|
49
|
+
this.cleanupTimer = dntShim.setInterval(() => this.cleanup(), 60_000);
|
|
50
|
+
// Don't keep the process alive for cleanup
|
|
51
|
+
unrefTimer(this.cleanupTimer);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Resolve an environment ID to a dedicated server URL, or null for shared pool.
|
|
56
|
+
*/
|
|
57
|
+
async resolve(environmentId: string | undefined): Promise<string | null> {
|
|
58
|
+
if (!environmentId) return null;
|
|
59
|
+
|
|
60
|
+
const cached = this.cache.get(environmentId);
|
|
61
|
+
if (cached && Date.now() < cached.expiresAt) {
|
|
62
|
+
return cached.server ? `http://${cached.server.hostname}` : null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Deduplicate concurrent requests for the same environment
|
|
66
|
+
const inflight = this.pending.get(environmentId);
|
|
67
|
+
if (inflight) {
|
|
68
|
+
const server = await inflight;
|
|
69
|
+
return server ? `http://${server.hostname}` : null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const promise = this.fetchServer(environmentId);
|
|
73
|
+
this.pending.set(environmentId, promise);
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const server = await promise;
|
|
77
|
+
// Only cache successful API responses (server found OR explicit "no server").
|
|
78
|
+
// Transient errors (network failures, non-OK status) are NOT cached so the
|
|
79
|
+
// next request retries the API instead of suppressing dedicated routing.
|
|
80
|
+
this.cache.set(environmentId, {
|
|
81
|
+
server,
|
|
82
|
+
expiresAt: Date.now() + this.cacheTtlMs,
|
|
83
|
+
});
|
|
84
|
+
return server ? `http://${server.hostname}` : null;
|
|
85
|
+
} catch (error) {
|
|
86
|
+
// API error — don't cache, fall back to shared pool for this request
|
|
87
|
+
logger.warn("[ServerResolver] Transient error, skipping cache", {
|
|
88
|
+
environmentId,
|
|
89
|
+
error: error instanceof Error ? error.message : String(error),
|
|
90
|
+
});
|
|
91
|
+
return null;
|
|
92
|
+
} finally {
|
|
93
|
+
this.pending.delete(environmentId);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
close(): void {
|
|
98
|
+
if (this.cleanupTimer) {
|
|
99
|
+
clearInterval(this.cleanupTimer);
|
|
100
|
+
this.cleanupTimer = null;
|
|
101
|
+
}
|
|
102
|
+
this.cache.clear();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Fetch dedicated server from API.
|
|
107
|
+
* Returns DedicatedServer | null on success (null = no dedicated server assigned).
|
|
108
|
+
* Throws ServerResolverError on transient failures (network, non-OK status).
|
|
109
|
+
*/
|
|
110
|
+
private async fetchServer(environmentId: string): Promise<DedicatedServer | null> {
|
|
111
|
+
const url = `${this.apiInternalUrl}/internal/environment-server?environmentId=${
|
|
112
|
+
encodeURIComponent(environmentId)
|
|
113
|
+
}`;
|
|
114
|
+
const headers: Record<string, string> = { Accept: "application/json" };
|
|
115
|
+
|
|
116
|
+
if (this.apiUser && this.apiPass) {
|
|
117
|
+
headers.Authorization = `Basic ${btoa(`${this.apiUser}:${this.apiPass}`)}`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let response: dntShim.Response;
|
|
121
|
+
try {
|
|
122
|
+
response = await dntShim.fetch(url, { headers, signal: AbortSignal.timeout(5_000) });
|
|
123
|
+
} catch (error) {
|
|
124
|
+
throw new ServerResolverError(
|
|
125
|
+
`Failed to reach API: ${error instanceof Error ? error.message : String(error)}`,
|
|
126
|
+
{ cause: error },
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (!response.ok) {
|
|
131
|
+
await response.body?.cancel();
|
|
132
|
+
throw new ServerResolverError(`API returned ${response.status} for ${environmentId}`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const data = (await response.json()) as { server: DedicatedServer | null };
|
|
136
|
+
if (data.server) {
|
|
137
|
+
logger.debug("[ServerResolver] Resolved dedicated server", {
|
|
138
|
+
environmentId,
|
|
139
|
+
hostname: data.server.hostname,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
return data.server;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private cleanup(): void {
|
|
146
|
+
const now = Date.now();
|
|
147
|
+
for (const [key, entry] of this.cache) {
|
|
148
|
+
if (now >= entry.expiresAt) this.cache.delete(key);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { BrowserInferenceStatus, InferenceMode } from "../../../../../agent/react/index.js";
|
|
3
|
+
|
|
4
|
+
export interface InferenceBadgeProps {
|
|
5
|
+
inferenceMode: InferenceMode;
|
|
6
|
+
browserStatus?: BrowserInferenceStatus | null;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function InferenceBadge({
|
|
10
|
+
inferenceMode,
|
|
11
|
+
browserStatus,
|
|
12
|
+
}: InferenceBadgeProps): React.ReactElement | null {
|
|
13
|
+
if (inferenceMode === "cloud") return null;
|
|
14
|
+
|
|
15
|
+
let label: string;
|
|
16
|
+
let dotColor: string;
|
|
17
|
+
let showProgress = false;
|
|
18
|
+
|
|
19
|
+
if (inferenceMode === "server-local") {
|
|
20
|
+
label = "Running locally";
|
|
21
|
+
dotColor = "bg-green-500";
|
|
22
|
+
} else if (browserStatus === "downloading-model") {
|
|
23
|
+
label = "Downloading model...";
|
|
24
|
+
dotColor = "bg-amber-500";
|
|
25
|
+
showProgress = true;
|
|
26
|
+
} else if (browserStatus === "loading-runtime") {
|
|
27
|
+
label = "Loading AI runtime...";
|
|
28
|
+
dotColor = "bg-amber-500";
|
|
29
|
+
} else if (browserStatus === "generating") {
|
|
30
|
+
label = "Running in browser";
|
|
31
|
+
dotColor = "bg-green-500";
|
|
32
|
+
} else if (browserStatus === "error") {
|
|
33
|
+
label = "Local model failed";
|
|
34
|
+
dotColor = "bg-red-500";
|
|
35
|
+
} else {
|
|
36
|
+
label = "Running locally";
|
|
37
|
+
dotColor = "bg-green-500";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<div className="flex items-center gap-1.5 px-3 py-1 text-xs text-neutral-500 dark:text-neutral-400">
|
|
42
|
+
<span
|
|
43
|
+
className={`size-1.5 rounded-full ${dotColor} ${showProgress ? "animate-pulse" : ""}`}
|
|
44
|
+
/>
|
|
45
|
+
<span>{label}</span>
|
|
46
|
+
</div>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { InferenceMode } from "../../../../../agent/react/index.js";
|
|
3
|
+
|
|
4
|
+
const DISMISS_KEY = "vf-upgrade-cta-dismissed";
|
|
5
|
+
|
|
6
|
+
export interface UpgradeCTAProps {
|
|
7
|
+
inferenceMode: InferenceMode;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function UpgradeCTA({ inferenceMode }: UpgradeCTAProps): React.ReactElement | null {
|
|
11
|
+
const [dismissed, setDismissed] = React.useState(() => {
|
|
12
|
+
try {
|
|
13
|
+
return localStorage.getItem(DISMISS_KEY) === "1";
|
|
14
|
+
} catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
if (inferenceMode === "cloud" || dismissed) return null;
|
|
20
|
+
|
|
21
|
+
const handleDismiss = () => {
|
|
22
|
+
setDismissed(true);
|
|
23
|
+
try {
|
|
24
|
+
localStorage.setItem(DISMISS_KEY, "1");
|
|
25
|
+
} catch {
|
|
26
|
+
// localStorage may be unavailable
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<div className="w-full max-w-2xl mx-auto mt-4 px-4 py-3 bg-blue-50 dark:bg-blue-900/20 rounded-xl text-sm text-blue-700 dark:text-blue-300 flex items-start gap-3">
|
|
32
|
+
<span className="flex-1">
|
|
33
|
+
Using a lightweight local model. Add an API key to your{" "}
|
|
34
|
+
<code className="px-1 py-0.5 bg-blue-100 dark:bg-blue-900/40 rounded text-xs">.env</code>
|
|
35
|
+
{" "}
|
|
36
|
+
for GPT-4o or Claude.
|
|
37
|
+
</span>
|
|
38
|
+
<button
|
|
39
|
+
type="button"
|
|
40
|
+
onClick={handleDismiss}
|
|
41
|
+
className="text-blue-400 hover:text-blue-600 dark:hover:text-blue-200 transition-colors flex-shrink-0"
|
|
42
|
+
aria-label="Dismiss"
|
|
43
|
+
>
|
|
44
|
+
<svg
|
|
45
|
+
className="size-4"
|
|
46
|
+
viewBox="0 0 16 16"
|
|
47
|
+
fill="none"
|
|
48
|
+
stroke="currentColor"
|
|
49
|
+
strokeWidth="2"
|
|
50
|
+
>
|
|
51
|
+
<path d="M4 4l8 8M12 4l-8 8" />
|
|
52
|
+
</svg>
|
|
53
|
+
</button>
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
@@ -7,7 +7,13 @@ import {
|
|
|
7
7
|
SubmitButton,
|
|
8
8
|
} from "../../../primitives/index.js";
|
|
9
9
|
import { useVoiceInput } from "../../../../agent/react/index.js";
|
|
10
|
-
import type {
|
|
10
|
+
import type {
|
|
11
|
+
BrowserInferenceStatus,
|
|
12
|
+
DynamicToolUIPart,
|
|
13
|
+
InferenceMode,
|
|
14
|
+
ToolUIPart,
|
|
15
|
+
UIMessage,
|
|
16
|
+
} from "../../../../agent/react/index.js";
|
|
11
17
|
import { type ChatTheme, cn, defaultChatTheme, mergeThemes } from "../theme.js";
|
|
12
18
|
import { Markdown } from "../markdown.js";
|
|
13
19
|
import { MessageSquareIcon, RefreshCwIcon } from "../icons/index.js";
|
|
@@ -27,6 +33,8 @@ export {
|
|
|
27
33
|
} from "./components/empty-state.js";
|
|
28
34
|
export { MessageActions, type MessageActionsProps } from "./components/message-actions.js";
|
|
29
35
|
export { ToolCallCard, ToolStatusBadge } from "./components/tool-ui.js";
|
|
36
|
+
export { InferenceBadge, type InferenceBadgeProps } from "./components/inference-badge.js";
|
|
37
|
+
export { UpgradeCTA, type UpgradeCTAProps } from "./components/upgrade-cta.js";
|
|
30
38
|
|
|
31
39
|
export {
|
|
32
40
|
getTextContent,
|
|
@@ -48,6 +56,8 @@ import {
|
|
|
48
56
|
import { MessageActions } from "./components/message-actions.js";
|
|
49
57
|
import { ReasoningCard } from "./components/reasoning.js";
|
|
50
58
|
import { ToolCallCard } from "./components/tool-ui.js";
|
|
59
|
+
import { InferenceBadge } from "./components/inference-badge.js";
|
|
60
|
+
import { UpgradeCTA } from "./components/upgrade-cta.js";
|
|
51
61
|
import { getTextContent, groupPartsInOrder } from "./utils/message-parts.js";
|
|
52
62
|
|
|
53
63
|
export interface ChatProps {
|
|
@@ -86,6 +96,10 @@ export interface ChatProps {
|
|
|
86
96
|
model?: string;
|
|
87
97
|
/** Called when user changes model */
|
|
88
98
|
onModelChange?: (model: string) => void;
|
|
99
|
+
/** Where inference is currently happening */
|
|
100
|
+
inferenceMode?: InferenceMode;
|
|
101
|
+
/** Browser-side model loading/inference status */
|
|
102
|
+
browserStatus?: BrowserInferenceStatus | null;
|
|
89
103
|
}
|
|
90
104
|
|
|
91
105
|
export const Chat = React.forwardRef<HTMLDivElement, ChatProps>(function Chat(
|
|
@@ -118,6 +132,8 @@ export const Chat = React.forwardRef<HTMLDivElement, ChatProps>(function Chat(
|
|
|
118
132
|
models,
|
|
119
133
|
model,
|
|
120
134
|
onModelChange,
|
|
135
|
+
inferenceMode,
|
|
136
|
+
browserStatus,
|
|
121
137
|
},
|
|
122
138
|
ref,
|
|
123
139
|
): React.ReactElement {
|
|
@@ -172,6 +188,9 @@ export const Chat = React.forwardRef<HTMLDivElement, ChatProps>(function Chat(
|
|
|
172
188
|
</Suggestions>
|
|
173
189
|
</div>
|
|
174
190
|
)}
|
|
191
|
+
{inferenceMode && inferenceMode !== "cloud" && (
|
|
192
|
+
<UpgradeCTA inferenceMode={inferenceMode} />
|
|
193
|
+
)}
|
|
175
194
|
<div className="flex-1" />
|
|
176
195
|
</div>
|
|
177
196
|
)
|
|
@@ -239,11 +258,24 @@ export const Chat = React.forwardRef<HTMLDivElement, ChatProps>(function Chat(
|
|
|
239
258
|
{isLoading && (
|
|
240
259
|
<div className="flex justify-start">
|
|
241
260
|
<div className="bg-neutral-100 dark:bg-neutral-800 rounded-[20px] rounded-bl-[4px] px-4 py-3">
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
261
|
+
{browserStatus === "downloading-model" || browserStatus === "loading-runtime"
|
|
262
|
+
? (
|
|
263
|
+
<div className="flex items-center gap-2 text-xs text-neutral-500 dark:text-neutral-400">
|
|
264
|
+
<span className="size-1.5 rounded-full bg-amber-500 animate-pulse" />
|
|
265
|
+
<span>
|
|
266
|
+
{browserStatus === "downloading-model"
|
|
267
|
+
? "Downloading model..."
|
|
268
|
+
: "Loading AI..."}
|
|
269
|
+
</span>
|
|
270
|
+
</div>
|
|
271
|
+
)
|
|
272
|
+
: (
|
|
273
|
+
<div className="flex gap-1.5 items-center">
|
|
274
|
+
<span className={cn(theme.loading)} />
|
|
275
|
+
<span className={cn(theme.loading)} style={{ animationDelay: "0.15s" }} />
|
|
276
|
+
<span className={cn(theme.loading)} style={{ animationDelay: "0.3s" }} />
|
|
277
|
+
</div>
|
|
278
|
+
)}
|
|
247
279
|
</div>
|
|
248
280
|
</div>
|
|
249
281
|
)}
|
|
@@ -278,6 +310,11 @@ export const Chat = React.forwardRef<HTMLDivElement, ChatProps>(function Chat(
|
|
|
278
310
|
)}
|
|
279
311
|
|
|
280
312
|
<div className="flex-shrink-0 bg-white dark:bg-neutral-900 border-t border-neutral-200 dark:border-neutral-800">
|
|
313
|
+
{inferenceMode && inferenceMode !== "cloud" && (
|
|
314
|
+
<div className="max-w-2xl mx-auto">
|
|
315
|
+
<InferenceBadge inferenceMode={inferenceMode} browserStatus={browserStatus} />
|
|
316
|
+
</div>
|
|
317
|
+
)}
|
|
281
318
|
<form onSubmit={submitHandler} className="max-w-2xl mx-auto px-4 py-3">
|
|
282
319
|
{models && models.length > 0 && onModelChange && (
|
|
283
320
|
<div className="mb-2">
|