@gmickel/gno 1.46.0 → 2.1.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.
- package/README.md +17 -5
- package/THIRD_PARTY_NOTICES.md +46 -0
- package/assets/skill/SKILL.md +7 -6
- package/assets/spa-production.json.gz +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v2.1.0.zip +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v2.1.0.zip.sha256 +1 -0
- package/browser-extension/dist/chunk-4tc9v0ja.js +74 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/browser-extension/dist/preview.html +1 -1
- package/browser-extension/dist/service-worker.js +32 -33
- package/bunfig.toml +2 -0
- package/package.json +40 -26
- package/spec/cli.md +29 -4
- package/spec/db/schema.sql +146 -1
- package/spec/mcp.md +26 -0
- package/src/app/context-runtime-types.ts +3 -0
- package/src/app/context-runtime.ts +2 -0
- package/src/cli/commands/ask.ts +6 -1
- package/src/cli/commands/daemon.ts +21 -8
- package/src/cli/commands/embed.ts +77 -41
- package/src/cli/detach.ts +3 -2
- package/src/config/types.ts +3 -3
- package/src/converters/adapters/markitdownTs/adapter.ts +1 -2
- package/src/converters/adapters/officeparser/adapter.ts +1 -2
- package/src/converters/versions.ts +6 -8
- package/src/core/context-evidence.ts +8 -4
- package/src/core/job-manager.ts +95 -13
- package/src/core/network-boundary-inventory.ts +10 -0
- package/src/core/shutdown-budget.ts +45 -0
- package/src/embed/backlog.ts +107 -4
- package/src/embed/batch.ts +42 -2
- package/src/embed/fingerprint.ts +16 -0
- package/src/embed/retry.ts +113 -5
- package/src/embed/variant-backlog.ts +105 -0
- package/src/embed/variant-plan.ts +62 -0
- package/src/embed/variant-retry.ts +113 -0
- package/src/ingestion/graph-reconciliation.ts +327 -0
- package/src/ingestion/sync.ts +9 -272
- package/src/llm/http-inference.ts +6 -0
- package/src/llm/httpEmbedding.ts +37 -6
- package/src/llm/httpGeneration.ts +18 -3
- package/src/llm/httpRerank.ts +23 -5
- package/src/llm/inference-cancellation.ts +168 -0
- package/src/llm/inference-scope.ts +202 -0
- package/src/llm/lazy-ports.ts +115 -0
- package/src/llm/native-worker/client.ts +541 -0
- package/src/llm/native-worker/dispatcher.ts +228 -0
- package/src/llm/native-worker/embedding-identity.ts +33 -0
- package/src/llm/native-worker/entry.ts +173 -0
- package/src/llm/native-worker/errors.ts +32 -0
- package/src/llm/native-worker/evaluation.ts +16 -0
- package/src/llm/native-worker/owned-exit.ts +108 -0
- package/src/llm/native-worker/owner.ts +141 -0
- package/src/llm/native-worker/ports.ts +317 -0
- package/src/llm/native-worker/protocol.ts +442 -0
- package/src/llm/native-worker/runtime-config.ts +92 -0
- package/src/llm/nodeLlamaCpp/adapter.ts +77 -20
- package/src/llm/nodeLlamaCpp/embedding.ts +130 -46
- package/src/llm/nodeLlamaCpp/generation.ts +34 -5
- package/src/llm/nodeLlamaCpp/lifecycle-options.ts +99 -0
- package/src/llm/nodeLlamaCpp/lifecycle.ts +209 -204
- package/src/llm/nodeLlamaCpp/rerank-capacity.ts +111 -0
- package/src/llm/nodeLlamaCpp/rerank.ts +118 -27
- package/src/llm/nodeLlamaCpp/simulator-handle.ts +73 -0
- package/src/llm/nodeLlamaCpp/simulator-install.ts +124 -0
- package/src/llm/nodeLlamaCpp/simulator-session.ts +240 -0
- package/src/llm/nodeLlamaCpp/simulator-types.ts +80 -0
- package/src/llm/types.ts +35 -5
- package/src/mcp/context.ts +27 -0
- package/src/mcp/http-transport.ts +12 -10
- package/src/mcp/server.ts +3 -0
- package/src/mcp/tool-profile.ts +30 -8
- package/src/mcp/tools/context.ts +8 -11
- package/src/mcp/tools/embed.ts +1 -1
- package/src/mcp/tools/index-cmd.ts +1 -1
- package/src/mcp/tools/index.ts +10 -8
- package/src/mcp/tools/query.ts +14 -30
- package/src/mcp/tools/vsearch.ts +1 -1
- package/src/pipeline/answer.ts +23 -3
- package/src/pipeline/claim-verifier.ts +6 -0
- package/src/pipeline/expansion.ts +43 -40
- package/src/pipeline/explain.ts +6 -2
- package/src/pipeline/filters.ts +63 -0
- package/src/pipeline/fusion.ts +29 -9
- package/src/pipeline/graph-retrieval.ts +29 -9
- package/src/pipeline/hybrid.ts +198 -55
- package/src/pipeline/hydration.ts +161 -0
- package/src/pipeline/owner-fusion.ts +87 -0
- package/src/pipeline/rerank.ts +35 -11
- package/src/pipeline/search.ts +13 -2
- package/src/pipeline/types.ts +5 -3
- package/src/pipeline/vsearch.ts +87 -7
- package/src/sdk/client.ts +47 -3
- package/src/sdk/embed.ts +63 -39
- package/src/serve/background-runtime.ts +1 -1
- package/src/serve/context.ts +41 -56
- package/src/serve/embed-scheduler.ts +58 -35
- package/src/serve/public/components/IndexingProgress.tsx +46 -60
- package/src/serve/public/components/PublishExportDialog.tsx +266 -0
- package/src/serve/public/globals.built.css +1 -1
- package/src/serve/public/globals.css +35 -0
- package/src/serve/public/lib/publish-export.ts +81 -1
- package/src/serve/public/lib/shiki-language-ids.ts +14 -0
- package/src/serve/public/pages/Collections.tsx +12 -46
- package/src/serve/public/pages/DocView.tsx +14 -52
- package/src/serve/resident-admission.ts +36 -36
- package/src/serve/resident-background-work.ts +20 -2
- package/src/serve/resident-request.ts +11 -5
- package/src/serve/resident-runtime.ts +97 -61
- package/src/serve/resident-shutdown.ts +153 -0
- package/src/serve/routes/api.ts +3 -1
- package/src/serve/server.ts +47 -26
- package/src/store/migrations/028-vector-variants.ts +54 -0
- package/src/store/migrations/029-graph-reference-state.ts +77 -0
- package/src/store/migrations/index.ts +4 -0
- package/src/store/sqlite/adapter.ts +251 -183
- package/src/store/sqlite/eligibility.ts +174 -0
- package/src/store/sqlite/graph-edge-application.ts +66 -0
- package/src/store/sqlite/graph-reference-state.ts +194 -0
- package/src/store/sqlite/legacy-vector-ownership.ts +79 -0
- package/src/store/types.ts +80 -12
- package/src/store/vector/eligibility.ts +36 -0
- package/src/store/vector/freshness.ts +33 -6
- package/src/store/vector/lazy.ts +81 -0
- package/src/store/vector/sqlite-vec.ts +106 -54
- package/src/store/vector/stats.ts +14 -3
- package/src/store/vector/types.ts +35 -2
- package/src/store/vector/variant-search.ts +192 -0
- package/src/store/vector/variants.ts +451 -0
- package/vendor/converters/markitdown-ts/LICENSE +21 -0
- package/vendor/converters/markitdown-ts/dist/index.cjs +1180 -0
- package/vendor/converters/markitdown-ts/dist/index.d.cts +46 -0
- package/vendor/converters/markitdown-ts/dist/index.d.mts +46 -0
- package/vendor/converters/markitdown-ts/dist/index.d.ts +46 -0
- package/vendor/converters/markitdown-ts/dist/index.mjs +1152 -0
- package/vendor/converters/markitdown-ts/package.json +77 -0
- package/vendor/converters/officeparser/LICENSE +21 -0
- package/vendor/converters/officeparser/dist/OfficeConverter.d.ts +47 -0
- package/vendor/converters/officeparser/dist/OfficeConverter.js +76 -0
- package/vendor/converters/officeparser/dist/OfficeGenerator.d.ts +23 -0
- package/vendor/converters/officeparser/dist/OfficeGenerator.js +73 -0
- package/vendor/converters/officeparser/dist/OfficeParser.d.ts +106 -0
- package/vendor/converters/officeparser/dist/OfficeParser.js +332 -0
- package/vendor/converters/officeparser/dist/cli.d.ts +28 -0
- package/vendor/converters/officeparser/dist/cli.js +381 -0
- package/vendor/converters/officeparser/dist/defaults.d.ts +41 -0
- package/vendor/converters/officeparser/dist/defaults.js +218 -0
- package/vendor/converters/officeparser/dist/generators/BaseGenerator.d.ts +107 -0
- package/vendor/converters/officeparser/dist/generators/BaseGenerator.js +248 -0
- package/vendor/converters/officeparser/dist/generators/ChunkingGenerator.d.ts +82 -0
- package/vendor/converters/officeparser/dist/generators/ChunkingGenerator.js +797 -0
- package/vendor/converters/officeparser/dist/generators/CsvGenerator.d.ts +38 -0
- package/vendor/converters/officeparser/dist/generators/CsvGenerator.js +245 -0
- package/vendor/converters/officeparser/dist/generators/EpubGenerator.d.ts +43 -0
- package/vendor/converters/officeparser/dist/generators/EpubGenerator.js +315 -0
- package/vendor/converters/officeparser/dist/generators/HtmlGenerator.d.ts +59 -0
- package/vendor/converters/officeparser/dist/generators/HtmlGenerator.js +1942 -0
- package/vendor/converters/officeparser/dist/generators/MarkdownGenerator.d.ts +96 -0
- package/vendor/converters/officeparser/dist/generators/MarkdownGenerator.js +1175 -0
- package/vendor/converters/officeparser/dist/generators/PdfGenerator.d.ts +22 -0
- package/vendor/converters/officeparser/dist/generators/PdfGenerator.js +194 -0
- package/vendor/converters/officeparser/dist/generators/RtfGenerator.d.ts +29 -0
- package/vendor/converters/officeparser/dist/generators/RtfGenerator.js +316 -0
- package/vendor/converters/officeparser/dist/generators/TextGenerator.d.ts +13 -0
- package/vendor/converters/officeparser/dist/generators/TextGenerator.js +201 -0
- package/vendor/converters/officeparser/dist/index.d.ts +60 -0
- package/vendor/converters/officeparser/dist/index.js +72 -0
- package/vendor/converters/officeparser/dist/index.mjs +18 -0
- package/vendor/converters/officeparser/dist/officeparser.browser.d.ts +2621 -0
- package/vendor/converters/officeparser/dist/officeparser.browser.iife.js +1336 -0
- package/vendor/converters/officeparser/dist/officeparser.browser.mjs +1335 -0
- package/vendor/converters/officeparser/dist/officeparser.browser.slim.d.ts +2621 -0
- package/vendor/converters/officeparser/dist/officeparser.browser.slim.iife.js +1336 -0
- package/vendor/converters/officeparser/dist/officeparser.browser.slim.mjs +1335 -0
- package/vendor/converters/officeparser/dist/parsers/CsvParser.d.ts +9 -0
- package/vendor/converters/officeparser/dist/parsers/CsvParser.js +115 -0
- package/vendor/converters/officeparser/dist/parsers/EpubParser.d.ts +8 -0
- package/vendor/converters/officeparser/dist/parsers/EpubParser.js +217 -0
- package/vendor/converters/officeparser/dist/parsers/ExcelParser.d.ts +32 -0
- package/vendor/converters/officeparser/dist/parsers/ExcelParser.js +736 -0
- package/vendor/converters/officeparser/dist/parsers/HtmlParser.d.ts +2 -0
- package/vendor/converters/officeparser/dist/parsers/HtmlParser.js +1287 -0
- package/vendor/converters/officeparser/dist/parsers/MarkdownParser.d.ts +2 -0
- package/vendor/converters/officeparser/dist/parsers/MarkdownParser.js +1272 -0
- package/vendor/converters/officeparser/dist/parsers/OpenOfficeParser.d.ts +31 -0
- package/vendor/converters/officeparser/dist/parsers/OpenOfficeParser.js +1819 -0
- package/vendor/converters/officeparser/dist/parsers/PdfParser.d.ts +67 -0
- package/vendor/converters/officeparser/dist/parsers/PdfParser.js +848 -0
- package/vendor/converters/officeparser/dist/parsers/PowerPointParser.d.ts +32 -0
- package/vendor/converters/officeparser/dist/parsers/PowerPointParser.js +950 -0
- package/vendor/converters/officeparser/dist/parsers/RtfParser.d.ts +187 -0
- package/vendor/converters/officeparser/dist/parsers/RtfParser.js +1801 -0
- package/vendor/converters/officeparser/dist/parsers/WordParser.d.ts +79 -0
- package/vendor/converters/officeparser/dist/parsers/WordParser.js +1177 -0
- package/vendor/converters/officeparser/dist/sbom.cdx.json +1763 -0
- package/vendor/converters/officeparser/dist/types.d.ts +2507 -0
- package/vendor/converters/officeparser/dist/types.js +107 -0
- package/vendor/converters/officeparser/dist/utils/astUtils.d.ts +16 -0
- package/vendor/converters/officeparser/dist/utils/astUtils.js +33 -0
- package/vendor/converters/officeparser/dist/utils/chartUtils.d.ts +6 -0
- package/vendor/converters/officeparser/dist/utils/chartUtils.js +257 -0
- package/vendor/converters/officeparser/dist/utils/configUtils.d.ts +44 -0
- package/vendor/converters/officeparser/dist/utils/configUtils.js +315 -0
- package/vendor/converters/officeparser/dist/utils/dateUtils.d.ts +17 -0
- package/vendor/converters/officeparser/dist/utils/dateUtils.js +69 -0
- package/vendor/converters/officeparser/dist/utils/envUtils.d.ts +29 -0
- package/vendor/converters/officeparser/dist/utils/envUtils.js +152 -0
- package/vendor/converters/officeparser/dist/utils/errorUtils.d.ts +72 -0
- package/vendor/converters/officeparser/dist/utils/errorUtils.js +245 -0
- package/vendor/converters/officeparser/dist/utils/imageUtils.d.ts +66 -0
- package/vendor/converters/officeparser/dist/utils/imageUtils.js +133 -0
- package/vendor/converters/officeparser/dist/utils/mathUtils.d.ts +42 -0
- package/vendor/converters/officeparser/dist/utils/mathUtils.js +385 -0
- package/vendor/converters/officeparser/dist/utils/moduleLoader.d.ts +18 -0
- package/vendor/converters/officeparser/dist/utils/moduleLoader.js +106 -0
- package/vendor/converters/officeparser/dist/utils/ocrUtils.d.ts +42 -0
- package/vendor/converters/officeparser/dist/utils/ocrUtils.js +428 -0
- package/vendor/converters/officeparser/dist/utils/sanitize.d.ts +148 -0
- package/vendor/converters/officeparser/dist/utils/sanitize.js +344 -0
- package/vendor/converters/officeparser/dist/utils/sheetUtils.d.ts +7 -0
- package/vendor/converters/officeparser/dist/utils/sheetUtils.js +35 -0
- package/vendor/converters/officeparser/dist/utils/styleMapper.d.ts +36 -0
- package/vendor/converters/officeparser/dist/utils/styleMapper.js +224 -0
- package/vendor/converters/officeparser/dist/utils/xmlUtils.d.ts +163 -0
- package/vendor/converters/officeparser/dist/utils/xmlUtils.js +461 -0
- package/vendor/converters/officeparser/dist/utils/zipUtils.d.ts +134 -0
- package/vendor/converters/officeparser/dist/utils/zipUtils.js +337 -0
- package/vendor/converters/officeparser/package.json +147 -0
- package/vendor/converters/upstream-manifest.json +124 -0
- package/vendor/dependency-fixes/README.md +77 -0
- package/vendor/dependency-fixes/vendor-converters.py +83 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.46.0.zip +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.46.0.zip.sha256 +0 -1
- package/browser-extension/dist/chunk-627emwpj.js +0 -75
- /package/browser-extension/dist/{chunk-ydfx5d7p.css → chunk-z74y8n8c.css} +0 -0
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
Loader2Icon,
|
|
16
16
|
TimerIcon,
|
|
17
17
|
} from "lucide-react";
|
|
18
|
-
import {
|
|
18
|
+
import { useEffect, useState } from "react";
|
|
19
19
|
|
|
20
20
|
import { apiFetch } from "../hooks/use-api";
|
|
21
21
|
import { cn } from "../lib/utils";
|
|
@@ -103,77 +103,63 @@ export function IndexingProgress({
|
|
|
103
103
|
const [status, setStatus] = useState<JobStatus | null>(null);
|
|
104
104
|
const [elapsed, setElapsed] = useState(0);
|
|
105
105
|
const [fetchError, setFetchError] = useState<string | null>(null);
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
// Each effect owns its requests and timers, including across quick restarts.
|
|
108
|
+
const controller = new AbortController();
|
|
109
|
+
let pollTimeout: ReturnType<typeof setTimeout> | undefined;
|
|
110
|
+
const startTime = Date.now();
|
|
111
|
+
const elapsedInterval = setInterval(() => {
|
|
112
|
+
setElapsed(Date.now() - startTime);
|
|
113
|
+
}, 1000);
|
|
111
114
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
`/api/jobs/${encodeURIComponent(jobId)}`
|
|
116
|
-
);
|
|
115
|
+
setStatus(null);
|
|
116
|
+
setFetchError(null);
|
|
117
|
+
setElapsed(0);
|
|
117
118
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
119
|
+
const poll = async () => {
|
|
120
|
+
if (controller.signal.aborted) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const { data, error: err } = await apiFetch<JobStatus>(
|
|
124
|
+
`/api/jobs/${encodeURIComponent(jobId)}`,
|
|
125
|
+
{ signal: controller.signal }
|
|
126
|
+
);
|
|
122
127
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
128
|
+
// Abort may race with response parsing; never publish or reschedule it.
|
|
129
|
+
if (controller.signal.aborted) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (err || !data) {
|
|
133
|
+
setFetchError(err ?? "No response from server");
|
|
134
|
+
clearInterval(elapsedInterval);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
127
137
|
|
|
128
|
-
|
|
129
|
-
|
|
138
|
+
setStatus(data);
|
|
139
|
+
setFetchError(null);
|
|
130
140
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
141
|
+
if (data.status === "running") {
|
|
142
|
+
pollTimeout = setTimeout(() => {
|
|
143
|
+
void poll();
|
|
144
|
+
}, 1000);
|
|
145
|
+
return;
|
|
136
146
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
147
|
+
|
|
148
|
+
clearInterval(elapsedInterval);
|
|
149
|
+
if (data.status === "completed" && data.result) {
|
|
150
|
+
onComplete?.(data.result);
|
|
151
|
+
} else if (data.status === "failed") {
|
|
140
152
|
onError?.(data.error ?? "Unknown error");
|
|
141
153
|
}
|
|
142
|
-
}
|
|
143
|
-
// Continue polling
|
|
144
|
-
pollTimeoutRef.current = setTimeout(() => {
|
|
145
|
-
void poll();
|
|
146
|
-
}, 1000);
|
|
147
|
-
}
|
|
148
|
-
}, [jobId, onComplete, onError]);
|
|
149
|
-
|
|
150
|
-
// Start polling on mount
|
|
151
|
-
useEffect(() => {
|
|
152
|
-
completedRef.current = false;
|
|
154
|
+
};
|
|
153
155
|
void poll();
|
|
154
156
|
|
|
155
|
-
// Track elapsed time
|
|
156
|
-
const startTime = Date.now();
|
|
157
|
-
elapsedIntervalRef.current = setInterval(() => {
|
|
158
|
-
setElapsed(Date.now() - startTime);
|
|
159
|
-
}, 1000);
|
|
160
|
-
|
|
161
157
|
return () => {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
if (elapsedIntervalRef.current) {
|
|
166
|
-
clearInterval(elapsedIntervalRef.current);
|
|
167
|
-
}
|
|
158
|
+
controller.abort();
|
|
159
|
+
clearTimeout(pollTimeout);
|
|
160
|
+
clearInterval(elapsedInterval);
|
|
168
161
|
};
|
|
169
|
-
}, [
|
|
170
|
-
|
|
171
|
-
// Stop elapsed timer when complete
|
|
172
|
-
useEffect(() => {
|
|
173
|
-
if (status?.status !== "running" && elapsedIntervalRef.current) {
|
|
174
|
-
clearInterval(elapsedIntervalRef.current);
|
|
175
|
-
}
|
|
176
|
-
}, [status?.status]);
|
|
162
|
+
}, [jobId, onComplete, onError]);
|
|
177
163
|
|
|
178
164
|
// Loading/error before first status
|
|
179
165
|
if (!status && !fetchError) {
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { Loader2Icon } from "lucide-react";
|
|
2
|
+
import { useRef, useState } from "react";
|
|
3
|
+
|
|
4
|
+
import type { PublishVisibility } from "../../../publish/artifact";
|
|
5
|
+
|
|
6
|
+
import { apiFetch } from "../hooks/use-api";
|
|
7
|
+
import {
|
|
8
|
+
buildPublishExportRequest,
|
|
9
|
+
downloadPublishArtifactFile,
|
|
10
|
+
PUBLISH_ACCESS_OPTIONS,
|
|
11
|
+
type PublishExportResponse,
|
|
12
|
+
} from "../lib/publish-export";
|
|
13
|
+
import { Button } from "./ui/button";
|
|
14
|
+
import {
|
|
15
|
+
Dialog,
|
|
16
|
+
DialogContent,
|
|
17
|
+
DialogDescription,
|
|
18
|
+
DialogFooter,
|
|
19
|
+
DialogHeader,
|
|
20
|
+
DialogTitle,
|
|
21
|
+
} from "./ui/dialog";
|
|
22
|
+
import { Input } from "./ui/input";
|
|
23
|
+
|
|
24
|
+
interface PublishExportDialogProps {
|
|
25
|
+
onClose: () => void;
|
|
26
|
+
target: string;
|
|
27
|
+
title: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Mount a fresh dialog for each export so access and secrets never carry over. */
|
|
31
|
+
export function PublishExportDialog({
|
|
32
|
+
onClose,
|
|
33
|
+
target,
|
|
34
|
+
title,
|
|
35
|
+
}: PublishExportDialogProps) {
|
|
36
|
+
const [visibility, setVisibility] = useState<PublishVisibility | null>(null);
|
|
37
|
+
const [passphrase, setPassphrase] = useState("");
|
|
38
|
+
const [passphraseConfirmation, setPassphraseConfirmation] = useState("");
|
|
39
|
+
const [audienceConfirmed, setAudienceConfirmed] = useState(false);
|
|
40
|
+
const [error, setError] = useState<string | null>(null);
|
|
41
|
+
const [busy, setBusy] = useState(false);
|
|
42
|
+
const exporting = useRef(false);
|
|
43
|
+
const [returnFocus] = useState(() => {
|
|
44
|
+
const active = document.activeElement;
|
|
45
|
+
const menuTriggerId = active
|
|
46
|
+
?.closest('[role="menu"]')
|
|
47
|
+
?.getAttribute("aria-labelledby");
|
|
48
|
+
const trigger = menuTriggerId
|
|
49
|
+
? document.getElementById(menuTriggerId)
|
|
50
|
+
: active;
|
|
51
|
+
return trigger instanceof HTMLElement ? trigger : null;
|
|
52
|
+
});
|
|
53
|
+
const selected = PUBLISH_ACCESS_OPTIONS.find(
|
|
54
|
+
({ value }) => value === visibility
|
|
55
|
+
);
|
|
56
|
+
const encryptionReady =
|
|
57
|
+
visibility !== "encrypted" ||
|
|
58
|
+
(passphrase.trim().length > 0 && passphrase === passphraseConfirmation);
|
|
59
|
+
|
|
60
|
+
const handleExport = async () => {
|
|
61
|
+
if (exporting.current) return;
|
|
62
|
+
setError(null);
|
|
63
|
+
try {
|
|
64
|
+
const body = buildPublishExportRequest({
|
|
65
|
+
target,
|
|
66
|
+
visibility,
|
|
67
|
+
passphrase,
|
|
68
|
+
passphraseConfirmation,
|
|
69
|
+
audienceConfirmed,
|
|
70
|
+
});
|
|
71
|
+
exporting.current = true;
|
|
72
|
+
setBusy(true);
|
|
73
|
+
const result = await apiFetch<PublishExportResponse>(
|
|
74
|
+
"/api/publish/export",
|
|
75
|
+
{
|
|
76
|
+
method: "POST",
|
|
77
|
+
body: JSON.stringify(body),
|
|
78
|
+
}
|
|
79
|
+
);
|
|
80
|
+
if (result.error || !result.data) {
|
|
81
|
+
setError(
|
|
82
|
+
result.error ?? "The export did not return an artifact. Try again."
|
|
83
|
+
);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
// Refuse a mismatched response rather than downloading a broader artifact.
|
|
87
|
+
if (
|
|
88
|
+
result.data.artifact.spaces.length !== 1 ||
|
|
89
|
+
result.data.artifact.spaces[0]?.visibility !== visibility ||
|
|
90
|
+
result.data.artifact.version !== (visibility === "encrypted" ? 2 : 1)
|
|
91
|
+
) {
|
|
92
|
+
setError(
|
|
93
|
+
"The returned artifact does not match the reviewed access. Nothing was downloaded."
|
|
94
|
+
);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
downloadPublishArtifactFile(result.data);
|
|
98
|
+
setPassphrase("");
|
|
99
|
+
setPassphraseConfirmation("");
|
|
100
|
+
onClose();
|
|
101
|
+
} catch (cause) {
|
|
102
|
+
setError(
|
|
103
|
+
cause instanceof Error ? cause.message : "Failed to export. Try again."
|
|
104
|
+
);
|
|
105
|
+
} finally {
|
|
106
|
+
exporting.current = false;
|
|
107
|
+
setBusy(false);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<Dialog
|
|
113
|
+
open
|
|
114
|
+
onOpenChange={(open) => {
|
|
115
|
+
if (!open && !exporting.current) onClose();
|
|
116
|
+
}}
|
|
117
|
+
>
|
|
118
|
+
<DialogContent
|
|
119
|
+
className="publish-export-dialog bg-card"
|
|
120
|
+
onCloseAutoFocus={(event) => {
|
|
121
|
+
if (returnFocus?.isConnected) {
|
|
122
|
+
event.preventDefault();
|
|
123
|
+
returnFocus.focus();
|
|
124
|
+
}
|
|
125
|
+
}}
|
|
126
|
+
showCloseButton={!busy}
|
|
127
|
+
>
|
|
128
|
+
<DialogHeader className="publish-export-dialog-header">
|
|
129
|
+
<DialogTitle>Export for gno.sh</DialogTitle>
|
|
130
|
+
<DialogDescription>
|
|
131
|
+
Review who can read “{title}”. This downloads a local file; upload
|
|
132
|
+
and publish it separately in Studio.
|
|
133
|
+
</DialogDescription>
|
|
134
|
+
</DialogHeader>
|
|
135
|
+
<div className="publish-export-dialog-body">
|
|
136
|
+
<fieldset className="space-y-2" disabled={busy}>
|
|
137
|
+
<legend className="mb-2 font-medium text-sm">
|
|
138
|
+
Who can read this?
|
|
139
|
+
</legend>
|
|
140
|
+
{PUBLISH_ACCESS_OPTIONS.map((option) => (
|
|
141
|
+
<label
|
|
142
|
+
className="flex cursor-pointer items-start gap-3 rounded-md border border-border p-3 transition-colors hover:bg-muted/40 has-[:checked]:border-primary has-[:checked]:bg-primary/10 focus-within:ring-2 focus-within:ring-primary/50"
|
|
143
|
+
key={option.value}
|
|
144
|
+
>
|
|
145
|
+
<input
|
|
146
|
+
checked={visibility === option.value}
|
|
147
|
+
className="mt-1 accent-primary"
|
|
148
|
+
name="publish-access"
|
|
149
|
+
onChange={() => {
|
|
150
|
+
setVisibility(option.value);
|
|
151
|
+
setAudienceConfirmed(false);
|
|
152
|
+
setPassphrase("");
|
|
153
|
+
setPassphraseConfirmation("");
|
|
154
|
+
setError(null);
|
|
155
|
+
}}
|
|
156
|
+
type="radio"
|
|
157
|
+
value={option.value}
|
|
158
|
+
/>
|
|
159
|
+
<span className="min-w-0 space-y-1">
|
|
160
|
+
<span className="block font-medium text-sm">
|
|
161
|
+
{option.label}
|
|
162
|
+
</span>
|
|
163
|
+
<span className="block text-muted-foreground text-sm">
|
|
164
|
+
{option.audience}
|
|
165
|
+
</span>
|
|
166
|
+
<span className="block font-mono text-muted-foreground text-xs">
|
|
167
|
+
{option.availability}
|
|
168
|
+
</span>
|
|
169
|
+
</span>
|
|
170
|
+
</label>
|
|
171
|
+
))}
|
|
172
|
+
</fieldset>
|
|
173
|
+
<p className="text-muted-foreground text-xs">
|
|
174
|
+
Local GNO cannot check your hosted account. Studio checks plan
|
|
175
|
+
availability and Egress Policy before publishing. See{" "}
|
|
176
|
+
<a
|
|
177
|
+
className="text-primary underline"
|
|
178
|
+
href="https://gno.sh/pricing"
|
|
179
|
+
rel="noopener noreferrer"
|
|
180
|
+
target="_blank"
|
|
181
|
+
>
|
|
182
|
+
current plans
|
|
183
|
+
</a>
|
|
184
|
+
.
|
|
185
|
+
</p>
|
|
186
|
+
{visibility === "encrypted" && (
|
|
187
|
+
<fieldset className="space-y-3" disabled={busy}>
|
|
188
|
+
<legend className="mb-2 font-medium text-sm">
|
|
189
|
+
Local encryption
|
|
190
|
+
</legend>
|
|
191
|
+
<p className="text-muted-foreground text-sm">
|
|
192
|
+
Your local GNO server encrypts the notes and bundled assets. The
|
|
193
|
+
passphrase is never sent to gno.sh or included in the downloaded
|
|
194
|
+
file. Keep it safe: lost passphrases cannot be recovered.
|
|
195
|
+
Switching into or out of encrypted sharing requires a new local
|
|
196
|
+
export.
|
|
197
|
+
</p>
|
|
198
|
+
<label className="block space-y-1 text-sm">
|
|
199
|
+
<span>Passphrase</span>
|
|
200
|
+
<Input
|
|
201
|
+
autoComplete="new-password"
|
|
202
|
+
onChange={(event) => setPassphrase(event.target.value)}
|
|
203
|
+
type="password"
|
|
204
|
+
value={passphrase}
|
|
205
|
+
/>
|
|
206
|
+
</label>
|
|
207
|
+
<label className="block space-y-1 text-sm">
|
|
208
|
+
<span>Confirm passphrase</span>
|
|
209
|
+
<Input
|
|
210
|
+
autoComplete="new-password"
|
|
211
|
+
onChange={(event) =>
|
|
212
|
+
setPassphraseConfirmation(event.target.value)
|
|
213
|
+
}
|
|
214
|
+
type="password"
|
|
215
|
+
value={passphraseConfirmation}
|
|
216
|
+
/>
|
|
217
|
+
</label>
|
|
218
|
+
{passphraseConfirmation &&
|
|
219
|
+
passphrase !== passphraseConfirmation && (
|
|
220
|
+
<p className="text-destructive text-sm" role="status">
|
|
221
|
+
The passphrases do not match.
|
|
222
|
+
</p>
|
|
223
|
+
)}
|
|
224
|
+
</fieldset>
|
|
225
|
+
)}
|
|
226
|
+
{selected && (
|
|
227
|
+
<label className="flex cursor-pointer items-start gap-2 text-sm">
|
|
228
|
+
<input
|
|
229
|
+
checked={audienceConfirmed}
|
|
230
|
+
className="mt-1 accent-primary"
|
|
231
|
+
disabled={busy}
|
|
232
|
+
onChange={(event) => setAudienceConfirmed(event.target.checked)}
|
|
233
|
+
type="checkbox"
|
|
234
|
+
/>
|
|
235
|
+
<span>
|
|
236
|
+
I reviewed the {selected.label.toLowerCase()} audience and want
|
|
237
|
+
to export with this access.
|
|
238
|
+
</span>
|
|
239
|
+
</label>
|
|
240
|
+
)}
|
|
241
|
+
{error && (
|
|
242
|
+
<p className="text-destructive text-sm" role="alert">
|
|
243
|
+
{error}
|
|
244
|
+
</p>
|
|
245
|
+
)}
|
|
246
|
+
</div>
|
|
247
|
+
<DialogFooter className="publish-export-dialog-footer">
|
|
248
|
+
<Button disabled={busy} onClick={onClose} variant="outline">
|
|
249
|
+
Cancel
|
|
250
|
+
</Button>
|
|
251
|
+
<Button
|
|
252
|
+
disabled={
|
|
253
|
+
busy || !selected || !audienceConfirmed || !encryptionReady
|
|
254
|
+
}
|
|
255
|
+
onClick={() => {
|
|
256
|
+
void handleExport();
|
|
257
|
+
}}
|
|
258
|
+
>
|
|
259
|
+
{busy && <Loader2Icon className="mr-2 size-4 animate-spin" />}
|
|
260
|
+
{busy ? "Exporting…" : "Download export"}
|
|
261
|
+
</Button>
|
|
262
|
+
</DialogFooter>
|
|
263
|
+
</DialogContent>
|
|
264
|
+
</Dialog>
|
|
265
|
+
);
|
|
266
|
+
}
|