@connextar/house 0.16.1 → 0.18.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 +205 -3
- package/dist/ai/index.d.ts +1 -1
- package/dist/ai/index.d.ts.map +1 -1
- package/dist/ai/index.js +1 -1
- package/dist/ai/index.js.map +1 -1
- package/dist/ai/provider.d.ts +30 -4
- package/dist/ai/provider.d.ts.map +1 -1
- package/dist/ai/provider.js +25 -4
- package/dist/ai/provider.js.map +1 -1
- package/dist/guides/guide-article.d.ts +13 -2
- package/dist/guides/guide-article.d.ts.map +1 -1
- package/dist/guides/guide-article.js +5 -3
- package/dist/guides/guide-article.js.map +1 -1
- package/dist/guides/guide-figure-zoom.d.ts +62 -0
- package/dist/guides/guide-figure-zoom.d.ts.map +1 -0
- package/dist/guides/guide-figure-zoom.js +272 -0
- package/dist/guides/guide-figure-zoom.js.map +1 -0
- package/dist/guides/guide-figure.d.ts +21 -0
- package/dist/guides/guide-figure.d.ts.map +1 -0
- package/dist/guides/guide-figure.js +19 -0
- package/dist/guides/guide-figure.js.map +1 -0
- package/dist/guides/index.d.ts +2 -0
- package/dist/guides/index.d.ts.map +1 -1
- package/dist/guides/index.js +2 -0
- package/dist/guides/index.js.map +1 -1
- package/dist/guides/library.d.ts +13 -0
- package/dist/guides/library.d.ts.map +1 -1
- package/dist/guides/library.js +8 -0
- package/dist/guides/library.js.map +1 -1
- package/dist/health/health.d.ts +65 -0
- package/dist/health/health.d.ts.map +1 -0
- package/dist/health/health.js +93 -0
- package/dist/health/health.js.map +1 -0
- package/dist/health/index.d.ts +2 -0
- package/dist/health/index.d.ts.map +1 -0
- package/dist/health/index.js +2 -0
- package/dist/health/index.js.map +1 -0
- package/dist/markdown/figure.d.ts +44 -0
- package/dist/markdown/figure.d.ts.map +1 -0
- package/dist/markdown/figure.js +56 -0
- package/dist/markdown/figure.js.map +1 -0
- package/dist/markdown/figures.d.ts +94 -0
- package/dist/markdown/figures.d.ts.map +1 -0
- package/dist/markdown/figures.js +126 -0
- package/dist/markdown/figures.js.map +1 -0
- package/dist/markdown/guide.d.ts +15 -3
- package/dist/markdown/guide.d.ts.map +1 -1
- package/dist/markdown/guide.js +20 -3
- package/dist/markdown/guide.js.map +1 -1
- package/dist/markdown/index.d.ts +2 -0
- package/dist/markdown/index.d.ts.map +1 -1
- package/dist/markdown/index.js +2 -0
- package/dist/markdown/index.js.map +1 -1
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -666,6 +666,34 @@ export function Markdown({ content }: { content: string }) {
|
|
|
666
666
|
|
|
667
667
|
`classNames` replaces the look of any element for an app without theme tokens.
|
|
668
668
|
|
|
669
|
+
**Pictures.** A guide names a screenshot or a diagram on a line of its own, with a
|
|
670
|
+
blank line either side:
|
|
671
|
+
|
|
672
|
+
```md
|
|
673
|
+

|
|
674
|
+
|
|
675
|
+

|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
`GuideMarkdown` draws one only when it is in the app's registry, `figures` — a
|
|
679
|
+
screenshot with its `width` and `height` (so the page doesn't jump as it loads) and
|
|
680
|
+
optionally a `dark` capture of the same size, or a diagram by name — and has alt
|
|
681
|
+
text; anything else is left out, so a guide can't pull an image in from elsewhere
|
|
682
|
+
and a typo never shows a broken image. (Before 0.18.0 such a line came out as a
|
|
683
|
+
`!` and a link.) A screenshot is a plain lazy `<img>`; with a `dark` capture both
|
|
684
|
+
are drawn and the theme's `dark:` variant shows one; without one, the light
|
|
685
|
+
picture is toned down a little on a dark page. A diagram registered as a component
|
|
686
|
+
is given the alt text as `label` for its own `<svg role="img" aria-label>`; one
|
|
687
|
+
registered as an element is wrapped in `role="img"`. Headings keep their ids
|
|
688
|
+
across pictures, and a picture ends a list.
|
|
689
|
+
|
|
690
|
+
`guideFigureProblems(body, figures, { minAltLength })` is the check an app's test
|
|
691
|
+
asserts empty for every guide: an unregistered picture, missing (or too short) alt
|
|
692
|
+
text, a picture without a blank line either side or indented as if in a list, and
|
|
693
|
+
an image written inside a line of text. `splitGuideFigures` is the same split as
|
|
694
|
+
data; `renderFigure` replaces the drawing. `GuideArticle` adds zoom — see
|
|
695
|
+
[Pictures and zoom](#pictures-and-zoom).
|
|
696
|
+
|
|
669
697
|
### `@connextar/house/guides`
|
|
670
698
|
|
|
671
699
|
The frame a help centre's pages sit in, so every app's guides read and are found
|
|
@@ -706,6 +734,170 @@ export default async function GuidePage({ params }) {
|
|
|
706
734
|
}
|
|
707
735
|
```
|
|
708
736
|
|
|
737
|
+
#### Pictures and zoom
|
|
738
|
+
|
|
739
|
+
Pass the registry and `GuideArticle` draws a guide's pictures
|
|
740
|
+
([the Markdown](#connextarhousemarkdown)), each one a button: a "Click to enlarge"
|
|
741
|
+
hint in its corner and a zoom-in cursor. A click, Enter or Space opens it in a
|
|
742
|
+
dialog fitted to the screen (the whole screen on a phone):
|
|
743
|
+
|
|
744
|
+
- zoom with the buttons, `+` and `−`, ⌘/Ctrl + scroll (a trackpad pinch arrives as
|
|
745
|
+
one) or a touch pinch, from fit up to four times full size, at the pointer;
|
|
746
|
+
- when it is bigger than the screen, drag it, scroll it, or move it with the arrow
|
|
747
|
+
keys — never past its edges;
|
|
748
|
+
- a double-click or double-tap, or the zoom level between the buttons, switches
|
|
749
|
+
between fit and full size (one image pixel to one CSS pixel); `0` fits again;
|
|
750
|
+
- the caption and the alt text are shown under it; Escape or the close button
|
|
751
|
+
closes it and focus goes back to the picture. Zoom eases only without
|
|
752
|
+
`prefers-reduced-motion`; dragging and pinching never do.
|
|
753
|
+
|
|
754
|
+
Diagrams zoom too. They are vector, so the dialog lays the diagram out at the size
|
|
755
|
+
it had in the article and scales it: fit fills the screen, and "full size" is twice
|
|
756
|
+
the fit. `zoom={false}` draws plain pictures. `labels` changes the words (`zoomHint`,
|
|
757
|
+
`zoomOpen`, `zoomIn`, `zoomOut`, `zoomToggle`, `zoomLevel`, `zoomClose`,
|
|
758
|
+
`zoomInstructions`), `classNames.figure` the picture's parts and `zoomClassNames`
|
|
759
|
+
the button and dialog's (`GUIDE_ZOOM_CLASS_NAMES` lists them), all on the theme
|
|
760
|
+
tokens. `GuideFigure` and `GuideFigureZoom` are there for a page laid out without
|
|
761
|
+
`GuideArticle`. The zoom's classes appear only in this package, so the
|
|
762
|
+
[Tailwind `@source`](#tailwind-setup-required-for-every-component) line is what
|
|
763
|
+
styles it.
|
|
764
|
+
|
|
765
|
+
Adopting it (PharmaLineAI's v0.23.1 pictures, which this generalises):
|
|
766
|
+
|
|
767
|
+
1. **Screenshots** live in `public/guide-images/`, as WebP about 1600px wide (sharp
|
|
768
|
+
in a 768px column on a 2× screen) and under ~150 KB each. A dark capture, if
|
|
769
|
+
the app makes one, sits beside it at the same size (`calls-log-dark.webp`).
|
|
770
|
+
2. **The registry.** Sizes in `lib/guide-screenshots.json`, written by the capture
|
|
771
|
+
script, and the diagrams beside them:
|
|
772
|
+
|
|
773
|
+
```json
|
|
774
|
+
{
|
|
775
|
+
"/guide-images/calls-log.webp": { "width": 1600, "height": 986 },
|
|
776
|
+
"/guide-images/overview.webp": { "width": 1600, "height": 506, "dark": "/guide-images/overview-dark.webp" }
|
|
777
|
+
}
|
|
778
|
+
```
|
|
779
|
+
|
|
780
|
+
```tsx
|
|
781
|
+
// lib/guide-figures.tsx
|
|
782
|
+
import type { GuideFigures } from "@connextar/house/markdown";
|
|
783
|
+
|
|
784
|
+
import { CallRouting, IdentityCheck } from "@components/guides/guide-diagrams";
|
|
785
|
+
import screenshots from "@lib/guide-screenshots.json";
|
|
786
|
+
|
|
787
|
+
export const GUIDE_FIGURES: GuideFigures = {
|
|
788
|
+
screenshots,
|
|
789
|
+
diagrams: { "call-routing": CallRouting, "identity-check": IdentityCheck }, // each takes { label }
|
|
790
|
+
};
|
|
791
|
+
```
|
|
792
|
+
|
|
793
|
+
3. **The pages** pass it: `<GuideArticle … figures={GUIDE_FIGURES} />`, on the public
|
|
794
|
+
and the signed-in guide pages alike. A diagram component is drawn on the server;
|
|
795
|
+
only what it drew crosses into the dialog.
|
|
796
|
+
4. **The app's test** reads the real guides:
|
|
797
|
+
|
|
798
|
+
```ts
|
|
799
|
+
for (const { slug } of GUIDES) {
|
|
800
|
+
expect(guideFigureProblems(body(slug), GUIDE_FIGURES, { minAltLength: 20 }), slug).toEqual([]);
|
|
801
|
+
}
|
|
802
|
+
```
|
|
803
|
+
|
|
804
|
+
and, because the package can't see the app's disk, checks the files: every
|
|
805
|
+
registered screenshot (and `dark`) exists at its recorded size (the app's own
|
|
806
|
+
`sharp`), every file in `public/guide-images/` is registered and used, all
|
|
807
|
+
WebP and small.
|
|
808
|
+
|
|
809
|
+
5. **Capturing them (optional).** PharmaLineAI's `scripts/guide-screenshots.ts` is
|
|
810
|
+
the pattern, and stays in the app — this package ships no Playwright. It runs
|
|
811
|
+
only against the local harness (it refuses any base URL that isn't
|
|
812
|
+
`localhost`/`127.0.0.1`), signed in as the seeded owner from the harness's
|
|
813
|
+
cookie jar, in headless Chromium at 1440px, 2× pixels, light mode and reduced
|
|
814
|
+
motion. Each shot names a page, an optional `before` step (open a call, pick a
|
|
815
|
+
setting), and a crop to the panel its guide talks about, found from the page
|
|
816
|
+
(a heading, a bordered card). It hides the dev overlay, replaces seed text a
|
|
817
|
+
public page mustn't show (a real brand in a seed name), puts back any seed data
|
|
818
|
+
it changed for a shot in a `finally`, resizes to 1600px WebP with sharp, and
|
|
819
|
+
writes the sizes to `lib/guide-screenshots.json`. A dark set is the same run
|
|
820
|
+
with `colorScheme: "dark"` writing `-dark.webp` files and each entry's `dark`.
|
|
821
|
+
|
|
822
|
+
PharmaLineAI's own `components/guides/guide-article.tsx` and the split in
|
|
823
|
+
`lib/guide-figures.ts` go; its diagrams and its JSON stay, and its list of diagram
|
|
824
|
+
names becomes the `diagrams` record.
|
|
825
|
+
|
|
826
|
+
### `@connextar/house/health`
|
|
827
|
+
|
|
828
|
+
A liveness check that says which release is running, so a deploy can be confirmed
|
|
829
|
+
from outside: `curl https://app.example/healthz` answers `ok 0.18.0 2c9a3cf`.
|
|
830
|
+
From PharmaLineAI's voice relay (v0.24.1), for every app.
|
|
831
|
+
|
|
832
|
+
- `buildInfo(version, env = process.env)` → `{ version?, commit? }`. The version
|
|
833
|
+
must be semver-shaped (`1.2.3`, `1.2.3-rc.1`); the commit is the first of
|
|
834
|
+
`VERCEL_GIT_COMMIT_SHA`, `RENDER_GIT_COMMIT` and `GIT_COMMIT` that is a whole
|
|
835
|
+
hex hash, cut to 7. Anything else is dropped, never echoed: the endpoint is
|
|
836
|
+
public.
|
|
837
|
+
- `healthText(build)` → `ok 1.2.3 abc1234` (just `ok` when nothing is known).
|
|
838
|
+
- `healthResponse(build, { format })` → always 200, `Cache-Control: no-store`;
|
|
839
|
+
`text/plain` by default, `{ "status": "ok", "version", "commit" }` for
|
|
840
|
+
`format: "json"`. `healthFormat(request)` picks: `?format=json|text`, then an
|
|
841
|
+
`Accept` that names `application/json`; a browser, curl and a host's checker get
|
|
842
|
+
text.
|
|
843
|
+
- `createHealthRoute({ version })` → `{ GET }`, the two above as a Next route
|
|
844
|
+
handler. Next answers `HEAD` from `GET` itself. No Next import, no package at all:
|
|
845
|
+
it is web `Request` and `Response`.
|
|
846
|
+
|
|
847
|
+
The whole adoption is one file, `app/healthz/route.ts` (`src/app/…` adds a `../`):
|
|
848
|
+
|
|
849
|
+
```ts
|
|
850
|
+
import { createHealthRoute } from "@connextar/house/health";
|
|
851
|
+
|
|
852
|
+
// A route handler is server-only, so package.json is bundled into the server and never reaches the browser.
|
|
853
|
+
import pkg from "../../package.json";
|
|
854
|
+
|
|
855
|
+
export const { GET } = createHealthRoute({ version: pkg.version });
|
|
856
|
+
```
|
|
857
|
+
|
|
858
|
+
(Next's default `tsconfig` has `resolveJsonModule`.) **Not**
|
|
859
|
+
`process.env.npm_package_version`: npm sets it only in a process npm itself
|
|
860
|
+
started — `node -e 'console.log(process.env.npm_package_version)'` prints
|
|
861
|
+
`undefined`, and the same line under `npm run` prints the version. A Vercel
|
|
862
|
+
function is started by Vercel's runtime, not `npm start`, so it is unset when the
|
|
863
|
+
route runs; it is set during `npm run build`, so it could be inlined through
|
|
864
|
+
`next.config`'s `env`, but only for as long as the build goes through npm. It works
|
|
865
|
+
on Render under `npm start`, which is why it looks reliable until it isn't.
|
|
866
|
+
|
|
867
|
+
The commit: Vercel sets `VERCEL_GIT_COMMIT_SHA` on Git deployments while "Automatically
|
|
868
|
+
expose System Environment Variables" is on (the default); Render sets
|
|
869
|
+
`RENDER_GIT_COMMIT`; anywhere else, set `GIT_COMMIT`. On Render, point the service's
|
|
870
|
+
Health Check Path at `/healthz`; it needs the 200 and nothing else. A plain Node
|
|
871
|
+
server (PharmaLineAI's relay) uses `buildInfo` and `healthText` directly.
|
|
872
|
+
|
|
873
|
+
**Let it through.** `/healthz` must answer without signing in: exclude it from the
|
|
874
|
+
app's `proxy.ts` (`middleware.ts` before Next 16) — e.g.
|
|
875
|
+
`matcher: ["/((?!healthz|_next/static|_next/image|favicon.ico).*)"]` — and from any
|
|
876
|
+
auth redirect, maintenance switch or locale rewrite in it. Vercel Deployment
|
|
877
|
+
Protection on preview deployments answers 401 before the app does; that is
|
|
878
|
+
expected, and production should be unprotected.
|
|
879
|
+
|
|
880
|
+
**Liveness only.** It touches no database and no provider, so it can't be slow and
|
|
881
|
+
a provider's outage never makes a host restart a healthy app. An app that wants a
|
|
882
|
+
readiness check writes its own, separately — never as the host's health check:
|
|
883
|
+
|
|
884
|
+
```ts
|
|
885
|
+
// app/readyz/route.ts — the app's own; says only ready / not ready
|
|
886
|
+
export async function GET() {
|
|
887
|
+
const ok = await Promise.race([
|
|
888
|
+
prisma.$queryRaw`select 1`.then(
|
|
889
|
+
() => true,
|
|
890
|
+
() => false,
|
|
891
|
+
),
|
|
892
|
+
new Promise<boolean>((resolve) => setTimeout(() => resolve(false), 2000)),
|
|
893
|
+
]);
|
|
894
|
+
return new Response(ok ? "ready" : "not ready", {
|
|
895
|
+
status: ok ? 200 : 503,
|
|
896
|
+
headers: { "cache-control": "no-store", "content-type": "text/plain; charset=utf-8" },
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
```
|
|
900
|
+
|
|
709
901
|
### `@connextar/house/trail`
|
|
710
902
|
|
|
711
903
|
Where you came from, rather than where the URL says you are. A breadcrumb built
|
|
@@ -1438,15 +1630,25 @@ Rules the module holds so no app has to rediscover them:
|
|
|
1438
1630
|
- **Thinking belongs to the job.** Adaptive thinking is sent to Anthropic only
|
|
1439
1631
|
for a job declared `thinking: true` (or a call that asks) — the small fast
|
|
1440
1632
|
models reject it with a 400, and the job's default model is where that is
|
|
1441
|
-
known.
|
|
1633
|
+
known. `effort` is Anthropic's; for OpenAI a call may pass `reasoningEffort`
|
|
1634
|
+
(`"minimal" | "low" | "medium" | "high"`, 0.17.0), sent only to models that
|
|
1635
|
+
reason (`takesReasoningEffort`: the gpt-5 family but its `-chat` models, and the
|
|
1636
|
+
o-series), so a non-reasoning model an operator picks still works. Omitted, the
|
|
1637
|
+
model reasons at its own default.
|
|
1442
1638
|
- **OpenAI output budgets get a floor** (`OPENAI_MIN_OUTPUT_TOKENS`, 4,000): a
|
|
1443
|
-
reasoning model spends the budget thinking before it writes a word.
|
|
1639
|
+
reasoning model spends the budget thinking before it writes a word. A job that
|
|
1640
|
+
reads and lists rather than solves can pass `reasoningEffort: "low"` and keep
|
|
1641
|
+
most of that budget, and time, for the answer (AltEd tranche 169: a year-long
|
|
1642
|
+
targets sheet took ~14,700 tokens and ~170 s at the default).
|
|
1444
1643
|
- **Structured calls stream**, because both SDKs refuse a non-streaming call that
|
|
1445
1644
|
could outlive their timeout. A `timeoutMs` call makes one attempt, no retries.
|
|
1446
1645
|
- **Failures are three classes.** `AiUnavailableError` — off, nothing was sent.
|
|
1447
1646
|
`AiServiceError` — the provider said no, in its own words, with its status.
|
|
1448
1647
|
`AiUnusableAnswerError` — a refusal, a cut-off or the wrong shape; the tokens
|
|
1449
|
-
were spent anyway, so `error.usage` carries them for the caller to record
|
|
1648
|
+
were spent anyway, so `error.usage` carries them for the caller to record, and
|
|
1649
|
+
`error.reason` (`"cut-off" | "refused" | "wrong-shape"`, 0.17.0) says which. A
|
|
1650
|
+
cut-off is the output budget running out — the app's choice of `maxTokens`, so
|
|
1651
|
+
an app may decide not to charge a person for it.
|
|
1450
1652
|
- **Usage is cache-aware.** Every result carries input, output, cache-read and
|
|
1451
1653
|
cache-write tokens. OpenAI counts cached tokens inside `input_tokens`; they are
|
|
1452
1654
|
moved out, not added, so nothing is billed twice.
|
package/dist/ai/index.d.ts
CHANGED
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
* imports `@connextar/house/ai/config` instead.
|
|
5
5
|
*/
|
|
6
6
|
export * from "./config-index.js";
|
|
7
|
-
export { AiServiceError, AiUnavailableError, AiUnusableAnswerError, OPENAI_MIN_OUTPUT_TOKENS, createAiHarness, type AiCallResult, type AiHarness, type AiHarnessOptions, type AiStructuredCall, type AiTextCall, } from "./provider.js";
|
|
7
|
+
export { AiServiceError, AiUnavailableError, AiUnusableAnswerError, OPENAI_MIN_OUTPUT_TOKENS, createAiHarness, takesReasoningEffort, type AiCallResult, type AiHarness, type AiHarnessOptions, type AiStructuredCall, type AiTextCall, type AiUnusableReason, type OpenAiReasoningEffort, } from "./provider.js";
|
|
8
8
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/ai/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ai/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,cAAc,mBAAmB,CAAC;AAClC,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,qBAAqB,EACrB,wBAAwB,EACxB,eAAe,EACf,KAAK,YAAY,EACjB,KAAK,SAAS,EACd,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,UAAU,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ai/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,cAAc,mBAAmB,CAAC;AAClC,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,qBAAqB,EACrB,wBAAwB,EACxB,eAAe,EACf,oBAAoB,EACpB,KAAK,YAAY,EACjB,KAAK,SAAS,EACd,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,GAC3B,MAAM,eAAe,CAAC"}
|
package/dist/ai/index.js
CHANGED
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
* imports `@connextar/house/ai/config` instead.
|
|
5
5
|
*/
|
|
6
6
|
export * from "./config-index.js";
|
|
7
|
-
export { AiServiceError, AiUnavailableError, AiUnusableAnswerError, OPENAI_MIN_OUTPUT_TOKENS, createAiHarness, } from "./provider.js";
|
|
7
|
+
export { AiServiceError, AiUnavailableError, AiUnusableAnswerError, OPENAI_MIN_OUTPUT_TOKENS, createAiHarness, takesReasoningEffort, } from "./provider.js";
|
|
8
8
|
//# sourceMappingURL=index.js.map
|
package/dist/ai/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ai/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,cAAc,mBAAmB,CAAC;AAClC,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,qBAAqB,EACrB,wBAAwB,EACxB,eAAe,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ai/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,cAAc,mBAAmB,CAAC;AAClC,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,qBAAqB,EACrB,wBAAwB,EACxB,eAAe,EACf,oBAAoB,GAQrB,MAAM,eAAe,CAAC"}
|
package/dist/ai/provider.d.ts
CHANGED
|
@@ -35,16 +35,41 @@ export declare class AiServiceError extends Error {
|
|
|
35
35
|
readonly status: number | undefined;
|
|
36
36
|
constructor(message: string, status?: number);
|
|
37
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Why an answer was unusable (0.17.0):
|
|
40
|
+
*
|
|
41
|
+
* - `cut-off` — it ran out of output budget before it finished: the caller's
|
|
42
|
+
* `maxTokens`, which an OpenAI reasoning model also spends its thinking from.
|
|
43
|
+
* Trying again with the same budget usually fails the same way, and the
|
|
44
|
+
* budget is the app's choice, not the person's — an app may decide not to
|
|
45
|
+
* charge for it.
|
|
46
|
+
* - `refused` — the model declined, or the provider's filter stopped it.
|
|
47
|
+
* - `wrong-shape` — it finished, but not in the schema asked for.
|
|
48
|
+
*/
|
|
49
|
+
export type AiUnusableReason = "cut-off" | "refused" | "wrong-shape";
|
|
38
50
|
/**
|
|
39
51
|
* The provider answered, but not with something usable — a refusal, a cut-off
|
|
40
52
|
* or an answer in the wrong shape. The tokens were spent all the same, so the
|
|
41
53
|
* usage travels with the error for the caller to record (AltEd round 10: these
|
|
42
|
-
* were free, and a failed step could be tried again without limit).
|
|
54
|
+
* were free, and a failed step could be tried again without limit). `reason`
|
|
55
|
+
* says which, for a caller whose charging or wording depends on it.
|
|
43
56
|
*/
|
|
44
57
|
export declare class AiUnusableAnswerError extends AiUnavailableError {
|
|
45
58
|
readonly usage: AiCallResult;
|
|
46
|
-
|
|
59
|
+
readonly reason: AiUnusableReason;
|
|
60
|
+
constructor(message: string, usage: AiCallResult, reason?: AiUnusableReason);
|
|
47
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* How hard an OpenAI reasoning model thinks before it answers (0.17.0).
|
|
64
|
+
* Its thinking is billed as output and shares `maxTokens`, so a job that is
|
|
65
|
+
* reading and listing rather than solving can save most of its time and cost
|
|
66
|
+
* with `"low"`. Sent only to models that reason — the gpt-5 family (not its
|
|
67
|
+
* `-chat` models) and the o-series — so a non-reasoning model an operator
|
|
68
|
+
* chooses still works. Anthropic ignores it; it has `thinking` and `effort`.
|
|
69
|
+
*/
|
|
70
|
+
export type OpenAiReasoningEffort = "minimal" | "low" | "medium" | "high";
|
|
71
|
+
/** Whether an OpenAI model takes a reasoning effort. By name: the API has no way to ask. */
|
|
72
|
+
export declare function takesReasoningEffort(model: string): boolean;
|
|
48
73
|
export interface AiCallResult {
|
|
49
74
|
provider: AiProviderName;
|
|
50
75
|
model: string;
|
|
@@ -66,6 +91,8 @@ export interface AiTextCall<Task extends string> {
|
|
|
66
91
|
* the function being killed mid-call.
|
|
67
92
|
*/
|
|
68
93
|
timeoutMs?: number;
|
|
94
|
+
/** OpenAI reasoning models only; omitted = the model's own default. See `OpenAiReasoningEffort`. */
|
|
95
|
+
reasoningEffort?: OpenAiReasoningEffort;
|
|
69
96
|
}
|
|
70
97
|
export interface AiStructuredCall<Task extends string, T> extends AiTextCall<Task> {
|
|
71
98
|
schema: z.ZodType<T>;
|
|
@@ -87,8 +114,7 @@ export interface AiStructuredCall<Task extends string, T> extends AiTextCall<Tas
|
|
|
87
114
|
thinking?: boolean;
|
|
88
115
|
/**
|
|
89
116
|
* How hard to think, on providers and models that take the instruction.
|
|
90
|
-
* Anthropic-only
|
|
91
|
-
* that a non-reasoning model an operator chooses still works.
|
|
117
|
+
* Anthropic-only; for OpenAI see `reasoningEffort`.
|
|
92
118
|
*/
|
|
93
119
|
effort?: "medium" | "high";
|
|
94
120
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/ai/provider.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B,OAAO,EAKL,KAAK,KAAK,EACV,KAAK,cAAc,EACnB,KAAK,QAAQ,EACb,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,UAAU,EAChB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,gEAAgE;AAChE,qBAAa,kBAAmB,SAAQ,KAAK;gBAC/B,OAAO,SAAkE;CAItF;AAED,0FAA0F;AAC1F,qBAAa,cAAe,SAAQ,KAAK;IACvC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;gBAExB,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM;CAK7C;AAED
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/ai/provider.ts"],"names":[],"mappings":"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"}
|
package/dist/ai/provider.js
CHANGED
|
@@ -48,16 +48,23 @@ export class AiServiceError extends Error {
|
|
|
48
48
|
* The provider answered, but not with something usable — a refusal, a cut-off
|
|
49
49
|
* or an answer in the wrong shape. The tokens were spent all the same, so the
|
|
50
50
|
* usage travels with the error for the caller to record (AltEd round 10: these
|
|
51
|
-
* were free, and a failed step could be tried again without limit).
|
|
51
|
+
* were free, and a failed step could be tried again without limit). `reason`
|
|
52
|
+
* says which, for a caller whose charging or wording depends on it.
|
|
52
53
|
*/
|
|
53
54
|
export class AiUnusableAnswerError extends AiUnavailableError {
|
|
54
55
|
usage;
|
|
55
|
-
|
|
56
|
+
reason;
|
|
57
|
+
constructor(message, usage, reason = "wrong-shape") {
|
|
56
58
|
super(message);
|
|
57
59
|
this.name = "AiUnusableAnswerError";
|
|
58
60
|
this.usage = usage;
|
|
61
|
+
this.reason = reason;
|
|
59
62
|
}
|
|
60
63
|
}
|
|
64
|
+
/** Whether an OpenAI model takes a reasoning effort. By name: the API has no way to ask. */
|
|
65
|
+
export function takesReasoningEffort(model) {
|
|
66
|
+
return /^(gpt-5|o\d)/.test(model) && !/-chat/.test(model);
|
|
67
|
+
}
|
|
61
68
|
/**
|
|
62
69
|
* An OpenAI reasoning model bills its thinking against the same budget as its
|
|
63
70
|
* answer, so a 900-token ceiling can be spent entirely before the prose starts.
|
|
@@ -95,6 +102,10 @@ function anthropicUsage(provider, message) {
|
|
|
95
102
|
cacheWriteTokens: message.usage.cache_creation_input_tokens ?? 0,
|
|
96
103
|
};
|
|
97
104
|
}
|
|
105
|
+
/** The `reasoning` field for an OpenAI request, or nothing when the model doesn't reason or none was asked for. */
|
|
106
|
+
function openAiReasoning(model, effort) {
|
|
107
|
+
return effort && takesReasoningEffort(model) ? { reasoning: { effort } } : {};
|
|
108
|
+
}
|
|
98
109
|
/**
|
|
99
110
|
* Bind the harness to an app's jobs.
|
|
100
111
|
*
|
|
@@ -141,6 +152,7 @@ export function createAiHarness(options) {
|
|
|
141
152
|
instructions: call.system,
|
|
142
153
|
input: call.prompt,
|
|
143
154
|
max_output_tokens: Math.max(call.maxTokens, OPENAI_MIN_OUTPUT_TOKENS),
|
|
155
|
+
...openAiReasoning(model, call.reasoningEffort),
|
|
144
156
|
}, requestOptions);
|
|
145
157
|
return { text: response.output_text.trim(), ...openAiUsage(provider, response.model, response.usage) };
|
|
146
158
|
}
|
|
@@ -177,7 +189,10 @@ export function createAiHarness(options) {
|
|
|
177
189
|
const response = await stream.finalMessage();
|
|
178
190
|
const usage = anthropicUsage(provider, response);
|
|
179
191
|
if (response.stop_reason === "refusal") {
|
|
180
|
-
throw new AiUnusableAnswerError("The model declined to process this request.", usage);
|
|
192
|
+
throw new AiUnusableAnswerError("The model declined to process this request.", usage, "refused");
|
|
193
|
+
}
|
|
194
|
+
if (response.stop_reason === "max_tokens") {
|
|
195
|
+
throw new AiUnusableAnswerError("The answer was cut off before it was complete (max_tokens). Try a model with more room, or a shorter input.", usage, "cut-off");
|
|
181
196
|
}
|
|
182
197
|
if (response.parsed_output === null || response.parsed_output === undefined) {
|
|
183
198
|
throw new AiUnusableAnswerError("The model's answer did not match the expected shape.", usage);
|
|
@@ -201,11 +216,17 @@ export function createAiHarness(options) {
|
|
|
201
216
|
input: [{ role: "user", content }],
|
|
202
217
|
max_output_tokens: Math.max(call.maxTokens, OPENAI_MIN_OUTPUT_TOKENS),
|
|
203
218
|
text: { format: zodTextFormat(call.schema, call.schemaName) },
|
|
219
|
+
...openAiReasoning(model, call.reasoningEffort),
|
|
204
220
|
}, requestOptions);
|
|
205
221
|
const response = await stream.finalResponse();
|
|
206
222
|
const usage = openAiUsage(provider, response.model, response.usage);
|
|
207
223
|
if (response.status === "incomplete") {
|
|
208
|
-
|
|
224
|
+
const why = response.incomplete_details?.reason;
|
|
225
|
+
// A content filter stopping it is a refusal by another name; anything else is the budget running out.
|
|
226
|
+
if (why === "content_filter") {
|
|
227
|
+
throw new AiUnusableAnswerError("The provider's content filter stopped the answer.", usage, "refused");
|
|
228
|
+
}
|
|
229
|
+
throw new AiUnusableAnswerError(`The answer was cut off before it was complete (${why ?? "no reason given"}). Try a model with more room, or a shorter input.`, usage, "cut-off");
|
|
209
230
|
}
|
|
210
231
|
if (response.output_parsed === null || response.output_parsed === undefined) {
|
|
211
232
|
throw new AiUnusableAnswerError("The model's answer did not match the expected shape.", usage);
|
package/dist/ai/provider.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../../src/ai/provider.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,mBAAmB,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAGnD,OAAO,EACL,QAAQ,EACR,eAAe,EACf,cAAc,EACd,SAAS,GAOV,MAAM,aAAa,CAAC;AAGrB;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,gEAAgE;AAChE,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAC3C,YAAY,OAAO,GAAG,+DAA+D;QACnF,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;IACnC,CAAC;CACF;AAED,0FAA0F;AAC1F,MAAM,OAAO,cAAe,SAAQ,KAAK;IAC9B,MAAM,CAAqB;IAEpC,YAAY,OAAe,EAAE,MAAe;QAC1C,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;QAC7B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF;
|
|
1
|
+
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../../src/ai/provider.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import type { GuideFigures } from "../markdown/figures.js";
|
|
1
2
|
import { type GuideClassNames } from "../markdown/guide.js";
|
|
3
|
+
import type { GuideZoomClassNames } from "./guide-figure-zoom.js";
|
|
2
4
|
import { type GuidesLabels } from "./library.js";
|
|
3
5
|
export interface GuideArticleProps {
|
|
4
6
|
/** The guide as `parseGuideFrontmatter` splits it. */
|
|
@@ -15,13 +17,22 @@ export interface GuideArticleProps {
|
|
|
15
17
|
category?: string;
|
|
16
18
|
labels?: Partial<GuidesLabels>;
|
|
17
19
|
classNames?: Partial<GuideClassNames>;
|
|
20
|
+
/**
|
|
21
|
+
* The pictures this app's guides may show (`` and
|
|
22
|
+
* `` on a line of their own). Unregistered ones are left out.
|
|
23
|
+
*/
|
|
24
|
+
figures?: GuideFigures;
|
|
25
|
+
/** Open a picture full-size on a click, Enter or Space. Default: on. */
|
|
26
|
+
zoom?: boolean;
|
|
27
|
+
zoomClassNames?: Partial<GuideZoomClassNames>;
|
|
18
28
|
}
|
|
19
29
|
/**
|
|
20
30
|
* One guide, laid out for reading inside `GuidesShell`: a way back to all guides, the category,
|
|
21
31
|
* the title and lede, the article drawn by `GuideMarkdown`, and "On this page" — a rail that
|
|
22
32
|
* follows the reader on wide screens, a collapsible list above the article on narrow ones. The
|
|
23
33
|
* contents use the same heading ids the renderer gives, so `#<heading id>` links land.
|
|
24
|
-
*
|
|
34
|
+
* Registered pictures are drawn where the guide names them, each opening full-size.
|
|
35
|
+
* Server-safe; the contents and the zoom are the only client parts.
|
|
25
36
|
*/
|
|
26
|
-
export declare function GuideArticle({ guide, guidesPath, indexSlug, category, labels, classNames }: GuideArticleProps): import("react").JSX.Element;
|
|
37
|
+
export declare function GuideArticle({ guide, guidesPath, indexSlug, category, labels, classNames, figures, zoom, zoomClassNames, }: GuideArticleProps): import("react").JSX.Element;
|
|
27
38
|
//# sourceMappingURL=guide-article.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guide-article.d.ts","sourceRoot":"","sources":["../../src/guides/guide-article.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAkC,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAE5F,OAAO,EAAiB,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAEhE,MAAM,WAAW,iBAAiB;IAChC,sDAAsD;IACtD,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACtD,iFAAiF;IACjF,UAAU,EAAE,MAAM,CAAC;IACnB,4FAA4F;IAC5F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2CAA2C;IAC3C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;IAC/B,UAAU,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"guide-article.d.ts","sourceRoot":"","sources":["../../src/guides/guide-article.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAkC,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAE5F,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAElE,OAAO,EAAiB,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAEhE,MAAM,WAAW,iBAAiB;IAChC,sDAAsD;IACtD,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACtD,iFAAiF;IACjF,UAAU,EAAE,MAAM,CAAC;IACnB,4FAA4F;IAC5F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2CAA2C;IAC3C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;IAC/B,UAAU,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;IACtC;;;OAGG;IACH,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,wEAAwE;IACxE,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,cAAc,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,CAAC;CAC/C;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,UAAU,EACV,SAAS,EACT,QAAQ,EACR,MAAM,EACN,UAAU,EACV,OAAO,EACP,IAAI,EACJ,cAAc,GACf,EAAE,iBAAiB,+BA0CnB"}
|
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { ChevronLeft } from "lucide-react";
|
|
3
3
|
import { Link } from "../internal/next-link.js";
|
|
4
4
|
import { extractHeadings, GuideMarkdown } from "../markdown/guide.js";
|
|
5
|
+
import { GuideFigure } from "./guide-figure.js";
|
|
5
6
|
import { GuideToc } from "./guide-toc.js";
|
|
6
7
|
import { GUIDES_LABELS } from "./library.js";
|
|
7
8
|
/**
|
|
@@ -9,11 +10,12 @@ import { GUIDES_LABELS } from "./library.js";
|
|
|
9
10
|
* the title and lede, the article drawn by `GuideMarkdown`, and "On this page" — a rail that
|
|
10
11
|
* follows the reader on wide screens, a collapsible list above the article on narrow ones. The
|
|
11
12
|
* contents use the same heading ids the renderer gives, so `#<heading id>` links land.
|
|
12
|
-
*
|
|
13
|
+
* Registered pictures are drawn where the guide names them, each opening full-size.
|
|
14
|
+
* Server-safe; the contents and the zoom are the only client parts.
|
|
13
15
|
*/
|
|
14
|
-
export function GuideArticle({ guide, guidesPath, indexSlug, category, labels, classNames }) {
|
|
16
|
+
export function GuideArticle({ guide, guidesPath, indexSlug, category, labels, classNames, figures, zoom, zoomClassNames, }) {
|
|
15
17
|
const text = { ...GUIDES_LABELS, ...labels };
|
|
16
18
|
const headings = extractHeadings(guide.body);
|
|
17
|
-
return (_jsxs("div", { className: "xl:grid xl:grid-cols-[minmax(0,1fr)_13rem] xl:gap-12", children: [_jsxs("article", { className: "max-w-3xl min-w-0", children: [_jsxs(Link, { href: guidesPath, className: "inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground", children: [_jsx(ChevronLeft, { className: "size-4", "aria-hidden": true }), " ", text.allGuides] }), category ? (_jsx("p", { className: "mt-5 text-xs font-bold tracking-[0.14em] text-primary uppercase", children: category })) : null, _jsx("h1", { className: "mt-2 text-3xl font-bold tracking-tight text-foreground sm:text-4xl", children: guide.title }), guide.lede ? _jsx("p", { className: "mt-3 text-lg text-muted-foreground", children: guide.lede }) : null, _jsx("div", { className: "mt-8", children: _jsx(GuideToc, { headings: headings, variant: "inline", labels: labels }) }), _jsx(GuideMarkdown, { content: guide.body, guidesPath: guidesPath, indexSlug: indexSlug, classNames: classNames })] }), _jsx(GuideToc, { headings: headings, variant: "rail", labels: labels })] }));
|
|
19
|
+
return (_jsxs("div", { className: "xl:grid xl:grid-cols-[minmax(0,1fr)_13rem] xl:gap-12", children: [_jsxs("article", { className: "max-w-3xl min-w-0", children: [_jsxs(Link, { href: guidesPath, className: "inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground", children: [_jsx(ChevronLeft, { className: "size-4", "aria-hidden": true }), " ", text.allGuides] }), category ? (_jsx("p", { className: "mt-5 text-xs font-bold tracking-[0.14em] text-primary uppercase", children: category })) : null, _jsx("h1", { className: "mt-2 text-3xl font-bold tracking-tight text-foreground sm:text-4xl", children: guide.title }), guide.lede ? _jsx("p", { className: "mt-3 text-lg text-muted-foreground", children: guide.lede }) : null, _jsx("div", { className: "mt-8", children: _jsx(GuideToc, { headings: headings, variant: "inline", labels: labels }) }), _jsx(GuideMarkdown, { content: guide.body, guidesPath: guidesPath, indexSlug: indexSlug, classNames: classNames, figures: figures, renderFigure: (figure) => (_jsx(GuideFigure, { figure: figure, figures: figures, zoom: zoom, labels: labels, classNames: classNames?.figure, zoomClassNames: zoomClassNames })) })] }), _jsx(GuideToc, { headings: headings, variant: "rail", labels: labels })] }));
|
|
18
20
|
}
|
|
19
21
|
//# sourceMappingURL=guide-article.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guide-article.js","sourceRoot":"","sources":["../../src/guides/guide-article.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C,OAAO,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"guide-article.js","sourceRoot":"","sources":["../../src/guides/guide-article.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C,OAAO,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAEhD,OAAO,EAAE,eAAe,EAAE,aAAa,EAAwB,MAAM,sBAAsB,CAAC;AAC5F,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAqB,MAAM,cAAc,CAAC;AAuBhE;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,EAC3B,KAAK,EACL,UAAU,EACV,SAAS,EACT,QAAQ,EACR,MAAM,EACN,UAAU,EACV,OAAO,EACP,IAAI,EACJ,cAAc,GACI;IAClB,MAAM,IAAI,GAAG,EAAE,GAAG,aAAa,EAAE,GAAG,MAAM,EAAE,CAAC;IAC7C,MAAM,QAAQ,GAAG,eAAe,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAE7C,OAAO,CACL,eAAK,SAAS,EAAC,sDAAsD,aACnE,mBAAS,SAAS,EAAC,mBAAmB,aACpC,MAAC,IAAI,IACH,IAAI,EAAE,UAAU,EAChB,SAAS,EAAC,oFAAoF,aAE9F,KAAC,WAAW,IAAC,SAAS,EAAC,QAAQ,wBAAe,OAAE,IAAI,CAAC,SAAS,IACzD,EACN,QAAQ,CAAC,CAAC,CAAC,CACV,YAAG,SAAS,EAAC,iEAAiE,YAAE,QAAQ,GAAK,CAC9F,CAAC,CAAC,CAAC,IAAI,EACR,aAAI,SAAS,EAAC,oEAAoE,YAAE,KAAK,CAAC,KAAK,GAAM,EACpG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,YAAG,SAAS,EAAC,oCAAoC,YAAE,KAAK,CAAC,IAAI,GAAK,CAAC,CAAC,CAAC,IAAI,EACvF,cAAK,SAAS,EAAC,MAAM,YACnB,KAAC,QAAQ,IAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAC,QAAQ,EAAC,MAAM,EAAE,MAAM,GAAI,GAC7D,EACN,KAAC,aAAa,IACZ,OAAO,EAAE,KAAK,CAAC,IAAI,EACnB,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,SAAS,EACpB,UAAU,EAAE,UAAU,EACtB,OAAO,EAAE,OAAO,EAChB,YAAY,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,CACxB,KAAC,WAAW,IACV,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,UAAU,EAAE,MAAM,EAC9B,cAAc,EAAE,cAAc,GAC9B,CACH,GACD,IACM,EACV,KAAC,QAAQ,IAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAC,MAAM,EAAC,MAAM,EAAE,MAAM,GAAI,IAC3D,CACP,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import { type GuidesLabels } from "./library.js";
|
|
3
|
+
/** Every element the zoom draws. Each value replaces the default for that element. */
|
|
4
|
+
export interface GuideZoomClassNames {
|
|
5
|
+
/** The button around the picture in the article. */
|
|
6
|
+
trigger: string;
|
|
7
|
+
/** "Click to enlarge", over the picture's corner. */
|
|
8
|
+
hint: string;
|
|
9
|
+
overlay: string;
|
|
10
|
+
/** The dialog: the whole screen on a phone, inset on wider ones. */
|
|
11
|
+
content: string;
|
|
12
|
+
toolbar: string;
|
|
13
|
+
instructions: string;
|
|
14
|
+
button: string;
|
|
15
|
+
/** The zoom level, a button that switches between fit and full size. */
|
|
16
|
+
level: string;
|
|
17
|
+
/** The area the picture is zoomed and dragged in. */
|
|
18
|
+
stage: string;
|
|
19
|
+
/** Under the picture: the caption and the alt text. */
|
|
20
|
+
footer: string;
|
|
21
|
+
caption: string;
|
|
22
|
+
alt: string;
|
|
23
|
+
}
|
|
24
|
+
export declare const GUIDE_ZOOM_CLASS_NAMES: GuideZoomClassNames;
|
|
25
|
+
export interface GuideFigureZoomProps {
|
|
26
|
+
/** The picture as the article draws it: the button's content. */
|
|
27
|
+
children: ReactNode;
|
|
28
|
+
/** What the dialog shows, laid out at the natural size and scaled. Default: `children` again. */
|
|
29
|
+
full?: ReactNode;
|
|
30
|
+
/**
|
|
31
|
+
* The picture's natural size in CSS pixels — a screenshot's registered size, so "full size"
|
|
32
|
+
* is one image pixel to one CSS pixel. Left out (a diagram), it is measured from the article's
|
|
33
|
+
* picture when the dialog opens, and full size is twice the fit.
|
|
34
|
+
*/
|
|
35
|
+
width?: number;
|
|
36
|
+
height?: number;
|
|
37
|
+
alt: string;
|
|
38
|
+
caption?: string;
|
|
39
|
+
labels?: Partial<GuidesLabels>;
|
|
40
|
+
classNames?: Partial<GuideZoomClassNames>;
|
|
41
|
+
}
|
|
42
|
+
interface Size {
|
|
43
|
+
w: number;
|
|
44
|
+
h: number;
|
|
45
|
+
}
|
|
46
|
+
/** Each button press or key multiplies or divides the zoom by this. */
|
|
47
|
+
export declare const ZOOM_STEP = 1.25;
|
|
48
|
+
/** The scale that fits `natural` inside `stage`, never past full size for a picture with a natural size. */
|
|
49
|
+
export declare function fitScale(natural: Size, stage: Size, capAtFull: boolean): number;
|
|
50
|
+
/**
|
|
51
|
+
* A guide's picture, opened full-size: a button around the article's picture (with a "Click to
|
|
52
|
+
* enlarge" hint) that opens a dialog fitting it to the screen. In there it zooms with the
|
|
53
|
+
* buttons, `+` and `−`, ⌘/Ctrl + scroll (a trackpad pinch) and a touch pinch; drags to pan
|
|
54
|
+
* when it is bigger than the screen (and scrolls, and moves with the arrow keys); a double-click
|
|
55
|
+
* or double-tap switches between fit and full size, and `0` fits again. Escape closes it and
|
|
56
|
+
* focus goes back to the picture. The caption and alt text are shown under it.
|
|
57
|
+
*
|
|
58
|
+
* Zooming eases unless the reader prefers reduced motion; dragging and pinching never do.
|
|
59
|
+
*/
|
|
60
|
+
export declare function GuideFigureZoom({ children, full, width, height, alt, caption, labels, classNames, }: GuideFigureZoomProps): import("react").JSX.Element;
|
|
61
|
+
export {};
|
|
62
|
+
//# sourceMappingURL=guide-figure-zoom.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guide-figure-zoom.d.ts","sourceRoot":"","sources":["../../src/guides/guide-figure-zoom.tsx"],"names":[],"mappings":"AAIA,OAAO,EAOL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAGf,OAAO,EAAiB,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAEhE,sFAAsF;AACtF,MAAM,WAAW,mBAAmB;IAClC,oDAAoD;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,wEAAwE;IACxE,KAAK,EAAE,MAAM,CAAC;IACd,qDAAqD;IACrD,KAAK,EAAE,MAAM,CAAC;IACd,uDAAuD;IACvD,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,eAAO,MAAM,sBAAsB,EAAE,mBAiBpC,CAAC;AAEF,MAAM,WAAW,oBAAoB;IACnC,iEAAiE;IACjE,QAAQ,EAAE,SAAS,CAAC;IACpB,iGAAiG;IACjG,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;IAC/B,UAAU,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,CAAC;CAC3C;AAED,UAAU,IAAI;IACZ,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAMD,uEAAuE;AACvE,eAAO,MAAM,SAAS,OAAO,CAAC;AAS9B,4GAA4G;AAC5G,wBAAgB,QAAQ,CAAC,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,GAAG,MAAM,CAM/E;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,EAC9B,QAAQ,EACR,IAAI,EACJ,KAAK,EACL,MAAM,EACN,GAAG,EACH,OAAO,EACP,MAAM,EACN,UAAU,GACX,EAAE,oBAAoB,+BAuVtB"}
|