@knolo/cli 3.2.1 → 4.0.0

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.
Files changed (28) hide show
  1. package/README.md +49 -3
  2. package/bin/knolo.mjs +555 -7
  3. package/package.json +4 -3
  4. package/templates/icp-knowledge-canister/README.md +71 -0
  5. package/templates/icp-knowledge-canister/canisters/knolo-core-rust/Cargo.toml +13 -0
  6. package/templates/icp-knowledge-canister/canisters/knolo-core-rust/src/lib.rs +499 -0
  7. package/templates/icp-knowledge-canister/canisters/knolo-icp-canister/Cargo.lock +541 -0
  8. package/templates/icp-knowledge-canister/canisters/knolo-icp-canister/Cargo.toml +20 -0
  9. package/templates/icp-knowledge-canister/canisters/knolo-icp-canister/knolo_icp.did +29 -0
  10. package/templates/icp-knowledge-canister/canisters/knolo-icp-canister/src/lib.rs +605 -0
  11. package/templates/icp-knowledge-canister/dfx.json +25 -0
  12. package/templates/icp-knowledge-canister/frontend/index.html +12 -0
  13. package/templates/icp-knowledge-canister/frontend/package.json +27 -0
  14. package/templates/icp-knowledge-canister/frontend/src/App.tsx +225 -0
  15. package/templates/icp-knowledge-canister/frontend/src/app.css +271 -0
  16. package/templates/icp-knowledge-canister/frontend/src/canister.ts +119 -0
  17. package/templates/icp-knowledge-canister/frontend/src/canisterConfig.test.ts +34 -0
  18. package/templates/icp-knowledge-canister/frontend/src/canisterConfig.ts +26 -0
  19. package/templates/icp-knowledge-canister/frontend/src/main.tsx +5 -0
  20. package/templates/icp-knowledge-canister/frontend/src/vite-env.d.ts +14 -0
  21. package/templates/icp-knowledge-canister/frontend/tsconfig.json +18 -0
  22. package/templates/icp-knowledge-canister/frontend/vite.config.ts +66 -0
  23. package/templates/icp-knowledge-canister/knolo.config.json +15 -0
  24. package/templates/icp-knowledge-canister/knowledge/alpha.md +5 -0
  25. package/templates/icp-knowledge-canister/knowledge/guides/beta.md +5 -0
  26. package/templates/icp-knowledge-canister/scripts/build-sample-pack.mjs +24 -0
  27. package/templates/icp-knowledge-canister/scripts/query.sh +11 -0
  28. package/templates/icp-knowledge-canister/scripts/upload-pack.sh +16 -0
@@ -0,0 +1,225 @@
1
+ import { useEffect, useState, type FormEvent } from 'react';
2
+
3
+ import { getClientConfig, getKnoloActor, type HitDto, type Opt, type PackInfo } from './canister';
4
+ import './app.css';
5
+
6
+ function readOpt<T>(value: Opt<T>): T | undefined {
7
+ return value[0];
8
+ }
9
+
10
+ function formatCount(value: Opt<bigint>): string {
11
+ const count = readOpt(value);
12
+ return count === undefined ? '--' : count.toString();
13
+ }
14
+
15
+ function formatScore(score: number): string {
16
+ return score.toFixed(3);
17
+ }
18
+
19
+ function formatError(error: unknown): string {
20
+ return error instanceof Error ? error.message : String(error);
21
+ }
22
+
23
+ const clientConfig = getClientConfig();
24
+
25
+ export default function App() {
26
+ const [packInfo, setPackInfo] = useState<PackInfo | null>(null);
27
+ const [query, setQuery] = useState('');
28
+ const [results, setResults] = useState<HitDto[]>([]);
29
+ const [isPackLoading, setIsPackLoading] = useState(true);
30
+ const [isSearching, setIsSearching] = useState(false);
31
+ const [hasSearched, setHasSearched] = useState(false);
32
+ const [lastQuery, setLastQuery] = useState('');
33
+ const [error, setError] = useState<string | null>(null);
34
+
35
+ useEffect(() => {
36
+ let active = true;
37
+
38
+ async function loadPackInfo() {
39
+ try {
40
+ const actor = await getKnoloActor();
41
+ const nextPackInfo = await actor.pack_info();
42
+ if (!active) {
43
+ return;
44
+ }
45
+
46
+ setPackInfo(nextPackInfo);
47
+ setError(null);
48
+ } catch (nextError) {
49
+ if (!active) {
50
+ return;
51
+ }
52
+
53
+ setError(formatError(nextError));
54
+ } finally {
55
+ if (active) {
56
+ setIsPackLoading(false);
57
+ }
58
+ }
59
+ }
60
+
61
+ void loadPackInfo();
62
+
63
+ return () => {
64
+ active = false;
65
+ };
66
+ }, []);
67
+
68
+ async function handleSearch(event: FormEvent<HTMLFormElement>) {
69
+ event.preventDefault();
70
+ const trimmedQuery = query.trim();
71
+
72
+ if (!trimmedQuery) {
73
+ setResults([]);
74
+ setHasSearched(false);
75
+ return;
76
+ }
77
+
78
+ setIsSearching(true);
79
+ setHasSearched(true);
80
+ setLastQuery(trimmedQuery);
81
+ setError(null);
82
+
83
+ try {
84
+ const actor = await getKnoloActor();
85
+ const nextResults = await actor.search(trimmedQuery, 5);
86
+ setResults(nextResults);
87
+ } catch (nextError) {
88
+ setResults([]);
89
+ setError(formatError(nextError));
90
+ } finally {
91
+ setIsSearching(false);
92
+ }
93
+ }
94
+
95
+ const packLoaded = packInfo?.loaded ?? false;
96
+ const searchDisabled = isPackLoading || isSearching || !packLoaded || !query.trim();
97
+
98
+ return (
99
+ <main className="shell">
100
+ <section className="hero">
101
+ <div>
102
+ <p className="eyebrow">Direct browser client</p>
103
+ <h1>Knolo ICP Search</h1>
104
+ <p className="hero-copy">
105
+ Query the `knolo_knowledge` canister straight from the browser with `@dfinity/agent`.
106
+ </p>
107
+ </div>
108
+ <dl className="connection-card">
109
+ <div>
110
+ <dt>Replica</dt>
111
+ <dd>{clientConfig.host}</dd>
112
+ </div>
113
+ <div>
114
+ <dt>Network</dt>
115
+ <dd>{clientConfig.network}</dd>
116
+ </div>
117
+ <div>
118
+ <dt>Canister</dt>
119
+ <dd>{clientConfig.canisterId ?? 'Not configured'}</dd>
120
+ </div>
121
+ </dl>
122
+ </section>
123
+
124
+ <section className="panel-grid">
125
+ <article className="panel">
126
+ <div className="panel-header">
127
+ <h2>Pack status</h2>
128
+ <span className={`badge ${packLoaded ? 'loaded' : 'idle'}`}>
129
+ {isPackLoading ? 'Checking...' : packLoaded ? 'Loaded' : 'Idle'}
130
+ </span>
131
+ </div>
132
+
133
+ {isPackLoading ? (
134
+ <p className="muted">Checking pack status...</p>
135
+ ) : packLoaded && packInfo ? (
136
+ <dl className="stats-grid">
137
+ <div>
138
+ <dt>Label</dt>
139
+ <dd>{readOpt(packInfo.label) ?? '--'}</dd>
140
+ </div>
141
+ <div>
142
+ <dt>Version</dt>
143
+ <dd>{readOpt(packInfo.version)?.toString() ?? '--'}</dd>
144
+ </div>
145
+ <div>
146
+ <dt>Docs</dt>
147
+ <dd>{formatCount(packInfo.docs)}</dd>
148
+ </div>
149
+ <div>
150
+ <dt>Blocks</dt>
151
+ <dd>{formatCount(packInfo.blocks)}</dd>
152
+ </div>
153
+ <div>
154
+ <dt>Terms</dt>
155
+ <dd>{formatCount(packInfo.terms)}</dd>
156
+ </div>
157
+ </dl>
158
+ ) : (
159
+ <p className="empty-state">No pack loaded</p>
160
+ )}
161
+ </article>
162
+
163
+ <article className="panel">
164
+ <div className="panel-header">
165
+ <h2>Search</h2>
166
+ <span className="badge neutral">Top 5 hits</span>
167
+ </div>
168
+
169
+ <form className="search-form" onSubmit={handleSearch}>
170
+ <label className="search-label" htmlFor="search-input">
171
+ Search query
172
+ </label>
173
+ <div className="search-row">
174
+ <input
175
+ id="search-input"
176
+ className="search-input"
177
+ type="search"
178
+ value={query}
179
+ placeholder="Try alpha beta"
180
+ onChange={(event) => setQuery(event.target.value)}
181
+ />
182
+ <button className="search-button" type="submit" disabled={searchDisabled}>
183
+ {isSearching ? 'Searching...' : 'Search'}
184
+ </button>
185
+ </div>
186
+ </form>
187
+
188
+ {!packLoaded && !isPackLoading ? (
189
+ <p className="muted">
190
+ Load a `.knolo` pack into the canister first, then the browser client can query it.
191
+ </p>
192
+ ) : null}
193
+
194
+ {error ? <p className="error-box">{error}</p> : null}
195
+ </article>
196
+ </section>
197
+
198
+ <section className="panel results-panel">
199
+ <div className="panel-header">
200
+ <h2>Results</h2>
201
+ <span className="badge neutral">{results.length} items</span>
202
+ </div>
203
+
204
+ {results.length > 0 ? (
205
+ <ol className="results-list">
206
+ {results.map((result) => (
207
+ <li className="result-card" key={`${result.block_id.toString()}-${result.score}`}>
208
+ <div className="result-meta">
209
+ <span>Source: {readOpt(result.source) ?? 'Unknown'}</span>
210
+ <span>Namespace: {readOpt(result.namespace) ?? '--'}</span>
211
+ <span>Score: {formatScore(result.score)}</span>
212
+ </div>
213
+ <p className="result-text">{result.text}</p>
214
+ </li>
215
+ ))}
216
+ </ol>
217
+ ) : hasSearched ? (
218
+ <p className="empty-state">No matches for "{lastQuery}".</p>
219
+ ) : (
220
+ <p className="muted">Run a search to inspect canister results here.</p>
221
+ )}
222
+ </section>
223
+ </main>
224
+ );
225
+ }
@@ -0,0 +1,271 @@
1
+ :root {
2
+ font-family: 'Avenir Next', 'Trebuchet MS', 'Segoe UI', sans-serif;
3
+ color: #132430;
4
+ background:
5
+ radial-gradient(circle at top left, rgba(253, 214, 146, 0.42), transparent 28%),
6
+ radial-gradient(circle at top right, rgba(130, 188, 196, 0.2), transparent 24%),
7
+ linear-gradient(180deg, #f7f4eb 0%, #eef2ec 100%);
8
+ line-height: 1.5;
9
+ font-weight: 400;
10
+ }
11
+
12
+ * {
13
+ box-sizing: border-box;
14
+ }
15
+
16
+ body {
17
+ margin: 0;
18
+ min-width: 320px;
19
+ min-height: 100vh;
20
+ }
21
+
22
+ button,
23
+ input {
24
+ font: inherit;
25
+ }
26
+
27
+ #root {
28
+ min-height: 100vh;
29
+ }
30
+
31
+ .shell {
32
+ width: min(1120px, calc(100vw - 2rem));
33
+ margin: 0 auto;
34
+ padding: 2.5rem 0 3.5rem;
35
+ }
36
+
37
+ .hero {
38
+ display: grid;
39
+ grid-template-columns: minmax(0, 1.7fr) minmax(280px, 0.9fr);
40
+ gap: 1.5rem;
41
+ align-items: start;
42
+ margin-bottom: 1.5rem;
43
+ }
44
+
45
+ .eyebrow {
46
+ margin: 0 0 0.5rem;
47
+ text-transform: uppercase;
48
+ letter-spacing: 0.2em;
49
+ font-size: 0.78rem;
50
+ color: #7b5842;
51
+ }
52
+
53
+ h1,
54
+ h2 {
55
+ margin: 0;
56
+ font-family: Georgia, 'Times New Roman', serif;
57
+ font-weight: 600;
58
+ }
59
+
60
+ h1 {
61
+ font-size: clamp(2.2rem, 4vw, 3.6rem);
62
+ line-height: 0.98;
63
+ max-width: 12ch;
64
+ }
65
+
66
+ .hero-copy {
67
+ margin: 1rem 0 0;
68
+ max-width: 55ch;
69
+ color: #3a4c56;
70
+ }
71
+
72
+ .connection-card,
73
+ .panel {
74
+ background: rgba(255, 255, 255, 0.8);
75
+ border: 1px solid rgba(19, 36, 48, 0.08);
76
+ border-radius: 24px;
77
+ box-shadow: 0 22px 60px rgba(19, 36, 48, 0.08);
78
+ backdrop-filter: blur(12px);
79
+ }
80
+
81
+ .connection-card {
82
+ margin: 0;
83
+ padding: 1.25rem;
84
+ display: grid;
85
+ gap: 1rem;
86
+ }
87
+
88
+ .connection-card dt,
89
+ .stats-grid dt {
90
+ margin: 0 0 0.35rem;
91
+ font-size: 0.78rem;
92
+ letter-spacing: 0.08em;
93
+ text-transform: uppercase;
94
+ color: #6c7b83;
95
+ }
96
+
97
+ .connection-card dd,
98
+ .stats-grid dd {
99
+ margin: 0;
100
+ font-size: 0.96rem;
101
+ overflow-wrap: anywhere;
102
+ }
103
+
104
+ .panel-grid {
105
+ display: grid;
106
+ grid-template-columns: repeat(2, minmax(0, 1fr));
107
+ gap: 1.5rem;
108
+ margin-bottom: 1.5rem;
109
+ }
110
+
111
+ .panel,
112
+ .results-panel {
113
+ padding: 1.35rem;
114
+ }
115
+
116
+ .panel-header {
117
+ display: flex;
118
+ align-items: center;
119
+ justify-content: space-between;
120
+ gap: 1rem;
121
+ margin-bottom: 1rem;
122
+ }
123
+
124
+ .badge {
125
+ display: inline-flex;
126
+ align-items: center;
127
+ border-radius: 999px;
128
+ padding: 0.35rem 0.75rem;
129
+ font-size: 0.82rem;
130
+ font-weight: 600;
131
+ }
132
+
133
+ .badge.loaded {
134
+ background: rgba(79, 154, 117, 0.16);
135
+ color: #246243;
136
+ }
137
+
138
+ .badge.idle {
139
+ background: rgba(196, 143, 76, 0.14);
140
+ color: #8b5c24;
141
+ }
142
+
143
+ .badge.neutral {
144
+ background: rgba(40, 82, 99, 0.1);
145
+ color: #285263;
146
+ }
147
+
148
+ .stats-grid {
149
+ margin: 0;
150
+ display: grid;
151
+ grid-template-columns: repeat(2, minmax(0, 1fr));
152
+ gap: 1rem;
153
+ }
154
+
155
+ .search-form {
156
+ display: grid;
157
+ gap: 0.7rem;
158
+ }
159
+
160
+ .search-label {
161
+ font-weight: 600;
162
+ color: #223944;
163
+ }
164
+
165
+ .search-row {
166
+ display: flex;
167
+ gap: 0.75rem;
168
+ }
169
+
170
+ .search-input {
171
+ flex: 1;
172
+ min-width: 0;
173
+ border: 1px solid rgba(34, 57, 68, 0.18);
174
+ border-radius: 16px;
175
+ background: rgba(250, 250, 247, 0.95);
176
+ padding: 0.9rem 1rem;
177
+ color: #132430;
178
+ }
179
+
180
+ .search-input:focus {
181
+ outline: 2px solid rgba(42, 110, 132, 0.22);
182
+ border-color: rgba(42, 110, 132, 0.45);
183
+ }
184
+
185
+ .search-button {
186
+ border: 0;
187
+ border-radius: 16px;
188
+ background: linear-gradient(135deg, #1d5666 0%, #43807f 100%);
189
+ color: #fffdf8;
190
+ padding: 0.9rem 1.15rem;
191
+ min-width: 8.5rem;
192
+ font-weight: 600;
193
+ cursor: pointer;
194
+ }
195
+
196
+ .search-button:disabled {
197
+ cursor: not-allowed;
198
+ opacity: 0.58;
199
+ }
200
+
201
+ .muted,
202
+ .empty-state {
203
+ margin: 1rem 0 0;
204
+ color: #52636d;
205
+ }
206
+
207
+ .empty-state {
208
+ font-weight: 600;
209
+ }
210
+
211
+ .error-box {
212
+ margin: 1rem 0 0;
213
+ padding: 0.85rem 1rem;
214
+ border-radius: 16px;
215
+ background: rgba(182, 63, 69, 0.08);
216
+ color: #8b232d;
217
+ }
218
+
219
+ .results-list {
220
+ list-style: none;
221
+ padding: 0;
222
+ margin: 0;
223
+ display: grid;
224
+ gap: 1rem;
225
+ }
226
+
227
+ .result-card {
228
+ border: 1px solid rgba(19, 36, 48, 0.08);
229
+ border-radius: 20px;
230
+ background: rgba(250, 249, 244, 0.9);
231
+ padding: 1rem;
232
+ }
233
+
234
+ .result-meta {
235
+ display: flex;
236
+ flex-wrap: wrap;
237
+ gap: 0.8rem 1rem;
238
+ margin-bottom: 0.7rem;
239
+ font-size: 0.88rem;
240
+ color: #52636d;
241
+ }
242
+
243
+ .result-text {
244
+ margin: 0;
245
+ white-space: pre-wrap;
246
+ color: #122631;
247
+ }
248
+
249
+ @media (max-width: 820px) {
250
+ .shell {
251
+ width: min(100vw - 1.2rem, 1120px);
252
+ padding-top: 1.5rem;
253
+ }
254
+
255
+ .hero,
256
+ .panel-grid {
257
+ grid-template-columns: 1fr;
258
+ }
259
+
260
+ .search-row {
261
+ flex-direction: column;
262
+ }
263
+
264
+ .search-button {
265
+ width: 100%;
266
+ }
267
+
268
+ .stats-grid {
269
+ grid-template-columns: 1fr;
270
+ }
271
+ }
@@ -0,0 +1,119 @@
1
+ import { Actor, HttpAgent } from '@dfinity/agent';
2
+ import type { ActorMethod, ActorSubclass } from '@dfinity/agent';
3
+ import { IDL } from '@dfinity/candid';
4
+ import type { InterfaceFactory } from '@dfinity/candid/lib/cjs/idl';
5
+
6
+ import { resolveCanisterId, resolveNetwork, resolveReplicaHost } from './canisterConfig';
7
+
8
+ export type Opt<T> = [] | [T];
9
+
10
+ export interface HealthDto {
11
+ ok: boolean;
12
+ message: string;
13
+ }
14
+
15
+ export interface HitDto {
16
+ block_id: bigint;
17
+ score: number;
18
+ text: string;
19
+ source: Opt<string>;
20
+ namespace: Opt<string>;
21
+ }
22
+
23
+ export interface PackInfo {
24
+ loaded: boolean;
25
+ label: Opt<string>;
26
+ version: Opt<number>;
27
+ docs: Opt<bigint>;
28
+ blocks: Opt<bigint>;
29
+ terms: Opt<bigint>;
30
+ }
31
+
32
+ export interface KnoloService {
33
+ clear_pack: ActorMethod<[], HealthDto>;
34
+ health: ActorMethod<[], HealthDto>;
35
+ pack_info: ActorMethod<[], PackInfo>;
36
+ search: ActorMethod<[string, number], Array<HitDto>>;
37
+ set_pack: ActorMethod<[Uint8Array | number[], string], HealthDto>;
38
+ }
39
+
40
+ export const idlFactory: InterfaceFactory = ({ IDL: idl }) => {
41
+ const HealthDto = idl.Record({ ok: idl.Bool, message: idl.Text });
42
+ const PackInfo = idl.Record({
43
+ terms: idl.Opt(idl.Nat64),
44
+ docs: idl.Opt(idl.Nat64),
45
+ loaded: idl.Bool,
46
+ label: idl.Opt(idl.Text),
47
+ version: idl.Opt(idl.Nat32),
48
+ blocks: idl.Opt(idl.Nat64),
49
+ });
50
+ const HitDto = idl.Record({
51
+ block_id: idl.Nat64,
52
+ source: idl.Opt(idl.Text),
53
+ text: idl.Text,
54
+ score: idl.Float64,
55
+ namespace: idl.Opt(idl.Text),
56
+ });
57
+
58
+ return idl.Service({
59
+ clear_pack: idl.Func([], [HealthDto], []),
60
+ health: idl.Func([], [HealthDto], ['query']),
61
+ pack_info: idl.Func([], [PackInfo], ['query']),
62
+ search: idl.Func([idl.Text, idl.Nat32], [idl.Vec(HitDto)], ['query']),
63
+ set_pack: idl.Func([idl.Vec(idl.Nat8), idl.Text], [HealthDto], []),
64
+ });
65
+ };
66
+
67
+ function formatError(error: unknown): string {
68
+ return error instanceof Error ? error.message : String(error);
69
+ }
70
+
71
+ export function getClientConfig(locationLike: Pick<Location, 'hostname'> = window.location) {
72
+ const network = resolveNetwork(
73
+ import.meta.env.VITE_DFX_NETWORK ?? __KNOLO_DFX_NETWORK__,
74
+ locationLike.hostname
75
+ );
76
+ const host = resolveReplicaHost(locationLike.hostname, import.meta.env.VITE_IC_HOST);
77
+ const canisterId = resolveCanisterId(
78
+ import.meta.env.VITE_KNOLO_CANISTER_ID,
79
+ __KNOLO_CANISTER_ID__
80
+ );
81
+
82
+ return { canisterId, host, network };
83
+ }
84
+
85
+ let actorPromise: Promise<ActorSubclass<KnoloService>> | undefined;
86
+
87
+ export async function getKnoloActor(): Promise<ActorSubclass<KnoloService>> {
88
+ if (!actorPromise) {
89
+ actorPromise = createKnoloActor().catch((error: unknown) => {
90
+ actorPromise = undefined;
91
+ throw error;
92
+ });
93
+ }
94
+
95
+ return actorPromise;
96
+ }
97
+
98
+ async function createKnoloActor(): Promise<ActorSubclass<KnoloService>> {
99
+ const config = getClientConfig();
100
+ if (!config.canisterId) {
101
+ throw new Error(
102
+ 'Missing canister ID. Set VITE_KNOLO_CANISTER_ID or run dfx deploy in examples/icp-knowledge-canister.'
103
+ );
104
+ }
105
+
106
+ const agent = new HttpAgent({ host: config.host });
107
+ if (config.network !== 'ic') {
108
+ await agent.fetchRootKey().catch((error: unknown) => {
109
+ throw new Error(
110
+ `Unable to fetch the local replica root key from ${config.host}: ${formatError(error)}`
111
+ );
112
+ });
113
+ }
114
+
115
+ return Actor.createActor<KnoloService>(idlFactory, {
116
+ agent,
117
+ canisterId: config.canisterId,
118
+ });
119
+ }
@@ -0,0 +1,34 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import {
4
+ IC_REPLICA_HOST,
5
+ LOCAL_REPLICA_HOST,
6
+ isLocalPage,
7
+ resolveCanisterId,
8
+ resolveNetwork,
9
+ resolveReplicaHost,
10
+ } from './canisterConfig';
11
+
12
+ describe('canister config helpers', () => {
13
+ it('prefers an explicit canister id', () => {
14
+ expect(resolveCanisterId(' aaaaa-aa ', 'bbbbb-bb')).toBe('aaaaa-aa');
15
+ });
16
+
17
+ it('falls back to the dfx-derived canister id', () => {
18
+ expect(resolveCanisterId(undefined, ' bkyz2-fmaaa-aaaaa-qaaaq-cai ')).toBe(
19
+ 'bkyz2-fmaaa-aaaaa-qaaaq-cai'
20
+ );
21
+ });
22
+
23
+ it('detects local pages and local replica defaults', () => {
24
+ expect(isLocalPage('localhost')).toBe(true);
25
+ expect(resolveNetwork(undefined, '127.0.0.1')).toBe('local');
26
+ expect(resolveReplicaHost('localhost', undefined)).toBe(LOCAL_REPLICA_HOST);
27
+ });
28
+
29
+ it('uses ic defaults away from localhost', () => {
30
+ expect(isLocalPage('knolo.example')).toBe(false);
31
+ expect(resolveNetwork(undefined, 'knolo.example')).toBe('ic');
32
+ expect(resolveReplicaHost('knolo.example', undefined)).toBe(IC_REPLICA_HOST);
33
+ });
34
+ });
@@ -0,0 +1,26 @@
1
+ export const LOCAL_REPLICA_HOST = 'http://127.0.0.1:4943';
2
+ export const IC_REPLICA_HOST = 'https://icp-api.io';
3
+
4
+ function trimValue(value: string | undefined | null): string | undefined {
5
+ const trimmed = value?.trim();
6
+ return trimmed ? trimmed : undefined;
7
+ }
8
+
9
+ export function isLocalPage(hostname: string): boolean {
10
+ return hostname === '127.0.0.1' || hostname === 'localhost';
11
+ }
12
+
13
+ export function resolveNetwork(networkOverride: string | undefined, hostname: string): string {
14
+ return trimValue(networkOverride) ?? (isLocalPage(hostname) ? 'local' : 'ic');
15
+ }
16
+
17
+ export function resolveReplicaHost(hostname: string, hostOverride: string | undefined): string {
18
+ return trimValue(hostOverride) ?? (isLocalPage(hostname) ? LOCAL_REPLICA_HOST : IC_REPLICA_HOST);
19
+ }
20
+
21
+ export function resolveCanisterId(
22
+ explicitCanisterId: string | undefined,
23
+ fallbackCanisterId: string | undefined
24
+ ): string | undefined {
25
+ return trimValue(explicitCanisterId) ?? trimValue(fallbackCanisterId);
26
+ }
@@ -0,0 +1,5 @@
1
+ import ReactDOM from 'react-dom/client';
2
+
3
+ import App from './App';
4
+
5
+ ReactDOM.createRoot(document.getElementById('root')!).render(<App />);
@@ -0,0 +1,14 @@
1
+ /// <reference types="vite/client" />
2
+
3
+ interface ImportMetaEnv {
4
+ readonly VITE_DFX_NETWORK?: string;
5
+ readonly VITE_IC_HOST?: string;
6
+ readonly VITE_KNOLO_CANISTER_ID?: string;
7
+ }
8
+
9
+ interface ImportMeta {
10
+ readonly env: ImportMetaEnv;
11
+ }
12
+
13
+ declare const __KNOLO_CANISTER_ID__: string | undefined;
14
+ declare const __KNOLO_DFX_NETWORK__: string;