paseo-plugin-genui 0.0.0-stage → 0.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/LICENSE +202 -0
- package/OVERVIEW.md +21 -0
- package/README.md +77 -2
- package/assets/fonts/Excalifont-OFL.txt +109 -0
- package/assets/fonts/Excalifont-Regular-latin.woff2 +0 -0
- package/assets/icon.png +0 -0
- package/assets/icon.svg +1 -0
- package/assets/screenshot-chat.png +0 -0
- package/assets/screenshot-gallery.png +0 -0
- package/assets/screenshot-playground.png +0 -0
- package/client/components/content.tsx +468 -0
- package/client/components/data.tsx +967 -0
- package/client/components/dev.tsx +998 -0
- package/client/components/diagram.tsx +513 -0
- package/client/components/inputs.tsx +994 -0
- package/client/components/layout.tsx +239 -0
- package/client/components/media.tsx +428 -0
- package/client/components/project.tsx +641 -0
- package/client/components/widgets.tsx +836 -0
- package/client/gallery.tsx +449 -0
- package/client/live-ui.tsx +525 -0
- package/client/loaders.ts +342 -0
- package/client/markdown.tsx +172 -0
- package/client/node-renderer.tsx +149 -0
- package/client/render.tsx +186 -0
- package/client/runtime.ts +103 -0
- package/client/settings.tsx +57 -0
- package/client/web.ts +186 -0
- package/gallery/Acceptance-baseline.ui +1 -0
- package/gallery/Acceptance-checklist.ui +14 -0
- package/gallery/Acceptance-evidence.ui +9 -0
- package/gallery/Acceptance.ui +1 -0
- package/gallery/Badge.ui +3 -0
- package/gallery/Benchmark-context.ui +8 -0
- package/gallery/Benchmark-hyperfine.ui +1 -0
- package/gallery/Benchmark-runs-list.ui +9 -0
- package/gallery/Benchmark-runs.ui +1 -0
- package/gallery/Benchmark-vitest.ui +1 -0
- package/gallery/Benchmark.ui +9 -0
- package/gallery/Board-bb-tasks.ui +1 -0
- package/gallery/Board-gh-issues.ui +1 -0
- package/gallery/Board-gh-prs.ui +1 -0
- package/gallery/Board.ui +12 -0
- package/gallery/Button-destructive.ui +4 -0
- package/gallery/Button.ui +5 -0
- package/gallery/Calendar.ui +15 -0
- package/gallery/CalendarHeatmap-short.ui +11 -0
- package/gallery/CalendarHeatmap.ui +4 -0
- package/gallery/Callout.ui +4 -0
- package/gallery/Card.ui +4 -0
- package/gallery/Carousel.ui +8 -0
- package/gallery/Changes-paths.ui +1 -0
- package/gallery/Changes-uncommitted.ui +1 -0
- package/gallery/Changes.ui +1 -0
- package/gallery/Chart-area.ui +3 -0
- package/gallery/Chart-funnel.ui +3 -0
- package/gallery/Chart-heatmap.ui +11 -0
- package/gallery/Chart-pie.ui +3 -0
- package/gallery/Chart-radar.ui +3 -0
- package/gallery/Chart-scatter.ui +4 -0
- package/gallery/Chart-stacked.ui +5 -0
- package/gallery/Chart.ui +3 -0
- package/gallery/Checklist.ui +6 -0
- package/gallery/Choice.ui +8 -0
- package/gallery/Code.ui +3 -0
- package/gallery/Commits-limit.ui +1 -0
- package/gallery/Commits.ui +1 -0
- package/gallery/Compare-conditions.ui +4 -0
- package/gallery/Compare-three.ui +7 -0
- package/gallery/Compare.ui +4 -0
- package/gallery/Coverage-go.ui +1 -0
- package/gallery/Coverage-lcov.ui +1 -0
- package/gallery/Coverage-python.ui +1 -0
- package/gallery/Coverage.ui +1 -0
- package/gallery/DateInput.ui +5 -0
- package/gallery/Decision-percent.ui +10 -0
- package/gallery/Decision.ui +12 -0
- package/gallery/Dependencies-audit.ui +1 -0
- package/gallery/Dependencies-both.ui +1 -0
- package/gallery/Dependencies-list.ui +7 -0
- package/gallery/Dependencies-pip.ui +1 -0
- package/gallery/Dependencies-pnpm.ui +1 -0
- package/gallery/Dependencies.ui +1 -0
- package/gallery/Details.ui +4 -0
- package/gallery/Diagram-flow.ui +16 -0
- package/gallery/Diagram-sequence.ui +15 -0
- package/gallery/Diagram.ui +16 -0
- package/gallery/Diff.ui +3 -0
- package/gallery/Divider.ui +5 -0
- package/gallery/EditableTable.ui +9 -0
- package/gallery/Entity-list.ui +6 -0
- package/gallery/Entity.ui +8 -0
- package/gallery/Environments.ui +1 -0
- package/gallery/Estimate-points.ui +6 -0
- package/gallery/Estimate.ui +8 -0
- package/gallery/File.ui +4 -0
- package/gallery/Findings-biome.ui +1 -0
- package/gallery/Findings-eslint.ui +1 -0
- package/gallery/Findings-oxlint.ui +1 -0
- package/gallery/Findings-review.ui +1 -0
- package/gallery/Findings-ruff.ui +1 -0
- package/gallery/Findings-sarif.ui +1 -0
- package/gallery/Findings-ship.ui +11 -0
- package/gallery/Findings-tsc.ui +1 -0
- package/gallery/Findings.ui +7 -0
- package/gallery/Flashcards.ui +3 -0
- package/gallery/FollowUps.ui +4 -0
- package/gallery/Form.ui +8 -0
- package/gallery/Gallery-links.ui +8 -0
- package/gallery/Gallery-strip.ui +14 -0
- package/gallery/Gallery.ui +10 -0
- package/gallery/Gauge.ui +7 -0
- package/gallery/Grid.ui +8 -0
- package/gallery/Hotspots-photo.ui +8 -0
- package/gallery/Hotspots.ui +11 -0
- package/gallery/Icon.ui +5 -0
- package/gallery/Image.ui +4 -0
- package/gallery/ImageSearch-aspects.ui +6 -0
- package/gallery/ImageSearch.ui +4 -0
- package/gallery/Input.ui +5 -0
- package/gallery/Json-api.ui +1 -0
- package/gallery/Json.ui +8 -0
- package/gallery/KeyValue.ui +3 -0
- package/gallery/List-dots.ui +8 -0
- package/gallery/List.ui +8 -0
- package/gallery/ListEditor.ui +5 -0
- package/gallery/Logs-jsonl.ui +1 -0
- package/gallery/Logs-lines.ui +11 -0
- package/gallery/Logs-python.ui +1 -0
- package/gallery/Logs.ui +1 -0
- package/gallery/Map-city.ui +8 -0
- package/gallery/Map.ui +8 -0
- package/gallery/Metric.ui +3 -0
- package/gallery/OptionCards-multi.ui +10 -0
- package/gallery/OptionCards.ui +10 -0
- package/gallery/Pipeline-checks.ui +1 -0
- package/gallery/Pipeline-passed.ui +1 -0
- package/gallery/Pipeline-running.ui +1 -0
- package/gallery/Pipeline-stages.ui +13 -0
- package/gallery/Pipeline.ui +1 -0
- package/gallery/Plan.ui +9 -0
- package/gallery/Progress.ui +4 -0
- package/gallery/Provenance-deploy.ui +6 -0
- package/gallery/Provenance-no-receipt.ui +1 -0
- package/gallery/Provenance.ui +1 -0
- package/gallery/PullRequest.ui +1 -0
- package/gallery/Quiz.ui +6 -0
- package/gallery/Radio.ui +5 -0
- package/gallery/Rating.ui +5 -0
- package/gallery/Roadmap-sprint.ui +8 -0
- package/gallery/Roadmap-year.ui +9 -0
- package/gallery/Roadmap.ui +12 -0
- package/gallery/Row.ui +3 -0
- package/gallery/SegmentedBar-status.ui +8 -0
- package/gallery/SegmentedBar.ui +8 -0
- package/gallery/Select.ui +5 -0
- package/gallery/Slider.ui +7 -0
- package/gallery/Snippet.ui +3 -0
- package/gallery/Sources-chips.ui +4 -0
- package/gallery/Sources.ui +8 -0
- package/gallery/Sparkline.ui +6 -0
- package/gallery/Stack.ui +7 -0
- package/gallery/StackTrace-aggregate.ui +1 -0
- package/gallery/StackTrace-browser.ui +1 -0
- package/gallery/StackTrace-go.ui +1 -0
- package/gallery/StackTrace-java.ui +1 -0
- package/gallery/StackTrace-python.ui +1 -0
- package/gallery/StackTrace-rust.ui +1 -0
- package/gallery/StackTrace.ui +1 -0
- package/gallery/Status.ui +9 -0
- package/gallery/Stepper.ui +5 -0
- package/gallery/Steps.ui +8 -0
- package/gallery/Swatches-long-names.ui +4 -0
- package/gallery/Swatches-picker.ui +5 -0
- package/gallery/Swatches.ui +10 -0
- package/gallery/Table.ui +7 -0
- package/gallery/Tabs.ui +8 -0
- package/gallery/Tests-junit.ui +1 -0
- package/gallery/Tests-vitest-junit.ui +1 -0
- package/gallery/Tests-vitest.ui +1 -0
- package/gallery/Tests.ui +9 -0
- package/gallery/Text.ui +4 -0
- package/gallery/TextArea.ui +5 -0
- package/gallery/Threads-ids.ui +1 -0
- package/gallery/Threads.ui +1 -0
- package/gallery/Timeline.ui +8 -0
- package/gallery/Timer.ui +4 -0
- package/gallery/Toggle.ui +5 -0
- package/gallery/Transcript-text.ui +1 -0
- package/gallery/Transcript-untimed.ui +1 -0
- package/gallery/Transcript-whisper.ui +1 -0
- package/gallery/Transcript.ui +1 -0
- package/gallery/Tree.ui +3 -0
- package/gallery/Treemap-du.ui +1 -0
- package/gallery/Treemap-esbuild.ui +1 -0
- package/gallery/Treemap-tree.ui +7 -0
- package/gallery/Treemap.ui +18 -0
- package/gallery/Walkthrough.ui +9 -0
- package/gallery/Waterfall-har.ui +1 -0
- package/gallery/Waterfall-trace.ui +1 -0
- package/gallery/Waterfall.ui +11 -0
- package/gallery/data/acceptance/coil/viewport-lifecycle.json +25 -0
- package/gallery/data/acceptance/coil-gate.json +153 -0
- package/gallery/data/acceptance/orbit-baseline.json +163 -0
- package/gallery/data/acceptance/shots/gate-cell-opened.png +0 -0
- package/gallery/data/acceptance/shots/gate-light.png +0 -0
- package/gallery/data/acceptance/shots/gate-phone-dark.png +0 -0
- package/gallery/data/activity.json +1 -0
- package/gallery/data/bb-tasks-orb.json +420 -0
- package/gallery/data/biome.json +229 -0
- package/gallery/data/compare/status-after.png +0 -0
- package/gallery/data/compare/status-before.png +0 -0
- package/gallery/data/compare/status-dark-2x.png +0 -0
- package/gallery/data/compare/status-rerender.png +0 -0
- package/gallery/data/coverage-py.json +1 -0
- package/gallery/data/coverage-summary.json +1 -0
- package/gallery/data/coverage.out +23 -0
- package/gallery/data/deploys.json +1 -0
- package/gallery/data/device-runs.json +285 -0
- package/gallery/data/du-node-modules.txt +359 -0
- package/gallery/data/esbuild-meta.json +292 -0
- package/gallery/data/eslint.json +266 -0
- package/gallery/data/eslint.sarif +343 -0
- package/gallery/data/findings-review.json +76 -0
- package/gallery/data/gh-issues-nextjs.json +293 -0
- package/gallery/data/gh-pr-checks.json +272 -0
- package/gallery/data/gh-prs-nextjs.json +419 -0
- package/gallery/data/gh-run-failed.json +1 -0
- package/gallery/data/gh-run-passed.json +1 -0
- package/gallery/data/gh-run-running.json +1 -0
- package/gallery/data/github-release.json +57 -0
- package/gallery/data/hyperfine-startup.json +187 -0
- package/gallery/data/lcov.info +234 -0
- package/gallery/data/logs/media-sync.log +127 -0
- package/gallery/data/logs/thumbnailer.jsonl +17 -0
- package/gallery/data/logs/vitest-run.log +89 -0
- package/gallery/data/npm-audit.json +672 -0
- package/gallery/data/npm-outdated.json +179 -0
- package/gallery/data/oxlint.json +156 -0
- package/gallery/data/pip-outdated.json +68 -0
- package/gallery/data/pnpm-outdated.json +58 -0
- package/gallery/data/provenance/devicectl-install.json +35 -0
- package/gallery/data/provenance/install-timeout.log +4 -0
- package/gallery/data/provenance/iphone-install.json +72 -0
- package/gallery/data/provenance/iphone-no-receipt.json +57 -0
- package/gallery/data/provenance/live-home.jpg +0 -0
- package/gallery/data/provenance/live-probe.txt +10 -0
- package/gallery/data/provenance/xcodebuild-2811.log +19 -0
- package/gallery/data/provenance/xcodebuild-4712.log +19 -0
- package/gallery/data/pytest-junit.xml +64 -0
- package/gallery/data/python-org-trace.json +35 -0
- package/gallery/data/python-org.har +31 -0
- package/gallery/data/retry.ts +13 -0
- package/gallery/data/ruff.json +261 -0
- package/gallery/data/src/durations.py +63 -0
- package/gallery/data/src/json-logic.ts +156 -0
- package/gallery/data/src/mapMath.ts +135 -0
- package/gallery/data/src/semver/bump.go +73 -0
- package/gallery/data/traces/browser-error.json +5 -0
- package/gallery/data/traces/go/main.go +27 -0
- package/gallery/data/traces/go-panic.txt +16 -0
- package/gallery/data/traces/java-caused-by.txt +15 -0
- package/gallery/data/traces/node-aggregate.txt +30 -0
- package/gallery/data/traces/node-cause.txt +21 -0
- package/gallery/data/traces/py/deploy.json +5 -0
- package/gallery/data/traces/py/deploy.py +17 -0
- package/gallery/data/traces/py/settings.py +19 -0
- package/gallery/data/traces/python-chain.txt +30 -0
- package/gallery/data/traces/rust/main.rs +11 -0
- package/gallery/data/traces/rust-panic.txt +16 -0
- package/gallery/data/traces/shop/cart.mjs +19 -0
- package/gallery/data/traces/shop/pricing.mjs +12 -0
- package/gallery/data/traces/shop/status.mjs +9 -0
- package/gallery/data/traces/web/app.js +11 -0
- package/gallery/data/traces/web/index.html +1 -0
- package/gallery/data/traces/web/search.js +10 -0
- package/gallery/data/transcript/call-noisy.wav +0 -0
- package/gallery/data/transcript/call.json +362 -0
- package/gallery/data/transcript/call.wav +0 -0
- package/gallery/data/transcript/mlx-whisper-tiny.en-noisy.json +1 -0
- package/gallery/data/transcript/whisper-cpp-base.en-noisy.json +658 -0
- package/gallery/data/transcript/whisper-cpp-base.en.json +658 -0
- package/gallery/data/transcript/whisper-cpp-base.en.srt +16 -0
- package/gallery/data/transcript/whisper-cpp-tiny.en-noisy.json +619 -0
- package/gallery/data/tsc.txt +4 -0
- package/gallery/data/vitest-bench.json +119 -0
- package/gallery/data/vitest-junit.xml +133 -0
- package/gallery/data/vitest-report.json +612 -0
- package/gallery/images/bell.svg +10 -0
- package/gallery/images/bike.svg +47 -0
- package/gallery/images/footer-planet.webp +0 -0
- package/gallery/images/front-light.svg +13 -0
- package/gallery/images/golden-hour.webp +0 -0
- package/gallery/images/option-flowers.webp +0 -0
- package/gallery/images/option-hilltop.webp +0 -0
- package/gallery/images/option-meadow.webp +0 -0
- package/gallery/images/option-sky.webp +0 -0
- package/gallery/images/u-lock.svg +10 -0
- package/index.client.tsx +164 -0
- package/index.server.ts +216 -0
- package/package.json +50 -4
- package/paseo-plugin.json +14 -0
- package/server/data-source.ts +407 -0
- package/server/project.ts +981 -0
- package/server/web/address.ts +57 -0
- package/server/web/cache.ts +85 -0
- package/server/web/commons.ts +126 -0
- package/server/web/geocode.ts +40 -0
- package/server/web/image.ts +35 -0
- package/server/web/lookups.ts +248 -0
- package/server/web/net.ts +210 -0
- package/shared/core/acceptance.ts +744 -0
- package/shared/core/ast.ts +76 -0
- package/shared/core/benchmark.ts +743 -0
- package/shared/core/catalog.ts +733 -0
- package/shared/core/compare.ts +225 -0
- package/shared/core/coverage.ts +456 -0
- package/shared/core/data.ts +334 -0
- package/shared/core/decisions.ts +527 -0
- package/shared/core/dependencies.ts +604 -0
- package/shared/core/diagnostics.ts +24 -0
- package/shared/core/environments.ts +212 -0
- package/shared/core/evaluate.ts +737 -0
- package/shared/core/fields.ts +66 -0
- package/shared/core/findings.ts +1172 -0
- package/shared/core/form.ts +69 -0
- package/shared/core/index.ts +44 -0
- package/shared/core/junit.ts +252 -0
- package/shared/core/lexer.ts +275 -0
- package/shared/core/logs.ts +496 -0
- package/shared/core/markdown.ts +2374 -0
- package/shared/core/parser.ts +355 -0
- package/shared/core/perf-units.ts +228 -0
- package/shared/core/pipeline.ts +679 -0
- package/shared/core/project.ts +472 -0
- package/shared/core/provenance.ts +725 -0
- package/shared/core/stack-trace.ts +718 -0
- package/shared/core/streaming.ts +136 -0
- package/shared/core/test-results.ts +562 -0
- package/shared/core/thread-activity.ts +203 -0
- package/shared/core/tracking.ts +887 -0
- package/shared/core/transcript.ts +1364 -0
- package/shared/core/treemap.ts +446 -0
- package/shared/core/types.ts +70 -0
- package/shared/core/validate.ts +574 -0
- package/shared/core/waterfall.ts +536 -0
- package/shared/diagram/flowchart.ts +205 -0
- package/shared/diagram/layout.ts +440 -0
- package/shared/diagram/markdown.ts +135 -0
- package/shared/diagram/sequence.ts +122 -0
- package/shared/diagram/zoom.ts +114 -0
- package/shared/fallback.ts +120 -0
- package/shared/genui.test.ts +122 -0
- package/shared/instructions.ts +144 -0
- package/shared/logic/calendarMath.ts +130 -0
- package/shared/logic/chartMath.ts +289 -0
- package/shared/logic/choose-logic.ts +196 -0
- package/shared/logic/compare-logic.ts +270 -0
- package/shared/logic/content-model.ts +227 -0
- package/shared/logic/environments-logic.ts +53 -0
- package/shared/logic/explain-logic.ts +315 -0
- package/shared/logic/fieldLogic.ts +138 -0
- package/shared/logic/gallery-search.ts +71 -0
- package/shared/logic/icons.ts +322 -0
- package/shared/logic/json-logic.ts +153 -0
- package/shared/logic/mapMath.ts +133 -0
- package/shared/logic/project-logic.ts +39 -0
- package/shared/logic/references-model.ts +201 -0
- package/shared/logic/sketch.ts +165 -0
- package/shared/logic/supersession.ts +77 -0
- package/shared/logic/values.ts +133 -0
- package/shared/logic/widget-logic.ts +163 -0
- package/shared/rpc.ts +252 -0
- package/shared/segment.ts +81 -0
- package/shared/settings.ts +12 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
// Pure logic behind the self-contained widgets (Timer, Quiz, Flashcards):
|
|
2
|
+
// no React, no DOM, so it is unit-testable without the host modules.
|
|
3
|
+
import type { Value } from "../core/types";
|
|
4
|
+
import { isObject, isScalar, list, optionalText, text } from "./values";
|
|
5
|
+
|
|
6
|
+
// ── Timer ────────────────────────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A run/pause clock as timestamps, never as a tick count, so it stays right
|
|
10
|
+
* when the tab is backgrounded and intervals are throttled.
|
|
11
|
+
*/
|
|
12
|
+
export interface Clock {
|
|
13
|
+
/** Milliseconds accumulated over finished runs. */
|
|
14
|
+
readonly banked: number;
|
|
15
|
+
/** When the current run started (epoch ms), or null while stopped. */
|
|
16
|
+
readonly startedAt: number | null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const STOPPED: Clock = { banked: 0, startedAt: null };
|
|
20
|
+
|
|
21
|
+
export function elapsedMs(clock: Clock, now: number): number {
|
|
22
|
+
return clock.banked + (clock.startedAt === null ? 0 : Math.max(0, now - clock.startedAt));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function startClock(clock: Clock, now: number): Clock {
|
|
26
|
+
return clock.startedAt === null ? { banked: clock.banked, startedAt: now } : clock;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function pauseClock(clock: Clock, now: number): Clock {
|
|
30
|
+
return clock.startedAt === null ? clock : { banked: elapsedMs(clock, now), startedAt: null };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Milliseconds left of a `totalMs` countdown, never below zero. */
|
|
34
|
+
export function remainingMs(clock: Clock, totalMs: number, now: number): number {
|
|
35
|
+
return Math.max(0, totalMs - elapsedMs(clock, now));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* `mm:ss`, or `h:mm:ss` from an hour up (or always, with `hours`, so a long
|
|
40
|
+
* countdown keeps one width as it crosses the hour).
|
|
41
|
+
*/
|
|
42
|
+
export function formatClock(totalSeconds: number, hours = false): string {
|
|
43
|
+
const whole = Number.isFinite(totalSeconds) ? Math.max(0, Math.floor(totalSeconds)) : 0;
|
|
44
|
+
const h = Math.floor(whole / 3600);
|
|
45
|
+
const m = Math.floor((whole % 3600) / 60);
|
|
46
|
+
const s = whole % 60;
|
|
47
|
+
const pad = (n: number) => String(n).padStart(2, "0");
|
|
48
|
+
return h > 0 || hours ? `${h}:${pad(m)}:${pad(s)}` : `${pad(m)}:${pad(s)}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** The countdown readout: a started second still shows until it is fully spent. */
|
|
52
|
+
export function countdownSeconds(remaining: number): number {
|
|
53
|
+
return Math.ceil(remaining / 1000);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ── Quiz ─────────────────────────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
export interface QuizQuestion {
|
|
59
|
+
readonly q: string;
|
|
60
|
+
readonly options: readonly string[];
|
|
61
|
+
/** 0-based index into `options`. */
|
|
62
|
+
readonly answer: number;
|
|
63
|
+
readonly why: string | null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The correct option's index: a 0-based integer in range, or the option's
|
|
68
|
+
* text (exact first, then ignoring case and surrounding space).
|
|
69
|
+
*/
|
|
70
|
+
export function resolveAnswer(answer: unknown, options: readonly string[]): number | null {
|
|
71
|
+
if (typeof answer === "number") {
|
|
72
|
+
return Number.isInteger(answer) && answer >= 0 && answer < options.length ? answer : null;
|
|
73
|
+
}
|
|
74
|
+
if (typeof answer !== "string") return null;
|
|
75
|
+
const exact = options.indexOf(answer);
|
|
76
|
+
if (exact >= 0) return exact;
|
|
77
|
+
const wanted = answer.trim().toLowerCase();
|
|
78
|
+
if (wanted === "") return null;
|
|
79
|
+
const loose = options.findIndex((option) => option.trim().toLowerCase() === wanted);
|
|
80
|
+
return loose >= 0 ? loose : null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** One question, or null when it cannot be asked (no prompt, under two options, no valid answer). */
|
|
84
|
+
export function parseQuestion(item: unknown): QuizQuestion | null {
|
|
85
|
+
if (!isObject(item)) return null;
|
|
86
|
+
const q = text(item.q).trim();
|
|
87
|
+
const rawOptions: readonly unknown[] = Array.isArray(item.options) ? item.options : [];
|
|
88
|
+
if (q === "" || rawOptions.length < 2 || !rawOptions.every(isScalar)) return null;
|
|
89
|
+
const options = rawOptions.map(text);
|
|
90
|
+
if (options.some((option) => option.trim() === "")) return null;
|
|
91
|
+
const answer = resolveAnswer(item.answer, options);
|
|
92
|
+
if (answer === null) return null;
|
|
93
|
+
const why = optionalText(item.why)?.trim() || null;
|
|
94
|
+
return { q, options, answer, why };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function parseQuestions(items: Value | undefined): {
|
|
98
|
+
questions: QuizQuestion[];
|
|
99
|
+
skipped: number;
|
|
100
|
+
} {
|
|
101
|
+
const all = list(items);
|
|
102
|
+
const questions = all.map(parseQuestion).filter((question) => question !== null);
|
|
103
|
+
return { questions, skipped: all.length - questions.length };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** How many picks match their question's answer; unanswered questions count as wrong. */
|
|
107
|
+
export function quizScore(
|
|
108
|
+
questions: readonly QuizQuestion[],
|
|
109
|
+
picks: readonly (number | null | undefined)[],
|
|
110
|
+
): number {
|
|
111
|
+
return questions.reduce((sum, question, index) => sum + (picks[index] === question.answer ? 1 : 0), 0);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ── Flashcards ───────────────────────────────────────────────────────────
|
|
115
|
+
|
|
116
|
+
export interface Flashcard {
|
|
117
|
+
readonly front: string;
|
|
118
|
+
readonly back: string;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Cards with a front; a missing back is shown as empty rather than dropped. */
|
|
122
|
+
export function parseCards(items: Value | undefined): Flashcard[] {
|
|
123
|
+
const cards: Flashcard[] = [];
|
|
124
|
+
const entries: readonly unknown[] = list(items);
|
|
125
|
+
for (const item of entries) {
|
|
126
|
+
if (!isObject(item)) continue;
|
|
127
|
+
const front = text(item.front).trim();
|
|
128
|
+
if (front === "") continue;
|
|
129
|
+
cards.push({ front, back: text(item.back).trim() });
|
|
130
|
+
}
|
|
131
|
+
return cards;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* A viewing order for `count` cards built on `order`: positions that no
|
|
136
|
+
* longer exist are dropped and new ones (cards still streaming in) appended.
|
|
137
|
+
*/
|
|
138
|
+
export function reconcileOrder(order: readonly number[], count: number): number[] {
|
|
139
|
+
const seen = new Set<number>();
|
|
140
|
+
const kept = order.filter((index) => {
|
|
141
|
+
const keep = Number.isInteger(index) && index >= 0 && index < count && !seen.has(index);
|
|
142
|
+
seen.add(index);
|
|
143
|
+
return keep;
|
|
144
|
+
});
|
|
145
|
+
for (let index = 0; index < count; index++) if (!seen.has(index)) kept.push(index);
|
|
146
|
+
return kept;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* A Fisher–Yates permutation of `order`. A shuffle that lands on the same
|
|
151
|
+
* order would look like a dead button, so that case swaps the first two.
|
|
152
|
+
*/
|
|
153
|
+
export function shuffleOrder(order: readonly number[], random: () => number = Math.random): number[] {
|
|
154
|
+
const result = [...order];
|
|
155
|
+
for (let i = result.length - 1; i > 0; i--) {
|
|
156
|
+
const j = Math.floor(random() * (i + 1));
|
|
157
|
+
[result[i], result[j]] = [result[j]!, result[i]!];
|
|
158
|
+
}
|
|
159
|
+
if (result.length > 1 && result.every((value, index) => value === order[index])) {
|
|
160
|
+
[result[0], result[1]] = [result[1]!, result[0]!];
|
|
161
|
+
}
|
|
162
|
+
return result;
|
|
163
|
+
}
|
package/shared/rpc.ts
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { defineRpc } from "@getpaseo/plugin";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import type { EnvironmentDetailResult } from "./core/environments";
|
|
4
|
+
import {
|
|
5
|
+
MAX_PATH_FILTERS,
|
|
6
|
+
MAX_THREAD_IDS,
|
|
7
|
+
type ChangesResult,
|
|
8
|
+
type CommitsResult,
|
|
9
|
+
type EnvironmentsResult,
|
|
10
|
+
type PatchesResult,
|
|
11
|
+
type PullRequestResult,
|
|
12
|
+
type ThreadsResult,
|
|
13
|
+
} from "./core/project";
|
|
14
|
+
import type { Value } from "./core/types";
|
|
15
|
+
|
|
16
|
+
export const MAX_TILE_ZOOM = 19;
|
|
17
|
+
|
|
18
|
+
export const foundImageSchema = z.object({
|
|
19
|
+
url: z.string(),
|
|
20
|
+
width: z.number(),
|
|
21
|
+
height: z.number(),
|
|
22
|
+
title: z.string(),
|
|
23
|
+
creator: z.string(),
|
|
24
|
+
license: z.string(),
|
|
25
|
+
licenseUrl: z.string().nullable(),
|
|
26
|
+
sourceUrl: z.string(),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export type FoundImage = z.infer<typeof foundImageSchema>;
|
|
30
|
+
|
|
31
|
+
export const locatedSchema = z.object({
|
|
32
|
+
lat: z.number().min(-90).max(90),
|
|
33
|
+
lng: z.number().min(-180).max(180),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
export type Located = z.infer<typeof locatedSchema>;
|
|
37
|
+
|
|
38
|
+
const filePath = z.string().min(1).max(1024);
|
|
39
|
+
const agentIdSchema = z.string().min(1).max(128);
|
|
40
|
+
const blockKeySchema = z.string().min(1).max(512).nullable();
|
|
41
|
+
|
|
42
|
+
const loadedDataSchema = z.discriminatedUnion("ok", [
|
|
43
|
+
z.object({ ok: z.literal(true), value: z.custom<Value>() }),
|
|
44
|
+
z.object({ ok: z.literal(false), error: z.string() }),
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
const loadedImageSchema = z.discriminatedUnion("ok", [
|
|
48
|
+
z.object({ ok: z.literal(true), dataUrl: z.string() }),
|
|
49
|
+
z.object({ ok: z.literal(false), error: z.string(), off: z.boolean().optional() }),
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
const webFailureSchema = z.object({
|
|
53
|
+
ok: z.literal(false),
|
|
54
|
+
error: z.string(),
|
|
55
|
+
off: z.boolean().optional(),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const webImageSchema = z.discriminatedUnion("ok", [
|
|
59
|
+
z.object({ ok: z.literal(true), dataUrl: z.string() }),
|
|
60
|
+
webFailureSchema,
|
|
61
|
+
]);
|
|
62
|
+
|
|
63
|
+
export const loadDataRpc = defineRpc({
|
|
64
|
+
name: "genui.load_data",
|
|
65
|
+
input: z.object({ agentId: agentIdSchema, path: filePath, raw: z.boolean().optional() }),
|
|
66
|
+
output: loadedDataSchema,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
export const loadImageRpc = defineRpc({
|
|
70
|
+
name: "genui.load_image",
|
|
71
|
+
input: z.object({ agentId: agentIdSchema, path: filePath }),
|
|
72
|
+
output: loadedImageSchema,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
export const loadAudioRpc = defineRpc({
|
|
76
|
+
name: "genui.load_audio",
|
|
77
|
+
input: z.object({ agentId: agentIdSchema, path: filePath }),
|
|
78
|
+
output: loadedImageSchema,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
export const renderMarkdownRpc = defineRpc({
|
|
82
|
+
name: "genui.render_markdown",
|
|
83
|
+
input: z.object({ agentId: agentIdSchema, text: z.string() }),
|
|
84
|
+
output: z.object({ markdown: z.string() }),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
export const checkUiRpc = defineRpc({
|
|
88
|
+
name: "genui.check_ui",
|
|
89
|
+
input: z.object({ agentId: agentIdSchema, spec: z.string().min(1).max(20_000) }),
|
|
90
|
+
output: z.object({ ok: z.boolean(), report: z.string() }),
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
export const uiExamplesRpc = defineRpc({
|
|
94
|
+
name: "genui.ui_examples",
|
|
95
|
+
input: z.object({ components: z.array(z.string().min(1).max(40)).min(1).max(6) }),
|
|
96
|
+
output: z.object({ ok: z.boolean(), text: z.string() }),
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
export const galleryListRpc = defineRpc({
|
|
100
|
+
name: "genui.gallery_list",
|
|
101
|
+
input: z.object({}),
|
|
102
|
+
output: z.object({ names: z.array(z.string()) }),
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
export const galleryGetRpc = defineRpc({
|
|
106
|
+
name: "genui.gallery_get",
|
|
107
|
+
input: z.object({ name: z.string().min(1).max(200) }),
|
|
108
|
+
output: z.object({ source: z.string() }),
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
export const galleryDataRpc = defineRpc({
|
|
112
|
+
name: "genui.gallery_data",
|
|
113
|
+
input: z.object({ path: filePath, raw: z.boolean().optional() }),
|
|
114
|
+
output: loadedDataSchema,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
export const galleryImageRpc = defineRpc({
|
|
118
|
+
name: "genui.gallery_image",
|
|
119
|
+
input: z.object({ path: filePath }),
|
|
120
|
+
output: loadedImageSchema,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
export const galleryAudioRpc = defineRpc({
|
|
124
|
+
name: "genui.gallery_audio",
|
|
125
|
+
input: z.object({ path: filePath }),
|
|
126
|
+
output: loadedImageSchema,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
export const sketchFontRpc = defineRpc({
|
|
130
|
+
name: "genui.sketch_font",
|
|
131
|
+
input: z.object({}),
|
|
132
|
+
output: z.object({ dataUrl: z.string() }),
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
export const webImageRpc = defineRpc({
|
|
136
|
+
name: "genui.web_image",
|
|
137
|
+
input: z.object({ url: z.string().min(1).max(2048) }),
|
|
138
|
+
output: webImageSchema,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
export const webSearchRpc = defineRpc({
|
|
142
|
+
name: "genui.web_search",
|
|
143
|
+
input: z.object({ query: z.string().min(1).max(200) }),
|
|
144
|
+
output: z.discriminatedUnion("ok", [
|
|
145
|
+
z.object({ ok: z.literal(true), image: foundImageSchema }),
|
|
146
|
+
webFailureSchema,
|
|
147
|
+
]),
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
export const webGeocodeRpc = defineRpc({
|
|
151
|
+
name: "genui.web_geocode",
|
|
152
|
+
input: z.object({ name: z.string().min(1).max(200), near: locatedSchema.nullable() }),
|
|
153
|
+
output: z.discriminatedUnion("ok", [
|
|
154
|
+
locatedSchema.extend({ ok: z.literal(true) }),
|
|
155
|
+
webFailureSchema,
|
|
156
|
+
]),
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
export const webTileRpc = defineRpc({
|
|
160
|
+
name: "genui.web_tile",
|
|
161
|
+
input: z.object({
|
|
162
|
+
z: z.number().int().min(0).max(MAX_TILE_ZOOM),
|
|
163
|
+
x: z.number().int().min(0),
|
|
164
|
+
y: z.number().int().min(0),
|
|
165
|
+
}),
|
|
166
|
+
output: webImageSchema,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
export const projectChangesRpc = defineRpc({
|
|
170
|
+
name: "genui.project_changes",
|
|
171
|
+
input: z.object({
|
|
172
|
+
agentId: agentIdSchema,
|
|
173
|
+
target: z.string().max(64).nullable(),
|
|
174
|
+
paths: z.array(z.string().max(1024)).max(MAX_PATH_FILTERS).nullable(),
|
|
175
|
+
blockKey: blockKeySchema,
|
|
176
|
+
refresh: z.boolean(),
|
|
177
|
+
}),
|
|
178
|
+
output: z.custom<ChangesResult>(),
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
export const projectPatchesRpc = defineRpc({
|
|
182
|
+
name: "genui.project_patches",
|
|
183
|
+
input: z.object({
|
|
184
|
+
agentId: agentIdSchema,
|
|
185
|
+
target: z.string().max(64).nullable(),
|
|
186
|
+
paths: z.array(filePath).min(1).max(25),
|
|
187
|
+
}),
|
|
188
|
+
output: z.custom<PatchesResult>(),
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
export const projectCommitsRpc = defineRpc({
|
|
192
|
+
name: "genui.project_commits",
|
|
193
|
+
input: z.object({
|
|
194
|
+
agentId: agentIdSchema,
|
|
195
|
+
blockKey: blockKeySchema,
|
|
196
|
+
refresh: z.boolean(),
|
|
197
|
+
}),
|
|
198
|
+
output: z.custom<CommitsResult>(),
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
export const projectPullRequestRpc = defineRpc({
|
|
202
|
+
name: "genui.project_pull_request",
|
|
203
|
+
input: z.object({ agentId: agentIdSchema }),
|
|
204
|
+
output: z.custom<PullRequestResult>(),
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
export const projectThreadsRpc = defineRpc({
|
|
208
|
+
name: "genui.project_threads",
|
|
209
|
+
input: z.object({
|
|
210
|
+
agentId: agentIdSchema,
|
|
211
|
+
ids: z.array(z.string().max(128)).max(MAX_THREAD_IDS).nullable(),
|
|
212
|
+
}),
|
|
213
|
+
output: z.custom<ThreadsResult>(),
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
export const projectEnvironmentsRpc = defineRpc({
|
|
217
|
+
name: "genui.project_environments",
|
|
218
|
+
input: z.object({ agentId: agentIdSchema }),
|
|
219
|
+
output: z.custom<EnvironmentsResult>(),
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
export const projectEnvironmentRpc = defineRpc({
|
|
223
|
+
name: "genui.project_environment",
|
|
224
|
+
input: z.object({ agentId: agentIdSchema, environmentId: z.string().min(1).max(128) }),
|
|
225
|
+
output: z.custom<EnvironmentDetailResult>(),
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
export const appendDemoRpc = defineRpc({
|
|
229
|
+
name: "genui.append_demo",
|
|
230
|
+
input: z.object({
|
|
231
|
+
agentId: agentIdSchema,
|
|
232
|
+
component: z.string().max(80).optional(),
|
|
233
|
+
}),
|
|
234
|
+
output: z.object({ appended: z.boolean(), name: z.string() }),
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
export const genuiBlockDataSchema = z.object({
|
|
238
|
+
language: z.enum(["ui", "app"]),
|
|
239
|
+
code: z.string(),
|
|
240
|
+
complete: z.boolean(),
|
|
241
|
+
fenceIndex: z.number(),
|
|
242
|
+
messageId: z.string().optional(),
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
export type GenUiBlockData = z.infer<typeof genuiBlockDataSchema>;
|
|
246
|
+
|
|
247
|
+
export const genuiTextDataSchema = z.object({
|
|
248
|
+
text: z.string(),
|
|
249
|
+
phase: z.enum(["streaming", "complete"]).optional(),
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
export type GenUiTextData = z.infer<typeof genuiTextDataSchema>;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { PluginTimelineItem } from "@getpaseo/plugin";
|
|
2
|
+
import { findFences } from "./fallback";
|
|
3
|
+
|
|
4
|
+
export const GENUI_BLOCK_KIND = "genui-block";
|
|
5
|
+
export const GENUI_TEXT_KIND = "genui-text";
|
|
6
|
+
|
|
7
|
+
export type MessageSegment =
|
|
8
|
+
| { readonly kind: "text"; readonly text: string }
|
|
9
|
+
| {
|
|
10
|
+
readonly kind: "fence";
|
|
11
|
+
readonly language: "ui" | "app";
|
|
12
|
+
readonly code: string;
|
|
13
|
+
readonly complete: boolean;
|
|
14
|
+
readonly fenceIndex: number;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function segmentMessage(text: string, phase: "streaming" | "complete"): MessageSegment[] {
|
|
18
|
+
const lines = text.split("\n");
|
|
19
|
+
const allFences = findFences(lines);
|
|
20
|
+
const uiFences = allFences.filter((fence) => fence.language === "ui" || fence.language === "app");
|
|
21
|
+
if (uiFences.length === 0) return [{ kind: "text", text }];
|
|
22
|
+
|
|
23
|
+
const segments: MessageSegment[] = [];
|
|
24
|
+
let cursor = 0;
|
|
25
|
+
let fenceIndex = 0;
|
|
26
|
+
for (const fence of uiFences) {
|
|
27
|
+
if (fence.open > cursor) {
|
|
28
|
+
const prose = lines.slice(cursor, fence.open).join("\n").trim();
|
|
29
|
+
if (prose !== "") segments.push({ kind: "text", text: prose });
|
|
30
|
+
}
|
|
31
|
+
const language = fence.language === "app" ? "app" : "ui";
|
|
32
|
+
segments.push({
|
|
33
|
+
kind: "fence",
|
|
34
|
+
language,
|
|
35
|
+
code: fence.body,
|
|
36
|
+
complete: fence.closed,
|
|
37
|
+
fenceIndex: fenceIndex++,
|
|
38
|
+
});
|
|
39
|
+
cursor = fence.close + 1;
|
|
40
|
+
}
|
|
41
|
+
if (cursor < lines.length) {
|
|
42
|
+
const tail = lines.slice(cursor).join("\n").trim();
|
|
43
|
+
if (tail !== "") segments.push({ kind: "text", text: tail });
|
|
44
|
+
}
|
|
45
|
+
return segments;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function splitTimelineMessage(
|
|
49
|
+
text: string,
|
|
50
|
+
phase: "streaming" | "complete",
|
|
51
|
+
messageId?: string,
|
|
52
|
+
): { items: PluginTimelineItem[] } | undefined {
|
|
53
|
+
const segments = segmentMessage(text, phase);
|
|
54
|
+
if (!segments.some((segment) => segment.kind === "fence")) return undefined;
|
|
55
|
+
|
|
56
|
+
const items: PluginTimelineItem[] = [];
|
|
57
|
+
segments.forEach((segment) => {
|
|
58
|
+
if (segment.kind === "fence") {
|
|
59
|
+
items.push({
|
|
60
|
+
type: "plugin",
|
|
61
|
+
kind: GENUI_BLOCK_KIND,
|
|
62
|
+
version: 1,
|
|
63
|
+
data: {
|
|
64
|
+
language: segment.language,
|
|
65
|
+
code: segment.code,
|
|
66
|
+
complete: segment.complete,
|
|
67
|
+
fenceIndex: segment.fenceIndex,
|
|
68
|
+
...(messageId ? { messageId } : {}),
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
} else {
|
|
72
|
+
items.push({
|
|
73
|
+
type: "plugin",
|
|
74
|
+
kind: GENUI_TEXT_KIND,
|
|
75
|
+
version: 1,
|
|
76
|
+
data: { text: segment.text, phase },
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
return { items };
|
|
81
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { defineSettings } from "@getpaseo/plugin";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
export const genuiSettings = defineSettings({
|
|
5
|
+
id: "preferences",
|
|
6
|
+
scope: "host",
|
|
7
|
+
version: 1,
|
|
8
|
+
schema: z.object({
|
|
9
|
+
autoInstructions: z.boolean().default(true),
|
|
10
|
+
webLookups: z.boolean().default(true),
|
|
11
|
+
}),
|
|
12
|
+
});
|