@pie-players/pie-assessment-toolkit 0.3.46 → 0.3.48

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 (86) hide show
  1. package/dist/attempt/AssessmentSession.js +2 -1
  2. package/dist/attempt/AssessmentSession.js.map +1 -1
  3. package/dist/attempt/adapters/activity-to-test-attempt-session.js +12 -8
  4. package/dist/attempt/adapters/activity-to-test-attempt-session.js.map +1 -1
  5. package/dist/components/ItemToolBar.custom-element.js +60 -21
  6. package/dist/components/PieAssessmentToolkit.custom-element.js +83 -28
  7. package/dist/components/SectionToolBar.custom-element.js +59 -21
  8. package/dist/index.d.ts +1 -1
  9. package/dist/index.js.map +1 -1
  10. package/dist/policy/core/pnp-policy-inputs.js +1 -2
  11. package/dist/policy/core/pnp-policy-inputs.js.map +1 -1
  12. package/dist/policy/core/provenance.js.map +1 -1
  13. package/dist/runtime/SectionRuntimeEngine.js +1 -1
  14. package/dist/runtime/SectionRuntimeEngine.js.map +1 -1
  15. package/dist/runtime/core/cohort.js.map +1 -1
  16. package/dist/runtime/core/engine-input.js.map +1 -1
  17. package/dist/runtime/core/engine-readiness.js.map +1 -1
  18. package/dist/runtime/core/engine-resolver.js.map +1 -1
  19. package/dist/runtime/core/engine-transition.js +1 -3
  20. package/dist/runtime/core/engine-transition.js.map +1 -1
  21. package/dist/runtime/registration-events.js.map +1 -1
  22. package/dist/runtime/session-event-emitter-policy.js.map +1 -1
  23. package/dist/services/HighlightCoordinator.js +2 -1
  24. package/dist/services/HighlightCoordinator.js.map +1 -1
  25. package/dist/services/I18nService.d.ts +1 -1
  26. package/dist/services/I18nService.js.map +1 -1
  27. package/dist/services/SSMLExtractor.js.map +1 -1
  28. package/dist/services/TTSService.js +5 -2
  29. package/dist/services/TTSService.js.map +1 -1
  30. package/dist/services/ThemeProvider.js.map +1 -1
  31. package/dist/services/ToolRegistry.d.ts +1 -1
  32. package/dist/services/ToolRegistry.js.map +1 -1
  33. package/dist/services/ToolkitCoordinator.js +21 -11
  34. package/dist/services/ToolkitCoordinator.js.map +1 -1
  35. package/dist/services/createDefaultToolRegistry.js +4 -2
  36. package/dist/services/createDefaultToolRegistry.js.map +1 -1
  37. package/dist/services/framework-error-bus.js.map +1 -1
  38. package/dist/services/framework-error.js.map +1 -1
  39. package/dist/services/interfaces.js.map +1 -1
  40. package/dist/services/section-controller-types.js.map +1 -1
  41. package/dist/services/tool-config-defaults.js +7 -1
  42. package/dist/services/tool-config-defaults.js.map +1 -1
  43. package/dist/services/tool-config-validation.js +3 -1
  44. package/dist/services/tool-config-validation.js.map +1 -1
  45. package/dist/services/tool-instance-id.js.map +1 -1
  46. package/dist/services/tool-providers/DesmosToolProvider.js.map +1 -1
  47. package/dist/services/tool-providers/TTSToolProvider.js.map +1 -1
  48. package/dist/services/toolbar-items.js.map +1 -1
  49. package/dist/services/tools-config-normalizer.js +1 -2
  50. package/dist/services/tools-config-normalizer.js.map +1 -1
  51. package/dist/services/tts/browser-provider.js +60 -18
  52. package/dist/services/tts/browser-provider.js.map +1 -1
  53. package/dist/services/tts/catalog-span-alignment.js +8 -3
  54. package/dist/services/tts/catalog-span-alignment.js.map +1 -1
  55. package/dist/services/tts/generated-speech/assemble-plan.js.map +1 -1
  56. package/dist/services/tts/generated-speech/index.d.ts +1 -1
  57. package/dist/services/tts/generated-speech/index.js +1 -1
  58. package/dist/services/tts/generated-speech/index.js.map +1 -1
  59. package/dist/services/tts/generated-speech/math-speech-cache.js +3 -1
  60. package/dist/services/tts/generated-speech/math-speech-cache.js.map +1 -1
  61. package/dist/services/tts/highlight-pipeline/boundary-normalizer.js.map +1 -1
  62. package/dist/services/tts/highlight-pipeline/create-highlight-plan.js.map +1 -1
  63. package/dist/services/tts/math-alignment/mathml-tokenizer.js.map +1 -1
  64. package/dist/services/tts/math-alignment/sequence-aligner.js.map +1 -1
  65. package/dist/services/tts/math-alignment/speech-tokenizer.js.map +1 -1
  66. package/dist/services/tts/math-aware-text-processing.js +6 -3
  67. package/dist/services/tts/math-aware-text-processing.js.map +1 -1
  68. package/dist/services/tts/math-speech.js +3 -1
  69. package/dist/services/tts/math-speech.js.map +1 -1
  70. package/dist/services/tts/visual-line-ranges.js.map +1 -1
  71. package/dist/services/tts-runtime-config.js +6 -2
  72. package/dist/services/tts-runtime-config.js.map +1 -1
  73. package/dist/tools/calculators/desmos-provider.js.map +1 -1
  74. package/dist/tools/default-tool-module-loaders.js.map +1 -1
  75. package/dist/tools/registrations/accessibility-tools.js.map +1 -1
  76. package/dist/tools/registrations/calculator.js +1 -1
  77. package/dist/tools/registrations/calculator.js.map +1 -1
  78. package/dist/tools/registrations/interaction-tools.js.map +1 -1
  79. package/dist/tools/registrations/measurement-tools.js.map +1 -1
  80. package/dist/tools/registrations/subject-specific-tools.js +1 -1
  81. package/dist/tools/registrations/subject-specific-tools.js.map +1 -1
  82. package/dist/tools/registrations/tts.js +4 -2
  83. package/dist/tools/registrations/tts.js.map +1 -1
  84. package/dist/tools/types.js.map +1 -1
  85. package/dist/tools/variant-resolver.js.map +1 -1
  86. package/package.json +7 -7
@@ -14,7 +14,8 @@ export function loadAssessmentSession(storage, assessmentAttemptSessionIdentifie
14
14
  const parsed = JSON.parse(raw);
15
15
  if (!parsed || parsed.version !== ASSESSMENT_SESSION_VERSION)
16
16
  return null;
17
- if (parsed.assessmentAttemptSessionIdentifier !== assessmentAttemptSessionIdentifier) {
17
+ if (parsed.assessmentAttemptSessionIdentifier !==
18
+ assessmentAttemptSessionIdentifier) {
18
19
  return null;
19
20
  }
20
21
  return parsed;
@@ -1 +1 @@
1
- {"version":3,"file":"AssessmentSession.js","sourceRoot":"","sources":["../../src/attempt/AssessmentSession.ts"],"names":[],"mappings":"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","sourcesContent":["import type { SectionControllerSessionState } from \"../services/section-controller-types.js\";\nimport type { StorageLike } from \"./TestSession.js\";\n\nexport interface AssessmentSessionNavigationState {\n\tcurrentSectionIndex: number;\n\tvisitedSectionIdentifiers: string[];\n\tcurrentSectionIdentifier?: string;\n}\n\nexport interface AssessmentSessionRealization {\n\tseed: string;\n\tsectionIdentifiers: string[];\n}\n\nexport interface AssessmentSectionSessionState {\n\tsectionIdentifier: string;\n\tupdatedAt: string;\n\tsession: SectionControllerSessionState | null;\n}\n\nexport interface AssessmentSession {\n\tversion: 1;\n\tassessmentAttemptSessionIdentifier: string;\n\tassessmentId: string;\n\tstartedAt: string;\n\tupdatedAt: string;\n\tcompletedAt?: string;\n\tnavigationState: AssessmentSessionNavigationState;\n\trealization: AssessmentSessionRealization;\n\tsectionSessions: Record<string, AssessmentSectionSessionState>;\n\tcontextVariables?: Record<string, unknown>;\n}\n\nconst ASSESSMENT_SESSION_VERSION = 1 as const;\nconst STORAGE_PREFIX = \"pie:assessment-session:v1:\";\n\nfunction nowIso(): string {\n\treturn new Date().toISOString();\n}\n\nexport function getAssessmentSessionStorageKey(\n\tassessmentAttemptSessionIdentifier: string,\n): string {\n\treturn `${STORAGE_PREFIX}${assessmentAttemptSessionIdentifier}`;\n}\n\nexport function loadAssessmentSession(\n\tstorage: StorageLike,\n\tassessmentAttemptSessionIdentifier: string,\n): AssessmentSession | null {\n\tconst raw = storage.getItem(\n\t\tgetAssessmentSessionStorageKey(assessmentAttemptSessionIdentifier),\n\t);\n\tif (!raw) return null;\n\ttry {\n\t\tconst parsed = JSON.parse(raw) as AssessmentSession;\n\t\tif (!parsed || parsed.version !== ASSESSMENT_SESSION_VERSION) return null;\n\t\tif (\n\t\t\tparsed.assessmentAttemptSessionIdentifier !== assessmentAttemptSessionIdentifier\n\t\t) {\n\t\t\treturn null;\n\t\t}\n\t\treturn parsed;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nexport function saveAssessmentSession(\n\tstorage: StorageLike,\n\tsession: AssessmentSession,\n): void {\n\tconst updated: AssessmentSession = {\n\t\t...session,\n\t\tupdatedAt: nowIso(),\n\t};\n\tstorage.setItem(\n\t\tgetAssessmentSessionStorageKey(session.assessmentAttemptSessionIdentifier),\n\t\tJSON.stringify(updated),\n\t);\n}\n\nexport function createNewAssessmentSession(args: {\n\tassessmentAttemptSessionIdentifier: string;\n\tassessmentId: string;\n\tseed: string;\n\tsectionIdentifiers: string[];\n}): AssessmentSession {\n\tconst startedAt = nowIso();\n\tconst initialSectionIdentifier = args.sectionIdentifiers[0];\n\treturn {\n\t\tversion: ASSESSMENT_SESSION_VERSION,\n\t\tassessmentAttemptSessionIdentifier: args.assessmentAttemptSessionIdentifier,\n\t\tassessmentId: args.assessmentId,\n\t\tstartedAt,\n\t\tupdatedAt: startedAt,\n\t\tnavigationState: {\n\t\t\tcurrentSectionIndex: 0,\n\t\t\tvisitedSectionIdentifiers: initialSectionIdentifier\n\t\t\t\t? [initialSectionIdentifier]\n\t\t\t\t: [],\n\t\t\tcurrentSectionIdentifier: initialSectionIdentifier,\n\t\t},\n\t\trealization: {\n\t\t\tseed: args.seed,\n\t\t\tsectionIdentifiers: args.sectionIdentifiers,\n\t\t},\n\t\tsectionSessions: {},\n\t};\n}\n\nexport function upsertSectionSession(\n\tsession: AssessmentSession,\n\targs: {\n\t\tsectionIdentifier: string;\n\t\tsectionSession: SectionControllerSessionState | null;\n\t},\n): AssessmentSession {\n\tif (!args.sectionIdentifier) return session;\n\tconst updatedAt = nowIso();\n\treturn {\n\t\t...session,\n\t\tupdatedAt,\n\t\tsectionSessions: {\n\t\t\t...session.sectionSessions,\n\t\t\t[args.sectionIdentifier]: {\n\t\t\t\tsectionIdentifier: args.sectionIdentifier,\n\t\t\t\tupdatedAt,\n\t\t\t\tsession: args.sectionSession,\n\t\t\t},\n\t\t},\n\t};\n}\n\nexport function setCurrentSectionPosition(\n\tsession: AssessmentSession,\n\targs: {\n\t\tcurrentSectionIndex: number;\n\t\tcurrentSectionIdentifier?: string;\n\t},\n): AssessmentSession {\n\tconst visited = new Set(session.navigationState.visitedSectionIdentifiers || []);\n\tif (args.currentSectionIdentifier) {\n\t\tvisited.add(args.currentSectionIdentifier);\n\t}\n\treturn {\n\t\t...session,\n\t\tnavigationState: {\n\t\t\t...session.navigationState,\n\t\t\tcurrentSectionIndex: args.currentSectionIndex,\n\t\t\tcurrentSectionIdentifier: args.currentSectionIdentifier,\n\t\t\tvisitedSectionIdentifiers: Array.from(visited),\n\t\t},\n\t};\n}\n"]}
1
+ {"version":3,"file":"AssessmentSession.js","sourceRoot":"","sources":["../../src/attempt/AssessmentSession.ts"],"names":[],"mappings":"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","sourcesContent":["import type { SectionControllerSessionState } from \"../services/section-controller-types.js\";\nimport type { StorageLike } from \"./TestSession.js\";\n\nexport interface AssessmentSessionNavigationState {\n\tcurrentSectionIndex: number;\n\tvisitedSectionIdentifiers: string[];\n\tcurrentSectionIdentifier?: string;\n}\n\nexport interface AssessmentSessionRealization {\n\tseed: string;\n\tsectionIdentifiers: string[];\n}\n\nexport interface AssessmentSectionSessionState {\n\tsectionIdentifier: string;\n\tupdatedAt: string;\n\tsession: SectionControllerSessionState | null;\n}\n\nexport interface AssessmentSession {\n\tversion: 1;\n\tassessmentAttemptSessionIdentifier: string;\n\tassessmentId: string;\n\tstartedAt: string;\n\tupdatedAt: string;\n\tcompletedAt?: string;\n\tnavigationState: AssessmentSessionNavigationState;\n\trealization: AssessmentSessionRealization;\n\tsectionSessions: Record<string, AssessmentSectionSessionState>;\n\tcontextVariables?: Record<string, unknown>;\n}\n\nconst ASSESSMENT_SESSION_VERSION = 1 as const;\nconst STORAGE_PREFIX = \"pie:assessment-session:v1:\";\n\nfunction nowIso(): string {\n\treturn new Date().toISOString();\n}\n\nexport function getAssessmentSessionStorageKey(\n\tassessmentAttemptSessionIdentifier: string,\n): string {\n\treturn `${STORAGE_PREFIX}${assessmentAttemptSessionIdentifier}`;\n}\n\nexport function loadAssessmentSession(\n\tstorage: StorageLike,\n\tassessmentAttemptSessionIdentifier: string,\n): AssessmentSession | null {\n\tconst raw = storage.getItem(\n\t\tgetAssessmentSessionStorageKey(assessmentAttemptSessionIdentifier),\n\t);\n\tif (!raw) return null;\n\ttry {\n\t\tconst parsed = JSON.parse(raw) as AssessmentSession;\n\t\tif (!parsed || parsed.version !== ASSESSMENT_SESSION_VERSION) return null;\n\t\tif (\n\t\t\tparsed.assessmentAttemptSessionIdentifier !==\n\t\t\tassessmentAttemptSessionIdentifier\n\t\t) {\n\t\t\treturn null;\n\t\t}\n\t\treturn parsed;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nexport function saveAssessmentSession(\n\tstorage: StorageLike,\n\tsession: AssessmentSession,\n): void {\n\tconst updated: AssessmentSession = {\n\t\t...session,\n\t\tupdatedAt: nowIso(),\n\t};\n\tstorage.setItem(\n\t\tgetAssessmentSessionStorageKey(session.assessmentAttemptSessionIdentifier),\n\t\tJSON.stringify(updated),\n\t);\n}\n\nexport function createNewAssessmentSession(args: {\n\tassessmentAttemptSessionIdentifier: string;\n\tassessmentId: string;\n\tseed: string;\n\tsectionIdentifiers: string[];\n}): AssessmentSession {\n\tconst startedAt = nowIso();\n\tconst initialSectionIdentifier = args.sectionIdentifiers[0];\n\treturn {\n\t\tversion: ASSESSMENT_SESSION_VERSION,\n\t\tassessmentAttemptSessionIdentifier: args.assessmentAttemptSessionIdentifier,\n\t\tassessmentId: args.assessmentId,\n\t\tstartedAt,\n\t\tupdatedAt: startedAt,\n\t\tnavigationState: {\n\t\t\tcurrentSectionIndex: 0,\n\t\t\tvisitedSectionIdentifiers: initialSectionIdentifier\n\t\t\t\t? [initialSectionIdentifier]\n\t\t\t\t: [],\n\t\t\tcurrentSectionIdentifier: initialSectionIdentifier,\n\t\t},\n\t\trealization: {\n\t\t\tseed: args.seed,\n\t\t\tsectionIdentifiers: args.sectionIdentifiers,\n\t\t},\n\t\tsectionSessions: {},\n\t};\n}\n\nexport function upsertSectionSession(\n\tsession: AssessmentSession,\n\targs: {\n\t\tsectionIdentifier: string;\n\t\tsectionSession: SectionControllerSessionState | null;\n\t},\n): AssessmentSession {\n\tif (!args.sectionIdentifier) return session;\n\tconst updatedAt = nowIso();\n\treturn {\n\t\t...session,\n\t\tupdatedAt,\n\t\tsectionSessions: {\n\t\t\t...session.sectionSessions,\n\t\t\t[args.sectionIdentifier]: {\n\t\t\t\tsectionIdentifier: args.sectionIdentifier,\n\t\t\t\tupdatedAt,\n\t\t\t\tsession: args.sectionSession,\n\t\t\t},\n\t\t},\n\t};\n}\n\nexport function setCurrentSectionPosition(\n\tsession: AssessmentSession,\n\targs: {\n\t\tcurrentSectionIndex: number;\n\t\tcurrentSectionIdentifier?: string;\n\t},\n): AssessmentSession {\n\tconst visited = new Set(\n\t\tsession.navigationState.visitedSectionIdentifiers || [],\n\t);\n\tif (args.currentSectionIdentifier) {\n\t\tvisited.add(args.currentSectionIdentifier);\n\t}\n\treturn {\n\t\t...session,\n\t\tnavigationState: {\n\t\t\t...session.navigationState,\n\t\t\tcurrentSectionIndex: args.currentSectionIndex,\n\t\t\tcurrentSectionIdentifier: args.currentSectionIdentifier,\n\t\t\tvisitedSectionIdentifiers: Array.from(visited),\n\t\t},\n\t};\n}\n"]}
@@ -2,10 +2,7 @@ function nowIso() {
2
2
  return new Date().toISOString();
3
3
  }
4
4
  function getItemIdentifier(itemRef) {
5
- return (itemRef.identifier ||
6
- itemRef.item?.identifier ||
7
- itemRef.item?.id ||
8
- null);
5
+ return (itemRef.identifier || itemRef.item?.identifier || itemRef.item?.id || null);
9
6
  }
10
7
  function resolveItemIdentifiers(args) {
11
8
  const refs = args.itemRefs || args.activityDefinition?.assessmentItemRefs || [];
@@ -39,7 +36,8 @@ function mapItemSessions(itemIdentifiers, backendItemSessions) {
39
36
  if (!rawSession)
40
37
  return acc;
41
38
  const pieSessionId = typeof rawSession.id === "string" ? rawSession.id : undefined;
42
- const attemptCount = typeof rawSession.attemptCount === "number" && rawSession.attemptCount > 0
39
+ const attemptCount = typeof rawSession.attemptCount === "number" &&
40
+ rawSession.attemptCount > 0
43
41
  ? rawSession.attemptCount
44
42
  : pieSessionId
45
43
  ? 1
@@ -47,8 +45,12 @@ function mapItemSessions(itemIdentifiers, backendItemSessions) {
47
45
  const isCompleted = typeof rawSession.isCompleted === "boolean"
48
46
  ? rawSession.isCompleted
49
47
  : Boolean(rawSession.complete);
50
- const startedAt = typeof rawSession.startedAt === "string" ? rawSession.startedAt : undefined;
51
- const updatedAt = typeof rawSession.updatedAt === "string" ? rawSession.updatedAt : undefined;
48
+ const startedAt = typeof rawSession.startedAt === "string"
49
+ ? rawSession.startedAt
50
+ : undefined;
51
+ const updatedAt = typeof rawSession.updatedAt === "string"
52
+ ? rawSession.updatedAt
53
+ : undefined;
52
54
  const completedAt = typeof rawSession.completedAt === "string"
53
55
  ? rawSession.completedAt
54
56
  : undefined;
@@ -128,7 +130,9 @@ export function buildActivitySessionItemUpdate(testAttemptSession, itemIdentifie
128
130
  visitedItemIdentifiers: [
129
131
  ...testAttemptSession.navigationState.visitedItemIdentifiers,
130
132
  ],
131
- itemSessions: itemSession?.session ? { [itemIdentifier]: itemSession.session } : {},
133
+ itemSessions: itemSession?.session
134
+ ? { [itemIdentifier]: itemSession.session }
135
+ : {},
132
136
  },
133
137
  };
134
138
  }
@@ -1 +1 @@
1
- {"version":3,"file":"activity-to-test-attempt-session.js","sourceRoot":"","sources":["../../../src/attempt/adapters/activity-to-test-attempt-session.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n\tTestAttemptItemSession,\n\tTestAttemptSession,\n} from \"../TestSession.js\";\n\nexport interface PieBackendActivityItemRef {\n\tidentifier?: string;\n\titem?: {\n\t\tid?: string;\n\t\tidentifier?: string;\n\t};\n}\n\nexport interface PieBackendActivityDefinition {\n\tid?: string;\n\tidentifier?: string;\n\tassessmentId?: string;\n\tassessmentItemRefs?: PieBackendActivityItemRef[];\n}\n\nexport interface PieBackendActivitySession {\n\tid?: string;\n\tactivitySessionId?: string;\n\tstartedAt?: string;\n\tupdatedAt?: string;\n\tcompletedAt?: string;\n\tcurrentItemIndex?: number;\n\tcurrentSectionIdentifier?: string;\n\tvisitedItemIdentifiers?: string[];\n\titemSessions?: Record<string, unknown>;\n}\n\nexport interface MapActivityToTestAttemptSessionArgs {\n\tactivityDefinition?: PieBackendActivityDefinition | null;\n\tactivitySession?: PieBackendActivitySession | null;\n\titemRefs?: PieBackendActivityItemRef[];\n\titemSessionsByItemIdentifier?: Record<string, unknown>;\n\tassessmentId?: string;\n\ttestAttemptSessionIdentifier?: string;\n}\n\nexport interface ActivitySessionPatchPayload {\n\tactivitySession: {\n\t\tid: string;\n\t\tstartedAt?: string;\n\t\tupdatedAt?: string;\n\t\tcompletedAt?: string;\n\t\tcurrentItemIndex: number;\n\t\tcurrentSectionIdentifier?: string;\n\t\tvisitedItemIdentifiers: string[];\n\t\titemSessions: Record<string, unknown>;\n\t};\n}\n\nfunction nowIso(): string {\n\treturn new Date().toISOString();\n}\n\nfunction getItemIdentifier(itemRef: PieBackendActivityItemRef): string | null {\n\treturn (\n\t\titemRef.identifier ||\n\t\titemRef.item?.identifier ||\n\t\titemRef.item?.id ||\n\t\tnull\n\t);\n}\n\nfunction resolveItemIdentifiers(args: MapActivityToTestAttemptSessionArgs): string[] {\n\tconst refs = args.itemRefs || args.activityDefinition?.assessmentItemRefs || [];\n\tconst identifiers = refs\n\t\t.map((itemRef) => getItemIdentifier(itemRef))\n\t\t.filter((itemIdentifier): itemIdentifier is string => !!itemIdentifier);\n\treturn Array.from(new Set(identifiers));\n}\n\nfunction resolveAssessmentId(args: MapActivityToTestAttemptSessionArgs): string {\n\treturn (\n\t\targs.assessmentId ||\n\t\targs.activityDefinition?.assessmentId ||\n\t\targs.activityDefinition?.identifier ||\n\t\targs.activityDefinition?.id ||\n\t\t\"unknown-assessment\"\n\t);\n}\n\nfunction resolveTestAttemptSessionIdentifier(\n\targs: MapActivityToTestAttemptSessionArgs,\n\tassessmentId: string,\n): string {\n\treturn (\n\t\targs.testAttemptSessionIdentifier ||\n\t\targs.activitySession?.activitySessionId ||\n\t\targs.activitySession?.id ||\n\t\t`tas_${assessmentId}`\n\t);\n}\n\nfunction resolveBackendItemSessions(\n\targs: MapActivityToTestAttemptSessionArgs,\n): Record<string, unknown> {\n\treturn {\n\t\t...(args.activitySession?.itemSessions || {}),\n\t\t...(args.itemSessionsByItemIdentifier || {}),\n\t};\n}\n\nfunction mapItemSessions(\n\titemIdentifiers: string[],\n\tbackendItemSessions: Record<string, unknown>,\n): Record<string, TestAttemptItemSession> {\n\treturn itemIdentifiers.reduce<Record<string, TestAttemptItemSession>>(\n\t\t(acc, itemIdentifier) => {\n\t\t\tconst rawSession = backendItemSessions[itemIdentifier] as\n\t\t\t\t| Record<string, unknown>\n\t\t\t\t| undefined;\n\t\t\tif (!rawSession) return acc;\n\n\t\t\tconst pieSessionId =\n\t\t\t\ttypeof rawSession.id === \"string\" ? rawSession.id : undefined;\n\t\t\tconst attemptCount =\n\t\t\t\ttypeof rawSession.attemptCount === \"number\" && rawSession.attemptCount > 0\n\t\t\t\t\t? rawSession.attemptCount\n\t\t\t\t\t: pieSessionId\n\t\t\t\t\t\t? 1\n\t\t\t\t\t\t: 0;\n\t\t\tconst isCompleted =\n\t\t\t\ttypeof rawSession.isCompleted === \"boolean\"\n\t\t\t\t\t? rawSession.isCompleted\n\t\t\t\t\t: Boolean(rawSession.complete);\n\t\t\tconst startedAt =\n\t\t\t\ttypeof rawSession.startedAt === \"string\" ? rawSession.startedAt : undefined;\n\t\t\tconst updatedAt =\n\t\t\t\ttypeof rawSession.updatedAt === \"string\" ? rawSession.updatedAt : undefined;\n\t\t\tconst completedAt =\n\t\t\t\ttypeof rawSession.completedAt === \"string\"\n\t\t\t\t\t? rawSession.completedAt\n\t\t\t\t\t: undefined;\n\n\t\t\tacc[itemIdentifier] = {\n\t\t\t\titemIdentifier,\n\t\t\t\tpieSessionId,\n\t\t\t\tattemptCount,\n\t\t\t\tisCompleted,\n\t\t\t\tstartedAt,\n\t\t\t\tupdatedAt,\n\t\t\t\tcompletedAt,\n\t\t\t\tsession: rawSession,\n\t\t\t};\n\t\t\treturn acc;\n\t\t},\n\t\t{},\n\t);\n}\n\nexport function mapActivityToTestAttemptSession(\n\targs: MapActivityToTestAttemptSessionArgs,\n): TestAttemptSession {\n\tconst assessmentId = resolveAssessmentId(args);\n\tconst itemIdentifiers = resolveItemIdentifiers(args);\n\tconst testAttemptSessionIdentifier = resolveTestAttemptSessionIdentifier(\n\t\targs,\n\t\tassessmentId,\n\t);\n\tconst backendItemSessions = resolveBackendItemSessions(args);\n\tconst now = nowIso();\n\tconst startedAt = args.activitySession?.startedAt || now;\n\tconst updatedAt = args.activitySession?.updatedAt || startedAt;\n\n\treturn {\n\t\tversion: 1,\n\t\ttestAttemptSessionIdentifier,\n\t\tassessmentId,\n\t\tstartedAt,\n\t\tupdatedAt,\n\t\tcompletedAt: args.activitySession?.completedAt,\n\t\tnavigationState: {\n\t\t\tcurrentItemIndex: args.activitySession?.currentItemIndex ?? -1,\n\t\t\tvisitedItemIdentifiers: [\n\t\t\t\t...(args.activitySession?.visitedItemIdentifiers || []),\n\t\t\t],\n\t\t\tcurrentSectionIdentifier: args.activitySession?.currentSectionIdentifier,\n\t\t},\n\t\trealization: {\n\t\t\tseed: testAttemptSessionIdentifier,\n\t\t\titemIdentifiers,\n\t\t},\n\t\titemSessions: mapItemSessions(itemIdentifiers, backendItemSessions),\n\t};\n}\n\nexport function toItemSessionsRecord(\n\ttestAttemptSession: TestAttemptSession,\n): Record<string, unknown> {\n\treturn Object.entries(testAttemptSession.itemSessions).reduce<\n\t\tRecord<string, unknown>\n\t>((acc, [itemIdentifier, itemSession]) => {\n\t\tif (itemSession.session) {\n\t\t\tacc[itemIdentifier] = itemSession.session;\n\t\t}\n\t\treturn acc;\n\t}, {});\n}\n\nexport function buildActivitySessionPatchFromTestAttemptSession(\n\ttestAttemptSession: TestAttemptSession,\n): ActivitySessionPatchPayload {\n\treturn {\n\t\tactivitySession: {\n\t\t\tid: testAttemptSession.testAttemptSessionIdentifier,\n\t\t\tstartedAt: testAttemptSession.startedAt,\n\t\t\tupdatedAt: testAttemptSession.updatedAt,\n\t\t\tcompletedAt: testAttemptSession.completedAt,\n\t\t\tcurrentItemIndex: testAttemptSession.navigationState.currentItemIndex,\n\t\t\tcurrentSectionIdentifier:\n\t\t\t\ttestAttemptSession.navigationState.currentSectionIdentifier,\n\t\t\tvisitedItemIdentifiers: [\n\t\t\t\t...testAttemptSession.navigationState.visitedItemIdentifiers,\n\t\t\t],\n\t\t\titemSessions: toItemSessionsRecord(testAttemptSession),\n\t\t},\n\t};\n}\n\nexport function buildActivitySessionItemUpdate(\n\ttestAttemptSession: TestAttemptSession,\n\titemIdentifier: string,\n): ActivitySessionPatchPayload {\n\tconst itemSession = testAttemptSession.itemSessions[itemIdentifier];\n\treturn {\n\t\tactivitySession: {\n\t\t\tid: testAttemptSession.testAttemptSessionIdentifier,\n\t\t\tcurrentItemIndex: testAttemptSession.navigationState.currentItemIndex,\n\t\t\tcurrentSectionIdentifier:\n\t\t\t\ttestAttemptSession.navigationState.currentSectionIdentifier,\n\t\t\tvisitedItemIdentifiers: [\n\t\t\t\t...testAttemptSession.navigationState.visitedItemIdentifiers,\n\t\t\t],\n\t\t\titemSessions: itemSession?.session ? { [itemIdentifier]: itemSession.session } : {},\n\t\t},\n\t};\n}\n"]}
1
+ {"version":3,"file":"activity-to-test-attempt-session.js","sourceRoot":"","sources":["../../../src/attempt/adapters/activity-to-test-attempt-session.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n\tTestAttemptItemSession,\n\tTestAttemptSession,\n} from \"../TestSession.js\";\n\nexport interface PieBackendActivityItemRef {\n\tidentifier?: string;\n\titem?: {\n\t\tid?: string;\n\t\tidentifier?: string;\n\t};\n}\n\nexport interface PieBackendActivityDefinition {\n\tid?: string;\n\tidentifier?: string;\n\tassessmentId?: string;\n\tassessmentItemRefs?: PieBackendActivityItemRef[];\n}\n\nexport interface PieBackendActivitySession {\n\tid?: string;\n\tactivitySessionId?: string;\n\tstartedAt?: string;\n\tupdatedAt?: string;\n\tcompletedAt?: string;\n\tcurrentItemIndex?: number;\n\tcurrentSectionIdentifier?: string;\n\tvisitedItemIdentifiers?: string[];\n\titemSessions?: Record<string, unknown>;\n}\n\nexport interface MapActivityToTestAttemptSessionArgs {\n\tactivityDefinition?: PieBackendActivityDefinition | null;\n\tactivitySession?: PieBackendActivitySession | null;\n\titemRefs?: PieBackendActivityItemRef[];\n\titemSessionsByItemIdentifier?: Record<string, unknown>;\n\tassessmentId?: string;\n\ttestAttemptSessionIdentifier?: string;\n}\n\nexport interface ActivitySessionPatchPayload {\n\tactivitySession: {\n\t\tid: string;\n\t\tstartedAt?: string;\n\t\tupdatedAt?: string;\n\t\tcompletedAt?: string;\n\t\tcurrentItemIndex: number;\n\t\tcurrentSectionIdentifier?: string;\n\t\tvisitedItemIdentifiers: string[];\n\t\titemSessions: Record<string, unknown>;\n\t};\n}\n\nfunction nowIso(): string {\n\treturn new Date().toISOString();\n}\n\nfunction getItemIdentifier(itemRef: PieBackendActivityItemRef): string | null {\n\treturn (\n\t\titemRef.identifier || itemRef.item?.identifier || itemRef.item?.id || null\n\t);\n}\n\nfunction resolveItemIdentifiers(\n\targs: MapActivityToTestAttemptSessionArgs,\n): string[] {\n\tconst refs =\n\t\targs.itemRefs || args.activityDefinition?.assessmentItemRefs || [];\n\tconst identifiers = refs\n\t\t.map((itemRef) => getItemIdentifier(itemRef))\n\t\t.filter((itemIdentifier): itemIdentifier is string => !!itemIdentifier);\n\treturn Array.from(new Set(identifiers));\n}\n\nfunction resolveAssessmentId(\n\targs: MapActivityToTestAttemptSessionArgs,\n): string {\n\treturn (\n\t\targs.assessmentId ||\n\t\targs.activityDefinition?.assessmentId ||\n\t\targs.activityDefinition?.identifier ||\n\t\targs.activityDefinition?.id ||\n\t\t\"unknown-assessment\"\n\t);\n}\n\nfunction resolveTestAttemptSessionIdentifier(\n\targs: MapActivityToTestAttemptSessionArgs,\n\tassessmentId: string,\n): string {\n\treturn (\n\t\targs.testAttemptSessionIdentifier ||\n\t\targs.activitySession?.activitySessionId ||\n\t\targs.activitySession?.id ||\n\t\t`tas_${assessmentId}`\n\t);\n}\n\nfunction resolveBackendItemSessions(\n\targs: MapActivityToTestAttemptSessionArgs,\n): Record<string, unknown> {\n\treturn {\n\t\t...(args.activitySession?.itemSessions || {}),\n\t\t...(args.itemSessionsByItemIdentifier || {}),\n\t};\n}\n\nfunction mapItemSessions(\n\titemIdentifiers: string[],\n\tbackendItemSessions: Record<string, unknown>,\n): Record<string, TestAttemptItemSession> {\n\treturn itemIdentifiers.reduce<Record<string, TestAttemptItemSession>>(\n\t\t(acc, itemIdentifier) => {\n\t\t\tconst rawSession = backendItemSessions[itemIdentifier] as\n\t\t\t\t| Record<string, unknown>\n\t\t\t\t| undefined;\n\t\t\tif (!rawSession) return acc;\n\n\t\t\tconst pieSessionId =\n\t\t\t\ttypeof rawSession.id === \"string\" ? rawSession.id : undefined;\n\t\t\tconst attemptCount =\n\t\t\t\ttypeof rawSession.attemptCount === \"number\" &&\n\t\t\t\trawSession.attemptCount > 0\n\t\t\t\t\t? rawSession.attemptCount\n\t\t\t\t\t: pieSessionId\n\t\t\t\t\t\t? 1\n\t\t\t\t\t\t: 0;\n\t\t\tconst isCompleted =\n\t\t\t\ttypeof rawSession.isCompleted === \"boolean\"\n\t\t\t\t\t? rawSession.isCompleted\n\t\t\t\t\t: Boolean(rawSession.complete);\n\t\t\tconst startedAt =\n\t\t\t\ttypeof rawSession.startedAt === \"string\"\n\t\t\t\t\t? rawSession.startedAt\n\t\t\t\t\t: undefined;\n\t\t\tconst updatedAt =\n\t\t\t\ttypeof rawSession.updatedAt === \"string\"\n\t\t\t\t\t? rawSession.updatedAt\n\t\t\t\t\t: undefined;\n\t\t\tconst completedAt =\n\t\t\t\ttypeof rawSession.completedAt === \"string\"\n\t\t\t\t\t? rawSession.completedAt\n\t\t\t\t\t: undefined;\n\n\t\t\tacc[itemIdentifier] = {\n\t\t\t\titemIdentifier,\n\t\t\t\tpieSessionId,\n\t\t\t\tattemptCount,\n\t\t\t\tisCompleted,\n\t\t\t\tstartedAt,\n\t\t\t\tupdatedAt,\n\t\t\t\tcompletedAt,\n\t\t\t\tsession: rawSession,\n\t\t\t};\n\t\t\treturn acc;\n\t\t},\n\t\t{},\n\t);\n}\n\nexport function mapActivityToTestAttemptSession(\n\targs: MapActivityToTestAttemptSessionArgs,\n): TestAttemptSession {\n\tconst assessmentId = resolveAssessmentId(args);\n\tconst itemIdentifiers = resolveItemIdentifiers(args);\n\tconst testAttemptSessionIdentifier = resolveTestAttemptSessionIdentifier(\n\t\targs,\n\t\tassessmentId,\n\t);\n\tconst backendItemSessions = resolveBackendItemSessions(args);\n\tconst now = nowIso();\n\tconst startedAt = args.activitySession?.startedAt || now;\n\tconst updatedAt = args.activitySession?.updatedAt || startedAt;\n\n\treturn {\n\t\tversion: 1,\n\t\ttestAttemptSessionIdentifier,\n\t\tassessmentId,\n\t\tstartedAt,\n\t\tupdatedAt,\n\t\tcompletedAt: args.activitySession?.completedAt,\n\t\tnavigationState: {\n\t\t\tcurrentItemIndex: args.activitySession?.currentItemIndex ?? -1,\n\t\t\tvisitedItemIdentifiers: [\n\t\t\t\t...(args.activitySession?.visitedItemIdentifiers || []),\n\t\t\t],\n\t\t\tcurrentSectionIdentifier: args.activitySession?.currentSectionIdentifier,\n\t\t},\n\t\trealization: {\n\t\t\tseed: testAttemptSessionIdentifier,\n\t\t\titemIdentifiers,\n\t\t},\n\t\titemSessions: mapItemSessions(itemIdentifiers, backendItemSessions),\n\t};\n}\n\nexport function toItemSessionsRecord(\n\ttestAttemptSession: TestAttemptSession,\n): Record<string, unknown> {\n\treturn Object.entries(testAttemptSession.itemSessions).reduce<\n\t\tRecord<string, unknown>\n\t>((acc, [itemIdentifier, itemSession]) => {\n\t\tif (itemSession.session) {\n\t\t\tacc[itemIdentifier] = itemSession.session;\n\t\t}\n\t\treturn acc;\n\t}, {});\n}\n\nexport function buildActivitySessionPatchFromTestAttemptSession(\n\ttestAttemptSession: TestAttemptSession,\n): ActivitySessionPatchPayload {\n\treturn {\n\t\tactivitySession: {\n\t\t\tid: testAttemptSession.testAttemptSessionIdentifier,\n\t\t\tstartedAt: testAttemptSession.startedAt,\n\t\t\tupdatedAt: testAttemptSession.updatedAt,\n\t\t\tcompletedAt: testAttemptSession.completedAt,\n\t\t\tcurrentItemIndex: testAttemptSession.navigationState.currentItemIndex,\n\t\t\tcurrentSectionIdentifier:\n\t\t\t\ttestAttemptSession.navigationState.currentSectionIdentifier,\n\t\t\tvisitedItemIdentifiers: [\n\t\t\t\t...testAttemptSession.navigationState.visitedItemIdentifiers,\n\t\t\t],\n\t\t\titemSessions: toItemSessionsRecord(testAttemptSession),\n\t\t},\n\t};\n}\n\nexport function buildActivitySessionItemUpdate(\n\ttestAttemptSession: TestAttemptSession,\n\titemIdentifier: string,\n): ActivitySessionPatchPayload {\n\tconst itemSession = testAttemptSession.itemSessions[itemIdentifier];\n\treturn {\n\t\tactivitySession: {\n\t\t\tid: testAttemptSession.testAttemptSessionIdentifier,\n\t\t\tcurrentItemIndex: testAttemptSession.navigationState.currentItemIndex,\n\t\t\tcurrentSectionIdentifier:\n\t\t\t\ttestAttemptSession.navigationState.currentSectionIdentifier,\n\t\t\tvisitedItemIdentifiers: [\n\t\t\t\t...testAttemptSession.navigationState.visitedItemIdentifiers,\n\t\t\t],\n\t\t\titemSessions: itemSession?.session\n\t\t\t\t? { [itemIdentifier]: itemSession.session }\n\t\t\t\t: {},\n\t\t},\n\t};\n}\n"]}
@@ -6223,6 +6223,39 @@ var segmentSentences = (text2, options) => {
6223
6223
  };
6224
6224
 
6225
6225
  // dist/services/tts/browser-provider.js
6226
+ var normalizeLanguageCode = (value) => String(value || "").trim().toLowerCase();
6227
+ var browserLanguage = () => {
6228
+ const navigatorLanguage = typeof navigator !== "undefined" ? navigator.language || navigator.languages?.[0] : "";
6229
+ return normalizeLanguageCode(navigatorLanguage || "en-US");
6230
+ };
6231
+ var findBrowserVoice = (voices, preferredName) => {
6232
+ if (preferredName) {
6233
+ const explicit = voices.find((voice) => voice.name === preferredName);
6234
+ if (explicit)
6235
+ return explicit;
6236
+ }
6237
+ const language = browserLanguage();
6238
+ const languagePrefix = language.split("-")[0] || "en";
6239
+ const matchesLanguage = (voice) => {
6240
+ const voiceLanguage = normalizeLanguageCode(voice.lang);
6241
+ return voiceLanguage === language || voiceLanguage.startsWith(`${languagePrefix}-`);
6242
+ };
6243
+ const ranked = [
6244
+ (voice) => voice.localService && matchesLanguage(voice),
6245
+ (voice) => voice.default && matchesLanguage(voice),
6246
+ (voice) => matchesLanguage(voice),
6247
+ (voice) => voice.localService,
6248
+ (voice) => voice.default
6249
+ ];
6250
+ for (const predicate of ranked) {
6251
+ const voice = voices.find(predicate);
6252
+ if (voice)
6253
+ return voice;
6254
+ }
6255
+ return voices[0] || null;
6256
+ };
6257
+ var shouldAssignBrowserVoice = (voice) => !voice.default;
6258
+
6226
6259
  class BrowserTTSProvider {
6227
6260
  providerId = "browser";
6228
6261
  providerName = "Browser Speech Synthesis";
@@ -6377,24 +6410,28 @@ class BrowserTTSProviderImpl {
6377
6410
  resolve(false);
6378
6411
  return;
6379
6412
  }
6380
- this.utterance = new SpeechSynthesisUtterance(chunkText);
6381
- if (this.config?.voice) {
6382
- const voices = speechSynthesis.getVoices();
6383
- const voice = voices.find((v) => v.name === this.config.voice);
6384
- if (voice)
6385
- this.utterance.voice = voice;
6386
- }
6413
+ const utterance = new SpeechSynthesisUtterance(chunkText);
6414
+ this.utterance = utterance;
6415
+ const voice = findBrowserVoice(speechSynthesis.getVoices(), this.config?.voice);
6416
+ if (voice && shouldAssignBrowserVoice(voice))
6417
+ utterance.voice = voice;
6387
6418
  if (this.config?.rate)
6388
- this.utterance.rate = this.config.rate;
6419
+ utterance.rate = this.config.rate;
6389
6420
  if (this.config?.pitch)
6390
- this.utterance.pitch = this.config.pitch;
6391
- this.utterance.onstart = () => {
6421
+ utterance.pitch = this.config.pitch;
6422
+ const clearOwnedUtterance = () => {
6423
+ if (this.utterance === utterance) {
6424
+ this.utterance = null;
6425
+ }
6426
+ };
6427
+ utterance.onstart = () => {
6392
6428
  if (runId !== this.speakRunId)
6393
6429
  return;
6394
6430
  this._isPlaying = true;
6395
6431
  this._isPaused = false;
6396
6432
  };
6397
- this.utterance.onend = () => {
6433
+ utterance.onend = () => {
6434
+ clearOwnedUtterance();
6398
6435
  if (runId !== this.speakRunId) {
6399
6436
  resolve(false);
6400
6437
  return;
@@ -6403,7 +6440,8 @@ class BrowserTTSProviderImpl {
6403
6440
  this._isPaused = false;
6404
6441
  resolve(true);
6405
6442
  };
6406
- this.utterance.onerror = (event2) => {
6443
+ utterance.onerror = (event2) => {
6444
+ clearOwnedUtterance();
6407
6445
  if (runId !== this.speakRunId) {
6408
6446
  resolve(false);
6409
6447
  return;
@@ -6416,17 +6454,17 @@ class BrowserTTSProviderImpl {
6416
6454
  }
6417
6455
  reject(new Error(`Speech synthesis error: ${event2.error}`));
6418
6456
  };
6419
- this.utterance.onpause = () => {
6457
+ utterance.onpause = () => {
6420
6458
  if (runId !== this.speakRunId)
6421
6459
  return;
6422
6460
  this._isPaused = true;
6423
6461
  };
6424
- this.utterance.onresume = () => {
6462
+ utterance.onresume = () => {
6425
6463
  if (runId !== this.speakRunId)
6426
6464
  return;
6427
6465
  this._isPaused = false;
6428
6466
  };
6429
- this.utterance.onboundary = (event2) => {
6467
+ utterance.onboundary = (event2) => {
6430
6468
  if (runId !== this.speakRunId)
6431
6469
  return;
6432
6470
  console.log("[BrowserProvider] Boundary event:", event2.name, "charIndex:", event2.charIndex, "charLength:", event2.charLength);
@@ -6444,7 +6482,7 @@ class BrowserTTSProviderImpl {
6444
6482
  console.log("[BrowserProvider] Calling onWordBoundary with word:", word, "at position:", absoluteBoundaryStart);
6445
6483
  this.onWordBoundary(word, absoluteBoundaryStart, wordLength);
6446
6484
  };
6447
- speechSynthesis.speak(this.utterance);
6485
+ speechSynthesis.speak(utterance);
6448
6486
  });
6449
6487
  }
6450
6488
  pause() {
@@ -6459,9 +6497,10 @@ class BrowserTTSProviderImpl {
6459
6497
  }
6460
6498
  stop() {
6461
6499
  this.speakRunId += 1;
6462
- if (this._isPlaying) {
6500
+ if (this._isPlaying || this.utterance) {
6463
6501
  speechSynthesis.cancel();
6464
6502
  }
6503
+ this.utterance = null;
6465
6504
  this._isPlaying = false;
6466
6505
  this._isPaused = false;
6467
6506
  }
@@ -8610,6 +8649,7 @@ function ItemToolBar($$anchor, $$props) {
8610
8649
  const FA_PRO_HREFS = ["/_fa-pro/fontawesome.min.css", "/_fa-pro/light.min.css"];
8611
8650
  const FA_FREE_HREF = "https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.5.2/css/all.min.css";
8612
8651
  const ROBOTO_HREF = "https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap";
8652
+ const FA_HREF_PATTERN = /font.?awesome|fa-?pro/i;
8613
8653
  let ndsAssetsInstalled = false;
8614
8654
  const ensureNdsAssets = () => {
8615
8655
  if (!isBrowser || ndsAssetsInstalled)
@@ -8621,6 +8661,9 @@ function ItemToolBar($$anchor, $$props) {
8621
8661
  link2.href = ROBOTO_HREF;
8622
8662
  document.head.appendChild(link2);
8623
8663
  }
8664
+ const hostHasFa = Array.from(document.querySelectorAll('link[rel="stylesheet"][href]')).some((link2) => FA_HREF_PATTERN.test(link2.href));
8665
+ if (hostHasFa)
8666
+ return;
8624
8667
  if (!document.querySelector(`link[href="${FA_FREE_HREF}"]`)) {
8625
8668
  const link2 = document.createElement("link");
8626
8669
  link2.rel = "stylesheet";
@@ -8637,7 +8680,6 @@ function ItemToolBar($$anchor, $$props) {
8637
8680
  }
8638
8681
  };
8639
8682
  const FA_TOOLBAR_SHADOW_INSTALLED = "__pieFaToolbarShadowInstalled";
8640
- const FA_HREF_PATTERN = /font.?awesome|fa-?pro/i;
8641
8683
  const installFaInToolbarShadow = (root) => {
8642
8684
  const shadow = root.getRootNode();
8643
8685
  if (!(shadow instanceof ShadowRoot))
@@ -8659,9 +8701,6 @@ function ItemToolBar($$anchor, $$props) {
8659
8701
  const documentFaLinks = Array.from(document.querySelectorAll('link[rel="stylesheet"][href]')).filter((link2) => FA_HREF_PATTERN.test(link2.href));
8660
8702
  for (const link2 of documentFaLinks)
8661
8703
  appendLink(link2.href);
8662
- const shim = document.createElement("style");
8663
- shim.textContent = '.fa-light{font-family:"Font Awesome 6 Free";font-weight:900;}';
8664
- shadow.appendChild(shim);
8665
8704
  };
8666
8705
  const ndsIconButtonAction = (node) => {
8667
8706
  ensureNdsAssets();
@@ -18080,6 +18080,39 @@ var segmentSentences = (text2, options) => {
18080
18080
  };
18081
18081
 
18082
18082
  // dist/services/tts/browser-provider.js
18083
+ var normalizeLanguageCode = (value) => String(value || "").trim().toLowerCase();
18084
+ var browserLanguage = () => {
18085
+ const navigatorLanguage = typeof navigator !== "undefined" ? navigator.language || navigator.languages?.[0] : "";
18086
+ return normalizeLanguageCode(navigatorLanguage || "en-US");
18087
+ };
18088
+ var findBrowserVoice = (voices, preferredName) => {
18089
+ if (preferredName) {
18090
+ const explicit = voices.find((voice) => voice.name === preferredName);
18091
+ if (explicit)
18092
+ return explicit;
18093
+ }
18094
+ const language = browserLanguage();
18095
+ const languagePrefix = language.split("-")[0] || "en";
18096
+ const matchesLanguage = (voice) => {
18097
+ const voiceLanguage = normalizeLanguageCode(voice.lang);
18098
+ return voiceLanguage === language || voiceLanguage.startsWith(`${languagePrefix}-`);
18099
+ };
18100
+ const ranked = [
18101
+ (voice) => voice.localService && matchesLanguage(voice),
18102
+ (voice) => voice.default && matchesLanguage(voice),
18103
+ (voice) => matchesLanguage(voice),
18104
+ (voice) => voice.localService,
18105
+ (voice) => voice.default
18106
+ ];
18107
+ for (const predicate of ranked) {
18108
+ const voice = voices.find(predicate);
18109
+ if (voice)
18110
+ return voice;
18111
+ }
18112
+ return voices[0] || null;
18113
+ };
18114
+ var shouldAssignBrowserVoice = (voice) => !voice.default;
18115
+
18083
18116
  class BrowserTTSProvider {
18084
18117
  providerId = "browser";
18085
18118
  providerName = "Browser Speech Synthesis";
@@ -18234,24 +18267,28 @@ class BrowserTTSProviderImpl {
18234
18267
  resolve(false);
18235
18268
  return;
18236
18269
  }
18237
- this.utterance = new SpeechSynthesisUtterance(chunkText);
18238
- if (this.config?.voice) {
18239
- const voices = speechSynthesis.getVoices();
18240
- const voice = voices.find((v) => v.name === this.config.voice);
18241
- if (voice)
18242
- this.utterance.voice = voice;
18243
- }
18270
+ const utterance = new SpeechSynthesisUtterance(chunkText);
18271
+ this.utterance = utterance;
18272
+ const voice = findBrowserVoice(speechSynthesis.getVoices(), this.config?.voice);
18273
+ if (voice && shouldAssignBrowserVoice(voice))
18274
+ utterance.voice = voice;
18244
18275
  if (this.config?.rate)
18245
- this.utterance.rate = this.config.rate;
18276
+ utterance.rate = this.config.rate;
18246
18277
  if (this.config?.pitch)
18247
- this.utterance.pitch = this.config.pitch;
18248
- this.utterance.onstart = () => {
18278
+ utterance.pitch = this.config.pitch;
18279
+ const clearOwnedUtterance = () => {
18280
+ if (this.utterance === utterance) {
18281
+ this.utterance = null;
18282
+ }
18283
+ };
18284
+ utterance.onstart = () => {
18249
18285
  if (runId !== this.speakRunId)
18250
18286
  return;
18251
18287
  this._isPlaying = true;
18252
18288
  this._isPaused = false;
18253
18289
  };
18254
- this.utterance.onend = () => {
18290
+ utterance.onend = () => {
18291
+ clearOwnedUtterance();
18255
18292
  if (runId !== this.speakRunId) {
18256
18293
  resolve(false);
18257
18294
  return;
@@ -18260,7 +18297,8 @@ class BrowserTTSProviderImpl {
18260
18297
  this._isPaused = false;
18261
18298
  resolve(true);
18262
18299
  };
18263
- this.utterance.onerror = (event2) => {
18300
+ utterance.onerror = (event2) => {
18301
+ clearOwnedUtterance();
18264
18302
  if (runId !== this.speakRunId) {
18265
18303
  resolve(false);
18266
18304
  return;
@@ -18273,17 +18311,17 @@ class BrowserTTSProviderImpl {
18273
18311
  }
18274
18312
  reject(new Error(`Speech synthesis error: ${event2.error}`));
18275
18313
  };
18276
- this.utterance.onpause = () => {
18314
+ utterance.onpause = () => {
18277
18315
  if (runId !== this.speakRunId)
18278
18316
  return;
18279
18317
  this._isPaused = true;
18280
18318
  };
18281
- this.utterance.onresume = () => {
18319
+ utterance.onresume = () => {
18282
18320
  if (runId !== this.speakRunId)
18283
18321
  return;
18284
18322
  this._isPaused = false;
18285
18323
  };
18286
- this.utterance.onboundary = (event2) => {
18324
+ utterance.onboundary = (event2) => {
18287
18325
  if (runId !== this.speakRunId)
18288
18326
  return;
18289
18327
  console.log("[BrowserProvider] Boundary event:", event2.name, "charIndex:", event2.charIndex, "charLength:", event2.charLength);
@@ -18301,7 +18339,7 @@ class BrowserTTSProviderImpl {
18301
18339
  console.log("[BrowserProvider] Calling onWordBoundary with word:", word, "at position:", absoluteBoundaryStart);
18302
18340
  this.onWordBoundary(word, absoluteBoundaryStart, wordLength);
18303
18341
  };
18304
- speechSynthesis.speak(this.utterance);
18342
+ speechSynthesis.speak(utterance);
18305
18343
  });
18306
18344
  }
18307
18345
  pause() {
@@ -18316,9 +18354,10 @@ class BrowserTTSProviderImpl {
18316
18354
  }
18317
18355
  stop() {
18318
18356
  this.speakRunId += 1;
18319
- if (this._isPlaying) {
18357
+ if (this._isPlaying || this.utterance) {
18320
18358
  speechSynthesis.cancel();
18321
18359
  }
18360
+ this.utterance = null;
18322
18361
  this._isPlaying = false;
18323
18362
  this._isPaused = false;
18324
18363
  }
@@ -21831,7 +21870,10 @@ var resolvePlainBoundaryOffset = (alignment, position, safeLength) => {
21831
21870
  const token = findTokenAtOffset(alignment.spokenTokens, position);
21832
21871
  if (!token)
21833
21872
  return null;
21834
- return { start: position, length: Math.min(safeLength, token.end - position) };
21873
+ return {
21874
+ start: position,
21875
+ length: Math.min(safeLength, token.end - position)
21876
+ };
21835
21877
  };
21836
21878
  var resolveSpokenBoundaryOffset = (alignment, position, length = 1, boundaryWord) => {
21837
21879
  if (alignment.boundaryOffsetMode === "unsupported" || !Number.isFinite(position)) {
@@ -22861,7 +22903,11 @@ var collectMathAware = (root, options) => {
22861
22903
  const flushTextChunk = () => {
22862
22904
  const text2 = normalizeTextForSpeech(acc.chars.slice(textChunkStart, acc.chars.length).join(""));
22863
22905
  if (text2) {
22864
- chunks.push({ type: "text", text: text2, sourceElement: textChunkSourceElement });
22906
+ chunks.push({
22907
+ type: "text",
22908
+ text: text2,
22909
+ sourceElement: textChunkSourceElement
22910
+ });
22865
22911
  }
22866
22912
  textChunkStart = acc.chars.length;
22867
22913
  textChunkSourceElement = undefined;
@@ -26896,27 +26942,36 @@ class ToolkitCoordinator {
26896
26942
  return;
26897
26943
  await new Promise((resolve) => {
26898
26944
  let settled2 = false;
26945
+ const canUseEventTarget = typeof synth.addEventListener === "function" && typeof synth.removeEventListener === "function";
26946
+ const canUseHandler = !canUseEventTarget && "onvoiceschanged" in synth;
26947
+ const previousHandler = canUseHandler ? synth.onvoiceschanged : null;
26948
+ let assignedHandler = false;
26899
26949
  const finish = () => {
26900
26950
  if (settled2)
26901
26951
  return;
26902
26952
  settled2 = true;
26903
- resolve();
26904
- };
26905
- const timeoutId = window.setTimeout(() => {
26906
- synth.removeEventListener("voiceschanged", onVoicesChanged);
26907
- finish();
26908
- }, timeoutMs);
26909
- const onVoicesChanged = () => {
26910
26953
  window.clearTimeout(timeoutId);
26911
- if (typeof synth.removeEventListener === "function" && typeof synth.addEventListener === "function") {
26954
+ if (canUseEventTarget) {
26912
26955
  synth.removeEventListener("voiceschanged", onVoicesChanged);
26956
+ } else if (assignedHandler && synth.onvoiceschanged === onVoicesChanged) {
26957
+ synth.onvoiceschanged = previousHandler;
26958
+ }
26959
+ resolve();
26960
+ };
26961
+ const onVoicesChanged = (event2) => {
26962
+ if (!canUseEventTarget && typeof previousHandler === "function" && event2) {
26963
+ previousHandler.call(synth, event2);
26913
26964
  }
26914
26965
  finish();
26915
26966
  };
26916
- if (typeof synth.addEventListener === "function" && typeof synth.removeEventListener === "function") {
26967
+ const timeoutId = window.setTimeout(finish, timeoutMs);
26968
+ if (canUseEventTarget) {
26917
26969
  synth.addEventListener("voiceschanged", onVoicesChanged, {
26918
26970
  once: true
26919
26971
  });
26972
+ } else if (canUseHandler) {
26973
+ synth.onvoiceschanged = onVoicesChanged;
26974
+ assignedHandler = true;
26920
26975
  }
26921
26976
  });
26922
26977
  const voicesAfterWait = synth.getVoices();