@cfasim-ui/docs 0.3.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/LICENSE +201 -0
  2. package/charts/ChartMenu/ChartMenu.vue +140 -0
  3. package/charts/ChartMenu/download.ts +44 -0
  4. package/charts/ChartTooltip/ChartTooltip.vue +97 -0
  5. package/charts/ChoroplethMap/ChoroplethMap.md +398 -0
  6. package/charts/ChoroplethMap/ChoroplethMap.vue +777 -0
  7. package/charts/ChoroplethMap/hsaMapping.ts +4116 -0
  8. package/charts/DataTable/DataTable.md +143 -0
  9. package/charts/DataTable/DataTable.vue +277 -0
  10. package/charts/LineChart/LineChart.md +472 -0
  11. package/charts/LineChart/LineChart.vue +1216 -0
  12. package/charts/index.ts +23 -0
  13. package/charts/tooltip-position.ts +49 -0
  14. package/components/Box/Box.md +49 -0
  15. package/components/Box/Box.vue +52 -0
  16. package/components/Button/Button.md +67 -0
  17. package/components/Button/Button.vue +81 -0
  18. package/components/Expander/Expander.md +34 -0
  19. package/components/Expander/Expander.vue +95 -0
  20. package/components/Hint/Hint.md +29 -0
  21. package/components/Hint/Hint.vue +83 -0
  22. package/components/Icon/Icon.md +67 -0
  23. package/components/Icon/Icon.vue +112 -0
  24. package/components/LightDarkToggle/LightDarkToggle.vue +49 -0
  25. package/components/NumberInput/NumberInput.md +305 -0
  26. package/components/NumberInput/NumberInput.vue +531 -0
  27. package/components/SelectBox/SelectBox.md +110 -0
  28. package/components/SelectBox/SelectBox.vue +195 -0
  29. package/components/SidebarLayout/SidebarLayout.md +104 -0
  30. package/components/SidebarLayout/SidebarLayout.vue +466 -0
  31. package/components/Spinner/Spinner.md +51 -0
  32. package/components/Spinner/Spinner.vue +55 -0
  33. package/components/TextInput/TextInput.md +82 -0
  34. package/components/TextInput/TextInput.vue +94 -0
  35. package/components/Toggle/Toggle.md +81 -0
  36. package/components/Toggle/Toggle.vue +81 -0
  37. package/components/index.ts +15 -0
  38. package/index.json +121 -0
  39. package/package.json +24 -0
  40. package/pyodide/index.ts +7 -0
  41. package/pyodide/pyodide.worker.ts +233 -0
  42. package/pyodide/pyodideWorkerApi.ts +102 -0
  43. package/pyodide/useModel.ts +86 -0
  44. package/pyodide/vitePlugin.js +51 -0
  45. package/shared/ModelOutput.ts +88 -0
  46. package/shared/csv.ts +22 -0
  47. package/shared/index.ts +24 -0
  48. package/shared/transferUtils.ts +126 -0
  49. package/shared/useUrlParams.ts +296 -0
  50. package/theme/all.js +5 -0
  51. package/theme/base.css +176 -0
  52. package/theme/cfasim.css +3 -0
  53. package/theme/theme.css +113 -0
  54. package/theme/themes/cdc.css +22 -0
  55. package/theme/utilities.css +518 -0
  56. package/wasm/index.ts +2 -0
  57. package/wasm/useModel.ts +53 -0
  58. package/wasm/vitePlugin.js +35 -0
  59. package/wasm/wasm.worker.ts +74 -0
  60. package/wasm/wasmWorkerApi.ts +38 -0
@@ -0,0 +1,94 @@
1
+ <script setup lang="ts">
2
+ import { ref, watch } from "vue";
3
+ import Hint from "../Hint/Hint.vue";
4
+
5
+ const model = defineModel<string>();
6
+ const local = ref(model.value);
7
+
8
+ watch(model, (v) => {
9
+ local.value = v;
10
+ });
11
+
12
+ function commit() {
13
+ model.value = local.value;
14
+ }
15
+
16
+ const props = defineProps<{
17
+ label?: string;
18
+ hideLabel?: boolean;
19
+ placeholder?: string;
20
+ hint?: string;
21
+ }>();
22
+ </script>
23
+
24
+ <template>
25
+ <label v-if="props.label" class="input-label">
26
+ <span
27
+ class="input-label-row"
28
+ :class="{ 'visually-hidden': props.hideLabel }"
29
+ >
30
+ {{ props.label }}
31
+ <Hint v-if="props.hint && !props.hideLabel" :text="props.hint" />
32
+ </span>
33
+ <input
34
+ type="text"
35
+ v-model="local"
36
+ :placeholder="props.placeholder"
37
+ @blur="commit"
38
+ @keydown.enter="commit"
39
+ />
40
+ </label>
41
+ <div v-else>
42
+ <input
43
+ type="text"
44
+ v-model="local"
45
+ :placeholder="props.placeholder"
46
+ @blur="commit"
47
+ @keydown.enter="commit"
48
+ />
49
+ </div>
50
+ </template>
51
+
52
+ <style scoped>
53
+ .input-label {
54
+ display: flex;
55
+ flex-direction: column;
56
+ gap: 0.25em;
57
+ font-size: var(--font-size-sm);
58
+ }
59
+
60
+ .input-label-row {
61
+ display: flex;
62
+ align-items: center;
63
+ justify-content: space-between;
64
+ }
65
+
66
+ input {
67
+ display: block;
68
+ width: 100%;
69
+ height: 2.5em;
70
+ padding: 0 0.75em;
71
+ font-size: inherit;
72
+ background-color: var(--color-bg-0);
73
+ color: var(--color-text);
74
+ border: 1px solid var(--color-border);
75
+ border-radius: 0.375em;
76
+ transition:
77
+ border-color var(--transition-fast),
78
+ box-shadow var(--transition-fast);
79
+ }
80
+
81
+ input:hover {
82
+ border-color: var(--color-border-hover);
83
+ }
84
+
85
+ input:focus {
86
+ outline: none;
87
+ border-color: var(--color-border-focus);
88
+ box-shadow: var(--shadow-focus);
89
+ }
90
+
91
+ input::placeholder {
92
+ color: var(--color-text-tertiary);
93
+ }
94
+ </style>
@@ -0,0 +1,81 @@
1
+ # Toggle
2
+
3
+ A boolean switch built on reka-ui.
4
+
5
+ ## Examples
6
+
7
+ <script setup>
8
+ import { ref } from 'vue'
9
+ const enabled = ref(false)
10
+ const disabled = ref(true)
11
+ </script>
12
+
13
+ ### Basic
14
+
15
+ <ComponentDemo>
16
+ <Toggle v-model="enabled" label="Enable vaccination" />
17
+
18
+ <template #code>
19
+
20
+ ```vue
21
+ <script setup>
22
+ import { ref } from "vue";
23
+ const enabled = ref(false);
24
+ </script>
25
+
26
+ <Toggle v-model="enabled" label="Enable vaccination" />
27
+ ```
28
+
29
+ </template>
30
+ </ComponentDemo>
31
+
32
+ ### With hint
33
+
34
+ <ComponentDemo>
35
+ <Toggle
36
+ v-model="enabled"
37
+ label="Enable isolation"
38
+ hint="Whether symptomatic individuals are isolated"
39
+ />
40
+
41
+ <template #code>
42
+
43
+ ```vue
44
+ <Toggle
45
+ v-model="enabled"
46
+ label="Enable isolation"
47
+ hint="Whether symptomatic individuals are isolated"
48
+ />
49
+ ```
50
+
51
+ </template>
52
+ </ComponentDemo>
53
+
54
+ ### Disabled
55
+
56
+ <ComponentDemo>
57
+ <Toggle v-model="disabled" label="Locked setting" :disabled="true" />
58
+
59
+ <template #code>
60
+
61
+ ```vue
62
+ <Toggle v-model="value" label="Locked setting" :disabled="true" />
63
+ ```
64
+
65
+ </template>
66
+ </ComponentDemo>
67
+
68
+ ## Model
69
+
70
+ | Name | Type |
71
+ |------|------|
72
+ | `v-model` | `boolean` |
73
+
74
+ ## Props
75
+
76
+ | Prop | Type | Required | Default |
77
+ |------|------|----------|---------|
78
+ | `label` | `string` | Yes | — |
79
+ | `hint` | `string` | No | — |
80
+ | `disabled` | `boolean` | No | — |
81
+
@@ -0,0 +1,81 @@
1
+ <script setup lang="ts">
2
+ import { SwitchRoot, SwitchThumb, useId } from "reka-ui";
3
+ import Hint from "../Hint/Hint.vue";
4
+
5
+ const model = defineModel<boolean>();
6
+
7
+ const props = defineProps<{
8
+ label: string;
9
+ hint?: string;
10
+ disabled?: boolean;
11
+ }>();
12
+
13
+ const id = useId();
14
+ </script>
15
+
16
+ <template>
17
+ <div class="toggle">
18
+ <label :for="id">{{ label }}</label>
19
+ <Hint v-if="props.hint" :text="props.hint" />
20
+ <SwitchRoot :id="id" v-model="model" :disabled="disabled" class="switch">
21
+ <SwitchThumb class="thumb" />
22
+ </SwitchRoot>
23
+ </div>
24
+ </template>
25
+
26
+ <style scoped>
27
+ .toggle {
28
+ display: flex;
29
+ align-items: center;
30
+ gap: 0.5em;
31
+ }
32
+
33
+ .toggle label {
34
+ user-select: none;
35
+ }
36
+
37
+ .switch {
38
+ --switch-w: 2.25em;
39
+ --switch-h: 1.25em;
40
+ --thumb-size: 1em;
41
+ --thumb-offset: 0.125em;
42
+
43
+ position: relative;
44
+ width: var(--switch-w);
45
+ height: var(--switch-h);
46
+ flex-shrink: 0;
47
+ background: var(--color-border-hover);
48
+ border-radius: var(--switch-h);
49
+ border: none;
50
+ padding: 0;
51
+ cursor: pointer;
52
+ transition: background 0.15s;
53
+ }
54
+
55
+ .switch[data-state="checked"] {
56
+ background: var(--color-primary);
57
+ }
58
+
59
+ .switch[data-disabled] {
60
+ opacity: 0.5;
61
+ cursor: not-allowed;
62
+ }
63
+
64
+ .thumb {
65
+ display: block;
66
+ width: var(--thumb-size);
67
+ height: var(--thumb-size);
68
+ background: white;
69
+ border-radius: 50%;
70
+ position: absolute;
71
+ top: var(--thumb-offset);
72
+ left: var(--thumb-offset);
73
+ transition: transform 0.15s;
74
+ }
75
+
76
+ .switch[data-state="checked"] .thumb {
77
+ transform: translateX(
78
+ calc(var(--switch-w) - var(--thumb-size) - var(--thumb-offset) * 2)
79
+ );
80
+ }
81
+ </style>
@@ -0,0 +1,15 @@
1
+ export { default as Box } from "./Box/Box.vue";
2
+ export type { BoxVariant } from "./Box/Box.vue";
3
+ export { default as Button } from "./Button/Button.vue";
4
+ export { default as Expander } from "./Expander/Expander.vue";
5
+ export { default as Hint } from "./Hint/Hint.vue";
6
+ export { default as Icon } from "./Icon/Icon.vue";
7
+ export { default as LightDarkToggle } from "./LightDarkToggle/LightDarkToggle.vue";
8
+ export { default as NumberInput } from "./NumberInput/NumberInput.vue";
9
+ export { default as SelectBox } from "./SelectBox/SelectBox.vue";
10
+ export type { SelectOption } from "./SelectBox/SelectBox.vue";
11
+ export { default as SidebarLayout } from "./SidebarLayout/SidebarLayout.vue";
12
+ export type { Tab } from "./SidebarLayout/SidebarLayout.vue";
13
+ export { default as Spinner } from "./Spinner/Spinner.vue";
14
+ export { default as TextInput } from "./TextInput/TextInput.vue";
15
+ export { default as Toggle } from "./Toggle/Toggle.vue";
package/index.json ADDED
@@ -0,0 +1,121 @@
1
+ {
2
+ "version": "0.3.11",
3
+ "package": "@cfasim-ui/docs",
4
+ "content": {
5
+ "components": [
6
+ {
7
+ "name": "Box",
8
+ "slug": "box",
9
+ "docs": "components/Box/Box.md",
10
+ "source": "components/Box/Box.vue",
11
+ "keywords": []
12
+ },
13
+ {
14
+ "name": "Button",
15
+ "slug": "button",
16
+ "docs": "components/Button/Button.md",
17
+ "source": "components/Button/Button.vue",
18
+ "keywords": [
19
+ "button",
20
+ "click",
21
+ "action",
22
+ "primary",
23
+ "secondary"
24
+ ]
25
+ },
26
+ {
27
+ "name": "Expander",
28
+ "slug": "expander",
29
+ "docs": "components/Expander/Expander.md",
30
+ "source": "components/Expander/Expander.vue",
31
+ "keywords": []
32
+ },
33
+ {
34
+ "name": "Hint",
35
+ "slug": "hint",
36
+ "docs": "components/Hint/Hint.md",
37
+ "source": "components/Hint/Hint.vue",
38
+ "keywords": []
39
+ },
40
+ {
41
+ "name": "Icon",
42
+ "slug": "icon",
43
+ "docs": "components/Icon/Icon.md",
44
+ "source": "components/Icon/Icon.vue",
45
+ "keywords": []
46
+ },
47
+ {
48
+ "name": "NumberInput",
49
+ "slug": "number-input",
50
+ "docs": "components/NumberInput/NumberInput.md",
51
+ "source": "components/NumberInput/NumberInput.vue",
52
+ "keywords": []
53
+ },
54
+ {
55
+ "name": "SelectBox",
56
+ "slug": "select-box",
57
+ "docs": "components/SelectBox/SelectBox.md",
58
+ "source": "components/SelectBox/SelectBox.vue",
59
+ "keywords": []
60
+ },
61
+ {
62
+ "name": "SidebarLayout",
63
+ "slug": "sidebar-layout",
64
+ "docs": "components/SidebarLayout/SidebarLayout.md",
65
+ "source": "components/SidebarLayout/SidebarLayout.vue",
66
+ "keywords": []
67
+ },
68
+ {
69
+ "name": "Spinner",
70
+ "slug": "spinner",
71
+ "docs": "components/Spinner/Spinner.md",
72
+ "source": "components/Spinner/Spinner.vue",
73
+ "keywords": []
74
+ },
75
+ {
76
+ "name": "TextInput",
77
+ "slug": "text-input",
78
+ "docs": "components/TextInput/TextInput.md",
79
+ "source": "components/TextInput/TextInput.vue",
80
+ "keywords": []
81
+ },
82
+ {
83
+ "name": "Toggle",
84
+ "slug": "toggle",
85
+ "docs": "components/Toggle/Toggle.md",
86
+ "source": "components/Toggle/Toggle.vue",
87
+ "keywords": []
88
+ }
89
+ ],
90
+ "charts": [
91
+ {
92
+ "name": "ChoroplethMap",
93
+ "slug": "choropleth-map",
94
+ "docs": "charts/ChoroplethMap/ChoroplethMap.md",
95
+ "source": "charts/ChoroplethMap/ChoroplethMap.vue",
96
+ "keywords": []
97
+ },
98
+ {
99
+ "name": "LineChart",
100
+ "slug": "line-chart",
101
+ "docs": "charts/LineChart/LineChart.md",
102
+ "source": "charts/LineChart/LineChart.vue",
103
+ "keywords": [
104
+ "line",
105
+ "chart",
106
+ "time-series",
107
+ "series",
108
+ "axis",
109
+ "svg"
110
+ ]
111
+ },
112
+ {
113
+ "name": "DataTable",
114
+ "slug": "data-table",
115
+ "docs": "charts/DataTable/DataTable.md",
116
+ "source": "charts/DataTable/DataTable.vue",
117
+ "keywords": []
118
+ }
119
+ ]
120
+ }
121
+ }
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@cfasim-ui/docs",
3
+ "version": "0.3.11",
4
+ "description": "LLM-friendly component and chart documentation for cfasim-ui",
5
+ "license": "Apache-2.0",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/CDCgov/cfa-simulator.git",
9
+ "directory": "cfasim-ui/docs"
10
+ },
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
14
+ "files": [
15
+ "index.json",
16
+ "components",
17
+ "charts",
18
+ "shared",
19
+ "pyodide",
20
+ "wasm",
21
+ "theme"
22
+ ],
23
+ "scripts": {}
24
+ }
@@ -0,0 +1,7 @@
1
+ export {
2
+ asyncRunPython,
3
+ loadModule,
4
+ loadModuleOnWorker,
5
+ type WorkerName,
6
+ } from "./pyodideWorkerApi.js";
7
+ export { useModel } from "./useModel.js";
@@ -0,0 +1,233 @@
1
+ import {
2
+ postWithTransfer,
3
+ postModelOutputsWithTransfer,
4
+ postErrorWithTransfer,
5
+ } from "@cfasim-ui/shared";
6
+ import type { ColumnDescriptor, ModelOutputsWire } from "@cfasim-ui/shared";
7
+
8
+ interface WorkerMessage {
9
+ id: number;
10
+ type?: "run" | "loadModule";
11
+ python?: string;
12
+ module?: string;
13
+ context?: Record<string, unknown>;
14
+ }
15
+
16
+ let wheelMap: Record<string, string> = {};
17
+
18
+ const baseUrl = import.meta.env.BASE_URL ?? "/";
19
+
20
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
21
+ let micropip: any;
22
+
23
+ const pyodideReadyPromise = (async () => {
24
+ // @ts-expect-error - Pyodide types from CDN
25
+ const { loadPyodide } = await import(
26
+ /* @vite-ignore */ "https://cdn.jsdelivr.net/pyodide/v0.29.3/full/pyodide.mjs"
27
+ );
28
+ // Load micropip and numpy in parallel with Pyodide bootstrap
29
+ const pyodide = await loadPyodide({
30
+ packages: ["micropip", "numpy"],
31
+ });
32
+
33
+ micropip = pyodide.pyimport("micropip");
34
+
35
+ // Build module→wheel lookup but don't install yet
36
+ const res = await fetch(`${self.location.origin}${baseUrl}wheels.json`);
37
+ const wheelFiles: string[] = await res.json();
38
+ for (const filename of wheelFiles) {
39
+ const moduleName = filename.split("-")[0];
40
+ wheelMap[moduleName] = filename;
41
+ }
42
+
43
+ return pyodide;
44
+ })();
45
+
46
+ let installPromise: Promise<void> | null = null;
47
+
48
+ function installAllWheels(): Promise<void> {
49
+ if (!installPromise) {
50
+ installPromise = (async () => {
51
+ const urls = Object.values(wheelMap).map(
52
+ (f) => `${self.location.origin}${baseUrl}${f}`,
53
+ );
54
+ if (urls.length > 0) {
55
+ await micropip.install(urls);
56
+ }
57
+ })();
58
+ installPromise.catch(() => {
59
+ installPromise = null;
60
+ });
61
+ }
62
+ return installPromise;
63
+ }
64
+
65
+ const modulePromises = new Map<string, Promise<void>>();
66
+
67
+ function ensureModule(
68
+ pyodide: Awaited<typeof pyodideReadyPromise>,
69
+ moduleName: string,
70
+ ): Promise<void> {
71
+ if (!modulePromises.has(moduleName)) {
72
+ if (!wheelMap[moduleName]) throw new Error(`Unknown module: ${moduleName}`);
73
+ const promise = (async () => {
74
+ await installAllWheels();
75
+ pyodide.pyimport(moduleName);
76
+ })();
77
+ promise.catch(() => {
78
+ modulePromises.delete(moduleName);
79
+ });
80
+ modulePromises.set(moduleName, promise);
81
+ }
82
+ return modulePromises.get(moduleName)!;
83
+ }
84
+
85
+ // Map Python struct format characters to TypedArray constructors
86
+ const FORMAT_TO_TYPED_ARRAY: Record<
87
+ string,
88
+ new (buffer: ArrayBuffer) => ArrayBufferView
89
+ > = {
90
+ b: Int8Array,
91
+ B: Uint8Array,
92
+ h: Int16Array,
93
+ H: Uint16Array,
94
+ i: Int32Array,
95
+ I: Uint32Array,
96
+ f: Float32Array,
97
+ d: Float64Array,
98
+ };
99
+
100
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
101
+ function extractNumpyBuffer(proxy: any): ArrayBuffer {
102
+ const pyBuffer = proxy.getBuffer();
103
+ const Ctor = FORMAT_TO_TYPED_ARRAY[pyBuffer.format] ?? Float64Array;
104
+ const typed = new Ctor(
105
+ pyBuffer.data.buffer.slice(
106
+ pyBuffer.data.byteOffset,
107
+ pyBuffer.data.byteOffset + pyBuffer.data.byteLength,
108
+ ),
109
+ );
110
+ pyBuffer.release();
111
+ return typed.buffer as ArrayBuffer;
112
+ }
113
+
114
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
115
+ function convertModelOutputs(jsResult: any): ModelOutputsWire | null {
116
+ if (!jsResult || typeof jsResult !== "object" || !jsResult.__modelOutputs) {
117
+ return null;
118
+ }
119
+
120
+ const outputs: ModelOutputsWire["outputs"] = {};
121
+ for (const [key, outputProxy] of Object.entries(
122
+ jsResult.outputs as Record<string, Record<string, unknown>>,
123
+ )) {
124
+ const wire = outputProxy as {
125
+ length: number;
126
+ columns: { name: string; type: string; enumLabels?: string[] }[];
127
+ buffers: unknown[];
128
+ };
129
+
130
+ const buffers: ArrayBuffer[] = [];
131
+ for (const buf of wire.buffers) {
132
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
133
+ if (buf && typeof buf === "object" && (buf as any).getBuffer) {
134
+ buffers.push(extractNumpyBuffer(buf));
135
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
136
+ if ((buf as any).destroy) (buf as any).destroy();
137
+ } else if (buf instanceof ArrayBuffer) {
138
+ buffers.push(buf);
139
+ } else if (ArrayBuffer.isView(buf)) {
140
+ buffers.push(
141
+ buf.buffer.slice(
142
+ buf.byteOffset,
143
+ buf.byteOffset + buf.byteLength,
144
+ ) as ArrayBuffer,
145
+ );
146
+ }
147
+ }
148
+
149
+ outputs[key] = {
150
+ __modelOutput: true,
151
+ length: wire.length,
152
+ columns: wire.columns as ColumnDescriptor[],
153
+ buffers,
154
+ };
155
+ }
156
+
157
+ return { __modelOutputs: true, outputs };
158
+ }
159
+
160
+ self.onmessage = async (event: MessageEvent<WorkerMessage>) => {
161
+ const pyodide = await pyodideReadyPromise;
162
+ const { id, type, python, module: moduleName, context } = event.data;
163
+
164
+ try {
165
+ if (type === "loadModule" && moduleName) {
166
+ await ensureModule(pyodide, moduleName);
167
+ postWithTransfer(self, id, true);
168
+ return;
169
+ }
170
+
171
+ let globals;
172
+ if (context) {
173
+ const dict = pyodide.globals.get("dict");
174
+ globals = dict(Object.entries(context));
175
+ }
176
+
177
+ const t0 = performance.now();
178
+
179
+ // Use synchronous runPython (models don't use top-level await)
180
+ const rawResult = pyodide.runPython(
181
+ python!,
182
+ globals ? { globals } : undefined,
183
+ );
184
+
185
+ const tPython = performance.now();
186
+
187
+ // Destroy PyProxy if returned to prevent memory leaks
188
+ let result = rawResult;
189
+ if (rawResult && typeof rawResult === "object" && rawResult.destroy) {
190
+ if (rawResult.toJs) {
191
+ result = rawResult.toJs({ dict_converter: Object.fromEntries });
192
+ } else if (rawResult.getBuffer) {
193
+ // Single numpy array: use getBuffer() for direct typed array access
194
+ const pyBuffer = rawResult.getBuffer();
195
+ const Ctor = FORMAT_TO_TYPED_ARRAY[pyBuffer.format] ?? Float64Array;
196
+ result = new Ctor(
197
+ pyBuffer.data.buffer.slice(
198
+ pyBuffer.data.byteOffset,
199
+ pyBuffer.data.byteOffset + pyBuffer.data.byteLength,
200
+ ),
201
+ );
202
+ pyBuffer.release();
203
+ } else {
204
+ result = rawResult.toString();
205
+ }
206
+ rawResult.destroy();
207
+ }
208
+
209
+ // Destroy globals proxy if created
210
+ if (globals && globals.destroy) {
211
+ globals.destroy();
212
+ }
213
+
214
+ const tConvert = performance.now();
215
+ const bench = {
216
+ python_ms: Math.round((tPython - t0) * 10) / 10,
217
+ convert_ms: Math.round((tConvert - tPython) * 10) / 10,
218
+ };
219
+ console.log(
220
+ `[pyodide-worker] python=${bench.python_ms}ms convert=${bench.convert_ms}ms`,
221
+ );
222
+
223
+ // Check for ModelOutputs wire format
224
+ const modelOutputs = convertModelOutputs(result);
225
+ if (modelOutputs) {
226
+ postModelOutputsWithTransfer(self, id, modelOutputs);
227
+ } else {
228
+ postWithTransfer(self, id, result);
229
+ }
230
+ } catch (error) {
231
+ postErrorWithTransfer(self, id, error);
232
+ }
233
+ };