lightnode-sdk 0.10.1 → 0.10.2
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/dist/add.js +74 -14
- package/package.json +1 -1
package/dist/add.js
CHANGED
|
@@ -963,7 +963,7 @@ const NEXTJS_CHAT_WEB3_PAGE = `// app/chat-web3/page.tsx
|
|
|
963
963
|
|
|
964
964
|
import { useEffect, useRef, useState } from "react";
|
|
965
965
|
import { useAccount, useWalletClient, usePublicClient } from "wagmi";
|
|
966
|
-
import { siweSignIn, GatewayClient, LightChatSession, estimateJobFee, NETWORKS } from "lightnode-sdk";
|
|
966
|
+
import { siweSignIn, GatewayClient, LightChatSession, estimateJobFee, modelId, NETWORKS } from "lightnode-sdk";
|
|
967
967
|
import { Streamdown } from "streamdown";
|
|
968
968
|
import { ConnectButton } from "@/components/connect-button";
|
|
969
969
|
import { LcaiMark } from "@/components/lcai-mark";
|
|
@@ -976,6 +976,7 @@ type Turn = {
|
|
|
976
976
|
jobId?: string | null;
|
|
977
977
|
submitTx?: \`0x\${string}\` | null;
|
|
978
978
|
jobCompletedTx?: \`0x\${string}\` | null;
|
|
979
|
+
sources?: { position: number; title: string; url: string; description: string }[];
|
|
979
980
|
};
|
|
980
981
|
|
|
981
982
|
// Models live on LightChain mainnet. The visitor picks one per the dropdown.
|
|
@@ -1000,6 +1001,10 @@ export default function ChatWeb3() {
|
|
|
1000
1001
|
// Reused across turns so follow-ups skip SIWE + createSession.
|
|
1001
1002
|
const sessionRef = useRef<LightChatSession | null>(null);
|
|
1002
1003
|
const sessionKeyRef = useRef<string>("");
|
|
1004
|
+
const [searchEnabled, setSearchEnabled] = useState(false);
|
|
1005
|
+
const [searchCapable, setSearchCapable] = useState(false);
|
|
1006
|
+
const searchEnabledRef = useRef(false);
|
|
1007
|
+
searchEnabledRef.current = searchEnabled && searchCapable;
|
|
1003
1008
|
|
|
1004
1009
|
// Read the on-chain fee for the connected network so we can show the
|
|
1005
1010
|
// visitor the real cost per turn before they click Send.
|
|
@@ -1013,6 +1018,23 @@ export default function ChatWeb3() {
|
|
|
1013
1018
|
return () => { cancelled = true; };
|
|
1014
1019
|
}, [network, model]);
|
|
1015
1020
|
|
|
1021
|
+
// Gate the Web Search toggle on the model advertising the "search" capability.
|
|
1022
|
+
// (On networks where the capabilities endpoint isn't deployed this 404s and the
|
|
1023
|
+
// toggle stays locked - the honest state until a search-capable worker is up.)
|
|
1024
|
+
useEffect(() => {
|
|
1025
|
+
let cancelled = false;
|
|
1026
|
+
(async () => {
|
|
1027
|
+
try {
|
|
1028
|
+
const gw = new GatewayClient({ network: network ?? "mainnet" });
|
|
1029
|
+
const caps = await gw.getModelCapabilities(modelId(model));
|
|
1030
|
+
if (!cancelled) setSearchCapable(Array.isArray(caps?.capabilities) && caps.capabilities.includes("search"));
|
|
1031
|
+
} catch {
|
|
1032
|
+
if (!cancelled) setSearchCapable(false);
|
|
1033
|
+
}
|
|
1034
|
+
})();
|
|
1035
|
+
return () => { cancelled = true; };
|
|
1036
|
+
}, [network, model]);
|
|
1037
|
+
|
|
1016
1038
|
// Keep the latest turn in view. Instant while streaming (smooth scrolling on
|
|
1017
1039
|
// every chunk competes for the main thread); smooth once idle.
|
|
1018
1040
|
useEffect(() => {
|
|
@@ -1046,7 +1068,9 @@ export default function ChatWeb3() {
|
|
|
1046
1068
|
*/
|
|
1047
1069
|
async function ensureSession(): Promise<LightChatSession> {
|
|
1048
1070
|
if (!walletClient || !publicClient || !address || !network) throw new Error("connect a wallet first");
|
|
1049
|
-
|
|
1071
|
+
// A search session must bind to a search-capable worker, so it keys separately.
|
|
1072
|
+
const wantSearch = searchEnabledRef.current;
|
|
1073
|
+
const key = \`\${address}:\${network}:\${model}:\${wantSearch}\`;
|
|
1050
1074
|
const existing = sessionRef.current;
|
|
1051
1075
|
if (existing && !existing.expired && sessionKeyRef.current === key) return existing;
|
|
1052
1076
|
setBusyStage("Sign in with your wallet (SIWE)...");
|
|
@@ -1059,6 +1083,7 @@ export default function ChatWeb3() {
|
|
|
1059
1083
|
publicClient: publicClient as unknown as Parameters<typeof LightChatSession.open>[0]["publicClient"],
|
|
1060
1084
|
network: NETWORKS[network],
|
|
1061
1085
|
model,
|
|
1086
|
+
...(wantSearch ? { requiredCapabilities: ["search"] } : {}),
|
|
1062
1087
|
});
|
|
1063
1088
|
sessionRef.current = chat;
|
|
1064
1089
|
sessionKeyRef.current = key;
|
|
@@ -1086,7 +1111,7 @@ export default function ChatWeb3() {
|
|
|
1086
1111
|
patchLastAssistant({ text: totalSoFar });
|
|
1087
1112
|
};
|
|
1088
1113
|
const onStage = (s: string) => setBusyStage(s);
|
|
1089
|
-
const sendOpts = { onChunk, onStage };
|
|
1114
|
+
const sendOpts = { onChunk, onStage, searchEnabled: searchEnabledRef.current };
|
|
1090
1115
|
|
|
1091
1116
|
const chat = await ensureSession();
|
|
1092
1117
|
const result = await chat.send(prompt, sendOpts).catch(async () => {
|
|
@@ -1105,6 +1130,7 @@ export default function ChatWeb3() {
|
|
|
1105
1130
|
jobId: result.jobId?.toString() ?? null,
|
|
1106
1131
|
submitTx: result.txs?.submitJob ?? null,
|
|
1107
1132
|
jobCompletedTx: result.txs?.jobCompleted ?? null,
|
|
1133
|
+
sources: result.sources,
|
|
1108
1134
|
});
|
|
1109
1135
|
} catch (e) {
|
|
1110
1136
|
// Roll back the optimistic user bubble so the visitor can retry.
|
|
@@ -1184,6 +1210,22 @@ export default function ChatWeb3() {
|
|
|
1184
1210
|
{busyStage || "Thinking..."}
|
|
1185
1211
|
</div>
|
|
1186
1212
|
)}
|
|
1213
|
+
{t.sources && t.sources.length > 0 && (
|
|
1214
|
+
<div className="mt-1 border-t border-border pt-3">
|
|
1215
|
+
<div className="mb-2 text-[11px] font-medium uppercase tracking-wide text-muted-foreground">Sources</div>
|
|
1216
|
+
<div className="grid gap-2">
|
|
1217
|
+
{t.sources.map((s) => (
|
|
1218
|
+
<a key={s.position + "-" + s.url} href={s.url} target="_blank" rel="noopener noreferrer" className="grid grid-cols-[1.5rem_1fr] gap-2 rounded-lg bg-surface-base-faint px-2.5 py-2 transition-colors hover:bg-card">
|
|
1219
|
+
<span className="flex size-5 items-center justify-center rounded-md bg-card text-[11px] font-medium text-muted-foreground">{s.position}</span>
|
|
1220
|
+
<span className="min-w-0">
|
|
1221
|
+
<span className="block truncate text-sm font-medium text-foreground hover:underline">{s.title || s.url}</span>
|
|
1222
|
+
<span className="block truncate text-xs text-muted-foreground">{s.description || s.url}</span>
|
|
1223
|
+
</span>
|
|
1224
|
+
</a>
|
|
1225
|
+
))}
|
|
1226
|
+
</div>
|
|
1227
|
+
</div>
|
|
1228
|
+
)}
|
|
1187
1229
|
{t.submitTx && (
|
|
1188
1230
|
<div className="flex flex-wrap items-center gap-3 text-[11px] text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100">
|
|
1189
1231
|
<button
|
|
@@ -1233,17 +1275,35 @@ export default function ChatWeb3() {
|
|
|
1233
1275
|
/>
|
|
1234
1276
|
</div>
|
|
1235
1277
|
<div className="mt-1 flex items-center justify-between gap-2">
|
|
1236
|
-
<
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1278
|
+
<div className="flex items-center gap-3">
|
|
1279
|
+
<select
|
|
1280
|
+
value={model}
|
|
1281
|
+
onChange={(e) => setModel(e.target.value as ModelId)}
|
|
1282
|
+
disabled={busy}
|
|
1283
|
+
title="Model (both live on LightChain mainnet)"
|
|
1284
|
+
className="rounded-lg border border-border bg-background px-2 py-1 text-xs font-medium text-muted-foreground outline-none focus:ring-2 focus:ring-primary disabled:opacity-50"
|
|
1285
|
+
>
|
|
1286
|
+
{MODELS.map((m) => (
|
|
1287
|
+
<option key={m} value={m}>{m}</option>
|
|
1288
|
+
))}
|
|
1289
|
+
</select>
|
|
1290
|
+
<div
|
|
1291
|
+
className="flex items-center gap-2"
|
|
1292
|
+
title={searchCapable ? "Let the worker search the web for this turn" : "No web-search-capable worker is online for this model right now."}
|
|
1293
|
+
>
|
|
1294
|
+
<button
|
|
1295
|
+
type="button"
|
|
1296
|
+
role="switch"
|
|
1297
|
+
aria-checked={searchEnabled && searchCapable}
|
|
1298
|
+
disabled={!searchCapable || busy}
|
|
1299
|
+
onClick={() => setSearchEnabled((v) => !v)}
|
|
1300
|
+
className={"relative inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-colors disabled:cursor-not-allowed disabled:opacity-50 " + (searchEnabled && searchCapable ? "bg-primary" : "bg-muted-foreground/30")}
|
|
1301
|
+
>
|
|
1302
|
+
<span className={"inline-block size-4 rounded-full bg-white shadow transition-transform " + (searchEnabled && searchCapable ? "translate-x-4" : "translate-x-0.5")} />
|
|
1303
|
+
</button>
|
|
1304
|
+
<span className="text-xs text-muted-foreground">Web Search</span>
|
|
1305
|
+
</div>
|
|
1306
|
+
</div>
|
|
1247
1307
|
<button
|
|
1248
1308
|
type="button"
|
|
1249
1309
|
onClick={() => send()}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lightnode-sdk",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.2",
|
|
4
4
|
"description": "Read-only TypeScript client for LightChain AI: workers, jobs, models, on-chain registration, and per-model network analytics. Independent, community-built (not an official LightChain package).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|