@promptbook/components 0.112.0-62 → 0.112.0-64

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 (57) hide show
  1. package/esm/index.es.js +2880 -775
  2. package/esm/index.es.js.map +1 -1
  3. package/esm/src/_packages/components.index.d.ts +4 -0
  4. package/esm/src/_packages/core.index.d.ts +2 -0
  5. package/esm/src/_packages/types.index.d.ts +2 -0
  6. package/esm/src/book-components/Chat/Chat/TeamToolCallModalContent.d.ts +0 -2
  7. package/esm/src/book-components/Chat/Chat/renderTimeoutToolCallDetails.d.ts +7 -1
  8. package/esm/src/book-components/Chat/Chat/useChatInputAreaComposer.d.ts +1 -1
  9. package/esm/src/book-components/Chat/Chat/useChatInputAreaDictation.d.ts +2 -2
  10. package/esm/src/book-components/Chat/hooks/useChatAutoScroll.d.ts +6 -3
  11. package/esm/src/book-components/Chat/types/ChatMessage.d.ts +34 -0
  12. package/esm/src/cli/cli-commands/agent/agentProjectPaths.d.ts +54 -0
  13. package/esm/src/cli/cli-commands/agent/agentRunCliOptions.d.ts +13 -0
  14. package/esm/src/cli/cli-commands/agent/init.d.ts +10 -0
  15. package/esm/src/cli/cli-commands/agent/initializeAgentProjectConfiguration.d.ts +21 -0
  16. package/esm/src/cli/cli-commands/agent/printAgentInitializationSummary.d.ts +7 -0
  17. package/esm/src/cli/cli-commands/agent/run.d.ts +10 -0
  18. package/esm/src/cli/cli-commands/agent/run.test.d.ts +1 -0
  19. package/esm/src/cli/cli-commands/agent/tick.d.ts +10 -0
  20. package/esm/src/cli/cli-commands/agent.d.ts +15 -0
  21. package/esm/src/cli/cli-commands/common/promptRunnerCliOptions.d.ts +86 -0
  22. package/esm/src/commitments/KNOWLEDGE/KNOWLEDGE.d.ts +11 -0
  23. package/esm/src/commitments/KNOWLEDGE/KNOWLEDGE.test.d.ts +1 -0
  24. package/esm/src/commitments/_common/toolRuntimeContext.d.ts +6 -0
  25. package/esm/src/commitments/index.d.ts +2 -1
  26. package/esm/src/llm-providers/openai/OpenAiAgentKitExecutionTools.d.ts +4 -2
  27. package/esm/src/llm-providers/openai/OpenAiAgentKitExecutionToolsOptions.d.ts +9 -0
  28. package/esm/src/version.d.ts +1 -1
  29. package/package.json +1 -1
  30. package/umd/index.umd.js +2880 -775
  31. package/umd/index.umd.js.map +1 -1
  32. package/umd/src/_packages/components.index.d.ts +4 -0
  33. package/umd/src/_packages/core.index.d.ts +2 -0
  34. package/umd/src/_packages/types.index.d.ts +2 -0
  35. package/umd/src/book-components/Chat/Chat/TeamToolCallModalContent.d.ts +0 -2
  36. package/umd/src/book-components/Chat/Chat/renderTimeoutToolCallDetails.d.ts +7 -1
  37. package/umd/src/book-components/Chat/Chat/useChatInputAreaComposer.d.ts +1 -1
  38. package/umd/src/book-components/Chat/Chat/useChatInputAreaDictation.d.ts +2 -2
  39. package/umd/src/book-components/Chat/hooks/useChatAutoScroll.d.ts +6 -3
  40. package/umd/src/book-components/Chat/types/ChatMessage.d.ts +34 -0
  41. package/umd/src/cli/cli-commands/agent/agentProjectPaths.d.ts +54 -0
  42. package/umd/src/cli/cli-commands/agent/agentRunCliOptions.d.ts +13 -0
  43. package/umd/src/cli/cli-commands/agent/init.d.ts +10 -0
  44. package/umd/src/cli/cli-commands/agent/initializeAgentProjectConfiguration.d.ts +21 -0
  45. package/umd/src/cli/cli-commands/agent/printAgentInitializationSummary.d.ts +7 -0
  46. package/umd/src/cli/cli-commands/agent/run.d.ts +10 -0
  47. package/umd/src/cli/cli-commands/agent/run.test.d.ts +1 -0
  48. package/umd/src/cli/cli-commands/agent/tick.d.ts +10 -0
  49. package/umd/src/cli/cli-commands/agent.d.ts +15 -0
  50. package/umd/src/cli/cli-commands/common/promptRunnerCliOptions.d.ts +86 -0
  51. package/umd/src/commitments/KNOWLEDGE/KNOWLEDGE.d.ts +11 -0
  52. package/umd/src/commitments/KNOWLEDGE/KNOWLEDGE.test.d.ts +1 -0
  53. package/umd/src/commitments/_common/toolRuntimeContext.d.ts +6 -0
  54. package/umd/src/commitments/index.d.ts +2 -1
  55. package/umd/src/llm-providers/openai/OpenAiAgentKitExecutionTools.d.ts +4 -2
  56. package/umd/src/llm-providers/openai/OpenAiAgentKitExecutionToolsOptions.d.ts +9 -0
  57. package/umd/src/version.d.ts +1 -1
package/umd/index.umd.js CHANGED
@@ -30,7 +30,7 @@
30
30
  * @generated
31
31
  * @see https://github.com/webgptorg/promptbook
32
32
  */
33
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-62';
33
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0-64';
34
34
  /**
35
35
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
36
36
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -10888,6 +10888,18 @@
10888
10888
  }
10889
10889
  // Note: [💞] Ignore a discrepancy between file name and entity name
10890
10890
 
10891
+ /**
10892
+ * Name of the tool used by agents to search configured `KNOWLEDGE` sources.
10893
+ *
10894
+ * @public exported from `@promptbook/core`
10895
+ */
10896
+ const KNOWLEDGE_SEARCH_TOOL_NAME = 'knowledge_search';
10897
+ /**
10898
+ * Title of the system-message section generated for `KNOWLEDGE` commitments.
10899
+ *
10900
+ * @private constant of `KnowledgeCommitmentDefinition`
10901
+ */
10902
+ const KNOWLEDGE_SEARCH_SYSTEM_SECTION_TITLE = 'Knowledge Search';
10891
10903
  /**
10892
10904
  * KNOWLEDGE commitment definition
10893
10905
  *
@@ -11009,9 +11021,17 @@
11009
11021
  knowledgeInfoEntries.push(`Knowledge Source Inline: ${inlineSource.filename} (derived from inline content and processed for retrieval during chat)`);
11010
11022
  }
11011
11023
  if (knowledgeInfoEntries.length === 0) {
11012
- return nextRequirements;
11024
+ return addKnowledgeSearchToolAndSystemSection(nextRequirements);
11013
11025
  }
11014
- return this.appendToSystemMessage(nextRequirements, knowledgeInfoEntries.join('\n'), '\n\n');
11026
+ return addKnowledgeSearchToolAndSystemSection(nextRequirements);
11027
+ }
11028
+ /**
11029
+ * Gets human-readable titles for tool functions provided by this commitment.
11030
+ */
11031
+ getToolTitles() {
11032
+ return {
11033
+ [KNOWLEDGE_SEARCH_TOOL_NAME]: 'Knowledge search',
11034
+ };
11015
11035
  }
11016
11036
  }
11017
11037
  /**
@@ -11025,6 +11045,128 @@
11025
11045
  const significantText = contentWithoutUrls.replace(/[\s.,!?;:'"`()[\]{}<>/-]+/g, '');
11026
11046
  return significantText.length > 0;
11027
11047
  }
11048
+ /**
11049
+ * Adds the shared `knowledge_search` tool definition and the consolidated system-message section.
11050
+ *
11051
+ * @param requirements - Requirements after one `KNOWLEDGE` commitment was applied.
11052
+ * @returns Requirements with the knowledge search instructions and tool definition.
11053
+ *
11054
+ * @private internal utility of `KnowledgeCommitmentDefinition`
11055
+ */
11056
+ function addKnowledgeSearchToolAndSystemSection(requirements) {
11057
+ const nextRequirements = addKnowledgeSearchTool(requirements);
11058
+ const section = createKnowledgeSearchSystemSection(nextRequirements);
11059
+ const sectionHeader = `## ${KNOWLEDGE_SEARCH_SYSTEM_SECTION_TITLE}`;
11060
+ if (nextRequirements.systemMessage.includes(sectionHeader)) {
11061
+ return {
11062
+ ...nextRequirements,
11063
+ systemMessage: nextRequirements.systemMessage.replace(new RegExp(`## ${KNOWLEDGE_SEARCH_SYSTEM_SECTION_TITLE.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[\\s\\S]*?(?=\\n\\n##|$)`), section),
11064
+ };
11065
+ }
11066
+ return {
11067
+ ...nextRequirements,
11068
+ systemMessage: nextRequirements.systemMessage.trim()
11069
+ ? `${nextRequirements.systemMessage}\n\n${section}`
11070
+ : section,
11071
+ };
11072
+ }
11073
+ /**
11074
+ * Adds the `knowledge_search` model tool when it is not already present.
11075
+ *
11076
+ * @param requirements - Current model requirements.
11077
+ * @returns Requirements with the tool definition available to the model.
11078
+ *
11079
+ * @private internal utility of `KnowledgeCommitmentDefinition`
11080
+ */
11081
+ function addKnowledgeSearchTool(requirements) {
11082
+ const existingTools = requirements.tools || [];
11083
+ if (existingTools.some((tool) => tool.name === KNOWLEDGE_SEARCH_TOOL_NAME)) {
11084
+ return requirements;
11085
+ }
11086
+ return {
11087
+ ...requirements,
11088
+ tools: [
11089
+ ...existingTools,
11090
+ {
11091
+ name: KNOWLEDGE_SEARCH_TOOL_NAME,
11092
+ description: spacetrim.spaceTrim(`
11093
+ Search the agent's configured knowledge sources and return relevant excerpts with citation ids.
11094
+ Use this before answering questions that may depend on the agent's KNOWLEDGE commitments.
11095
+ `),
11096
+ parameters: {
11097
+ type: 'object',
11098
+ properties: {
11099
+ query: {
11100
+ type: 'string',
11101
+ description: 'The natural-language search query for the knowledge base.',
11102
+ },
11103
+ limit: {
11104
+ type: 'integer',
11105
+ description: 'Maximum number of matching source excerpts to return.',
11106
+ },
11107
+ },
11108
+ required: ['query'],
11109
+ },
11110
+ },
11111
+ ],
11112
+ };
11113
+ }
11114
+ /**
11115
+ * Creates the model-facing system-message section for knowledge search.
11116
+ *
11117
+ * @param requirements - Current model requirements.
11118
+ * @returns Markdown system-message section.
11119
+ *
11120
+ * @private internal utility of `KnowledgeCommitmentDefinition`
11121
+ */
11122
+ function createKnowledgeSearchSystemSection(requirements) {
11123
+ const sourceEntries = createKnowledgeSourceSystemEntries(requirements);
11124
+ const sourceList = sourceEntries.length > 0 ? sourceEntries.map((entry) => `- ${entry}`).join('\n') : '- None';
11125
+ return spacetrim.spaceTrim(`
11126
+ ## ${KNOWLEDGE_SEARCH_SYSTEM_SECTION_TITLE}
11127
+
11128
+ - Use \`${KNOWLEDGE_SEARCH_TOOL_NAME}\` to search the configured knowledge sources before answering questions that depend on this agent's knowledge base.
11129
+ - Base source-backed factual answers on the returned excerpts.
11130
+ - When you use a returned excerpt, include its citation marker in the answer body, for example \`[0:0]\`.
11131
+ - If the search returns no relevant information, say that the knowledge base did not contain the answer instead of inventing it.
11132
+
11133
+ Configured knowledge sources:
11134
+ ${sourceList}
11135
+ `);
11136
+ }
11137
+ /**
11138
+ * Builds a stable list of configured knowledge sources for system-message diagnostics.
11139
+ *
11140
+ * @param requirements - Current model requirements.
11141
+ * @returns Human-readable source entries.
11142
+ *
11143
+ * @private internal utility of `KnowledgeCommitmentDefinition`
11144
+ */
11145
+ function createKnowledgeSourceSystemEntries(requirements) {
11146
+ var _a;
11147
+ const entries = [];
11148
+ const seenEntries = new Set();
11149
+ for (const source of requirements.knowledgeSources || []) {
11150
+ const entry = `Source URL: ${source} (processed for retrieval during chat)`;
11151
+ if (seenEntries.has(entry)) {
11152
+ continue;
11153
+ }
11154
+ seenEntries.add(entry);
11155
+ entries.push(entry);
11156
+ }
11157
+ const inlineSources = (((_a = requirements._metadata) === null || _a === void 0 ? void 0 : _a.inlineKnowledgeSources) || [])
11158
+ .map((source) => source.filename)
11159
+ .filter(Boolean);
11160
+ for (const filename of inlineSources) {
11161
+ const entry = `Knowledge Source Inline: ${filename} (Inline source: processed for retrieval during chat)`;
11162
+ if (seenEntries.has(entry)) {
11163
+ continue;
11164
+ }
11165
+ seenEntries.add(entry);
11166
+ entries.push(entry);
11167
+ }
11168
+ return entries;
11169
+ }
11028
11170
 
11029
11171
  /**
11030
11172
  * LANGUAGE commitment definition
@@ -15627,7 +15769,7 @@
15627
15769
  const runtimeContext = (readToolRuntimeContextFromToolArgs(args) ||
15628
15770
  {});
15629
15771
  const configuredCalendars = normalizeConfiguredCalendars$1((_a = runtimeContext.calendars) === null || _a === void 0 ? void 0 : _a.connections);
15630
- const calendarArgument = normalizeOptionalText$1(args.calendarUrl);
15772
+ const calendarArgument = normalizeOptionalText$2(args.calendarUrl);
15631
15773
  let calendarReference = null;
15632
15774
  if (calendarArgument) {
15633
15775
  calendarReference = parseGoogleCalendarReference(calendarArgument);
@@ -15647,7 +15789,7 @@
15647
15789
  if (!calendarReference) {
15648
15790
  throw new Error('Calendar is required but was not resolved.');
15649
15791
  }
15650
- const accessToken = normalizeOptionalText$1((_b = runtimeContext.calendars) === null || _b === void 0 ? void 0 : _b.googleAccessToken) || '';
15792
+ const accessToken = normalizeOptionalText$2((_b = runtimeContext.calendars) === null || _b === void 0 ? void 0 : _b.googleAccessToken) || '';
15651
15793
  if (!accessToken) {
15652
15794
  throw new CalendarWalletCredentialRequiredError({
15653
15795
  calendarReference,
@@ -15680,7 +15822,7 @@
15680
15822
  continue;
15681
15823
  }
15682
15824
  const calendar = rawCalendar;
15683
- const rawUrl = normalizeOptionalText$1(calendar.url);
15825
+ const rawUrl = normalizeOptionalText$2(calendar.url);
15684
15826
  if (!rawUrl) {
15685
15827
  continue;
15686
15828
  }
@@ -15731,7 +15873,7 @@
15731
15873
  *
15732
15874
  * @private function of resolveUseCalendarToolRuntimeOrWalletCredentialResult
15733
15875
  */
15734
- function normalizeOptionalText$1(value) {
15876
+ function normalizeOptionalText$2(value) {
15735
15877
  if (typeof value !== 'string') {
15736
15878
  return undefined;
15737
15879
  }
@@ -15763,13 +15905,13 @@
15763
15905
  async [UseCalendarToolNames.listEvents](args) {
15764
15906
  return withUseCalendarRuntime(args, async ({ calendarReference, accessToken }) => {
15765
15907
  const query = {};
15766
- if (normalizeOptionalText(args.timeMin)) {
15908
+ if (normalizeOptionalText$1(args.timeMin)) {
15767
15909
  query.timeMin = args.timeMin.trim();
15768
15910
  }
15769
- if (normalizeOptionalText(args.timeMax)) {
15911
+ if (normalizeOptionalText$1(args.timeMax)) {
15770
15912
  query.timeMax = args.timeMax.trim();
15771
15913
  }
15772
- if (normalizeOptionalText(args.query)) {
15914
+ if (normalizeOptionalText$1(args.query)) {
15773
15915
  query.q = args.query.trim();
15774
15916
  }
15775
15917
  if (typeof args.maxResults === 'number' && Number.isFinite(args.maxResults) && args.maxResults > 0) {
@@ -15781,7 +15923,7 @@
15781
15923
  if (args.orderBy === 'startTime' || args.orderBy === 'updated') {
15782
15924
  query.orderBy = args.orderBy;
15783
15925
  }
15784
- if (normalizeOptionalText(args.timeZone)) {
15926
+ if (normalizeOptionalText$1(args.timeZone)) {
15785
15927
  query.timeZone = args.timeZone.trim();
15786
15928
  }
15787
15929
  const payload = await callGoogleCalendarApi(accessToken, {
@@ -15819,11 +15961,11 @@
15819
15961
  return withUseCalendarRuntime(args, async ({ calendarReference, accessToken }) => {
15820
15962
  const requestBody = createGoogleCalendarEventPayload({
15821
15963
  summary: normalizeRequiredText(args.summary, 'summary'),
15822
- description: normalizeOptionalText(args.description),
15823
- location: normalizeOptionalText(args.location),
15964
+ description: normalizeOptionalText$1(args.description),
15965
+ location: normalizeOptionalText$1(args.location),
15824
15966
  start: normalizeRequiredText(args.start, 'start'),
15825
15967
  end: normalizeRequiredText(args.end, 'end'),
15826
- timeZone: normalizeOptionalText(args.timeZone),
15968
+ timeZone: normalizeOptionalText$1(args.timeZone),
15827
15969
  attendees: normalizeAttendees(args.attendees),
15828
15970
  reminderMinutes: normalizeReminderMinutes(args.reminderMinutes),
15829
15971
  });
@@ -15845,12 +15987,12 @@
15845
15987
  return withUseCalendarRuntime(args, async ({ calendarReference, accessToken }) => {
15846
15988
  const eventId = normalizeRequiredText(args.eventId, 'eventId');
15847
15989
  const requestBody = createGoogleCalendarEventPayload({
15848
- summary: normalizeOptionalText(args.summary),
15849
- description: normalizeOptionalText(args.description),
15850
- location: normalizeOptionalText(args.location),
15851
- start: normalizeOptionalText(args.start),
15852
- end: normalizeOptionalText(args.end),
15853
- timeZone: normalizeOptionalText(args.timeZone),
15990
+ summary: normalizeOptionalText$1(args.summary),
15991
+ description: normalizeOptionalText$1(args.description),
15992
+ location: normalizeOptionalText$1(args.location),
15993
+ start: normalizeOptionalText$1(args.start),
15994
+ end: normalizeOptionalText$1(args.end),
15995
+ timeZone: normalizeOptionalText$1(args.timeZone),
15854
15996
  attendees: normalizeAttendees(args.attendees),
15855
15997
  reminderMinutes: normalizeReminderMinutes(args.reminderMinutes),
15856
15998
  });
@@ -15897,7 +16039,7 @@
15897
16039
  path: `/calendars/${encodeGoogleCalendarId(calendarReference.calendarId)}/events/${encodeURIComponent(eventId)}`,
15898
16040
  });
15899
16041
  const existingAttendees = ((existingEvent === null || existingEvent === void 0 ? void 0 : existingEvent.attendees) || [])
15900
- .map((attendee) => normalizeOptionalText(attendee.email))
16042
+ .map((attendee) => normalizeOptionalText$1(attendee.email))
15901
16043
  .filter((email) => Boolean(email));
15902
16044
  const mergedAttendees = [...new Set([...existingAttendees, ...guests])];
15903
16045
  const payload = await callGoogleCalendarApi(accessToken, {
@@ -15945,7 +16087,7 @@
15945
16087
  * @private function of createUseCalendarToolFunctions
15946
16088
  */
15947
16089
  function normalizeRequiredText(value, fieldName) {
15948
- const normalizedValue = normalizeOptionalText(value);
16090
+ const normalizedValue = normalizeOptionalText$1(value);
15949
16091
  if (!normalizedValue) {
15950
16092
  throw new Error(`Tool "${fieldName}" requires non-empty value.`);
15951
16093
  }
@@ -15956,7 +16098,7 @@
15956
16098
  *
15957
16099
  * @private function of createUseCalendarToolFunctions
15958
16100
  */
15959
- function normalizeOptionalText(value) {
16101
+ function normalizeOptionalText$1(value) {
15960
16102
  if (typeof value !== 'string') {
15961
16103
  return undefined;
15962
16104
  }
@@ -20642,6 +20784,580 @@
20642
20784
  }
20643
20785
  // Note: [💞] Ignore a discrepancy between file name and entity name
20644
20786
 
20787
+ /**
20788
+ * Names of tools used by the WALLET commitment.
20789
+ *
20790
+ * @private constant of WalletCommitmentDefinition
20791
+ */
20792
+ const WalletToolNames = {
20793
+ retrieve: 'retrieve_wallet_records',
20794
+ store: 'store_wallet_record',
20795
+ update: 'update_wallet_record',
20796
+ delete: 'delete_wallet_record',
20797
+ request: 'request_wallet_record',
20798
+ };
20799
+
20800
+ /**
20801
+ * Creates WALLET system-message instructions.
20802
+ *
20803
+ * @private function of WalletCommitmentDefinition
20804
+ */
20805
+ function createWalletSystemMessage(extraInstructions) {
20806
+ return spacetrim.spaceTrim((block) => `
20807
+ Wallet:
20808
+ - Use "${WalletToolNames.retrieve}" before authenticated operations.
20809
+ - Use "${WalletToolNames.store}" and "${WalletToolNames.update}" to maintain credentials.
20810
+ - Use "${WalletToolNames.delete}" to remove invalid credentials.
20811
+ - Use "${WalletToolNames.request}" to request missing credentials via UI popup.
20812
+ - Scope records by user (\`isUserScoped\`) and/or by agent (\`isGlobal=false\`) as needed.
20813
+ - Never expose raw credentials in chat responses.
20814
+ ${block(extraInstructions)}
20815
+ `);
20816
+ }
20817
+
20818
+ /**
20819
+ * Resolves disabled message for wallet runtime context.
20820
+ *
20821
+ * @private function of WalletCommitmentDefinition
20822
+ */
20823
+ function resolveWalletDisabledMessage(runtimeContext) {
20824
+ if (runtimeContext.isPrivateMode) {
20825
+ return 'Wallet is disabled because private mode is active.';
20826
+ }
20827
+ if (runtimeContext.isTeamConversation) {
20828
+ return 'Wallet is disabled for TEAM conversations.';
20829
+ }
20830
+ if (!runtimeContext.enabled) {
20831
+ return 'Wallet is disabled for unauthenticated users.';
20832
+ }
20833
+ return null;
20834
+ }
20835
+ /**
20836
+ * Resolves runtime adapter for wallet tools or returns disabled payload when unavailable.
20837
+ *
20838
+ * @private function of WalletCommitmentDefinition
20839
+ */
20840
+ function getWalletToolRuntimeAdapterOrDisabledResult(action, runtimeContext) {
20841
+ const disabledMessage = resolveWalletDisabledMessage(runtimeContext);
20842
+ if (disabledMessage) {
20843
+ return {
20844
+ adapter: null,
20845
+ disabledResult: {
20846
+ action,
20847
+ status: 'disabled',
20848
+ records: action === 'retrieve' ? [] : undefined,
20849
+ message: disabledMessage,
20850
+ },
20851
+ };
20852
+ }
20853
+ {
20854
+ return {
20855
+ adapter: null,
20856
+ disabledResult: {
20857
+ action,
20858
+ status: 'disabled',
20859
+ records: action === 'retrieve' ? [] : undefined,
20860
+ message: 'Wallet runtime is not available in this environment.',
20861
+ },
20862
+ };
20863
+ }
20864
+ }
20865
+
20866
+ /**
20867
+ * Parses store/update wallet payload.
20868
+ *
20869
+ * @private function of WalletCommitmentDefinition
20870
+ */
20871
+ function parseWalletPayload(args) {
20872
+ const recordType = parseWalletRecordType(args.recordType);
20873
+ return {
20874
+ recordType,
20875
+ service: parseWalletService(args.service),
20876
+ key: parseWalletKey(args.key),
20877
+ isUserScoped: args.isUserScoped === true,
20878
+ isGlobal: args.isGlobal === true,
20879
+ ...parseWalletSecrets({
20880
+ recordType,
20881
+ username: args.username,
20882
+ password: args.password,
20883
+ secret: args.secret,
20884
+ cookies: args.cookies,
20885
+ }),
20886
+ };
20887
+ }
20888
+ /**
20889
+ * Parses text argument and returns trimmed text when available.
20890
+ *
20891
+ * @private function of WalletCommitmentDefinition
20892
+ */
20893
+ function normalizeOptionalText(value) {
20894
+ if (typeof value !== 'string') {
20895
+ return undefined;
20896
+ }
20897
+ const trimmed = value.trim();
20898
+ return trimmed || undefined;
20899
+ }
20900
+ /**
20901
+ * Parses wallet service argument.
20902
+ *
20903
+ * @private function of WalletCommitmentDefinition
20904
+ */
20905
+ function parseWalletService(value) {
20906
+ return (normalizeOptionalText(value) || 'generic').toLowerCase();
20907
+ }
20908
+ /**
20909
+ * Parses wallet key argument.
20910
+ *
20911
+ * @private function of WalletCommitmentDefinition
20912
+ */
20913
+ function parseWalletKey(value) {
20914
+ return normalizeOptionalText(value) || 'default';
20915
+ }
20916
+ /**
20917
+ * Parses one wallet record id argument.
20918
+ *
20919
+ * @private function of WalletCommitmentDefinition
20920
+ */
20921
+ function parseWalletId(value) {
20922
+ const walletId = normalizeOptionalText(value);
20923
+ if (!walletId) {
20924
+ throw new Error('Wallet id is required.');
20925
+ }
20926
+ return walletId;
20927
+ }
20928
+ /**
20929
+ * Parses wallet record type.
20930
+ *
20931
+ * @private function of WalletCommitmentDefinition
20932
+ */
20933
+ function parseWalletRecordType(value, fallback) {
20934
+ var _a;
20935
+ const normalizedType = (_a = normalizeOptionalText(value)) === null || _a === void 0 ? void 0 : _a.toUpperCase();
20936
+ if (normalizedType === 'USERNAME_PASSWORD') {
20937
+ return 'USERNAME_PASSWORD';
20938
+ }
20939
+ if (normalizedType === 'SESSION_COOKIE') {
20940
+ return 'SESSION_COOKIE';
20941
+ }
20942
+ if (normalizedType === 'ACCESS_TOKEN') {
20943
+ return 'ACCESS_TOKEN';
20944
+ }
20945
+ if (fallback) {
20946
+ return fallback;
20947
+ }
20948
+ throw new Error('Unsupported wallet recordType. Expected one of: USERNAME_PASSWORD, SESSION_COOKIE, ACCESS_TOKEN.');
20949
+ }
20950
+ /**
20951
+ * Parses wallet secret fields according to record type.
20952
+ *
20953
+ * @private function of WalletCommitmentDefinition
20954
+ */
20955
+ function parseWalletSecrets(args) {
20956
+ const username = normalizeOptionalText(args.username);
20957
+ const password = normalizeOptionalText(args.password);
20958
+ const secret = normalizeOptionalText(args.secret);
20959
+ const cookies = normalizeOptionalText(args.cookies);
20960
+ if (args.recordType === 'USERNAME_PASSWORD') {
20961
+ if (!username || !password) {
20962
+ throw new Error('Both username and password are required for USERNAME_PASSWORD.');
20963
+ }
20964
+ return { username, password };
20965
+ }
20966
+ if (args.recordType === 'SESSION_COOKIE') {
20967
+ if (!cookies) {
20968
+ throw new Error('Cookies are required for SESSION_COOKIE.');
20969
+ }
20970
+ return { cookies };
20971
+ }
20972
+ if (!secret) {
20973
+ throw new Error('Secret is required for ACCESS_TOKEN.');
20974
+ }
20975
+ return { secret };
20976
+ }
20977
+ /**
20978
+ * Collection of WALLET tool argument parsers.
20979
+ *
20980
+ * @private function of WalletCommitmentDefinition
20981
+ */
20982
+ const parseWalletToolArgs = {
20983
+ /**
20984
+ * Parses retrieve arguments.
20985
+ */
20986
+ retrieve(args) {
20987
+ const limit = typeof args.limit === 'number' && Number.isFinite(args.limit) ? Math.floor(args.limit) : undefined;
20988
+ return {
20989
+ query: normalizeOptionalText(args.query),
20990
+ recordType: normalizeOptionalText(args.recordType) ? parseWalletRecordType(args.recordType) : undefined,
20991
+ service: normalizeOptionalText(args.service) ? parseWalletService(args.service) : undefined,
20992
+ key: normalizeOptionalText(args.key) ? parseWalletKey(args.key) : undefined,
20993
+ limit: limit && limit > 0 ? Math.min(limit, 20) : undefined,
20994
+ };
20995
+ },
20996
+ /**
20997
+ * Parses store payload.
20998
+ */
20999
+ store(args) {
21000
+ return parseWalletPayload(args);
21001
+ },
21002
+ /**
21003
+ * Parses update payload.
21004
+ */
21005
+ update(args) {
21006
+ const walletId = parseWalletId(args.walletId);
21007
+ const record = parseWalletPayload(args);
21008
+ return {
21009
+ ...record,
21010
+ walletId,
21011
+ };
21012
+ },
21013
+ /**
21014
+ * Parses delete payload.
21015
+ */
21016
+ delete(args) {
21017
+ return { walletId: parseWalletId(args.walletId) };
21018
+ },
21019
+ /**
21020
+ * Parses request payload for user wallet input prompt.
21021
+ */
21022
+ request(args) {
21023
+ return {
21024
+ recordType: parseWalletRecordType(args.recordType, 'ACCESS_TOKEN'),
21025
+ service: parseWalletService(args.service),
21026
+ key: parseWalletKey(args.key),
21027
+ message: normalizeOptionalText(args.message),
21028
+ isUserScoped: args.isUserScoped === true,
21029
+ isGlobal: args.isGlobal === true,
21030
+ };
21031
+ },
21032
+ };
21033
+
21034
+ /**
21035
+ * Resolves runtime context from hidden tool arguments.
21036
+ *
21037
+ * @private function of WalletCommitmentDefinition
21038
+ */
21039
+ function resolveWalletRuntimeContext(args) {
21040
+ const runtimeContext = readToolRuntimeContextFromToolArgs(args);
21041
+ const memoryContext = runtimeContext === null || runtimeContext === void 0 ? void 0 : runtimeContext.memory;
21042
+ return {
21043
+ enabled: (memoryContext === null || memoryContext === void 0 ? void 0 : memoryContext.enabled) === true,
21044
+ userId: memoryContext === null || memoryContext === void 0 ? void 0 : memoryContext.userId,
21045
+ username: memoryContext === null || memoryContext === void 0 ? void 0 : memoryContext.username,
21046
+ agentId: memoryContext === null || memoryContext === void 0 ? void 0 : memoryContext.agentId,
21047
+ agentName: memoryContext === null || memoryContext === void 0 ? void 0 : memoryContext.agentName,
21048
+ isTeamConversation: (memoryContext === null || memoryContext === void 0 ? void 0 : memoryContext.isTeamConversation) === true,
21049
+ isPrivateMode: (memoryContext === null || memoryContext === void 0 ? void 0 : memoryContext.isPrivateMode) === true,
21050
+ };
21051
+ }
21052
+
21053
+ /**
21054
+ * Creates runtime wallet tool function implementations.
21055
+ *
21056
+ * @private function of WalletCommitmentDefinition
21057
+ */
21058
+ function createWalletToolFunctions() {
21059
+ return {
21060
+ async [WalletToolNames.retrieve](args) {
21061
+ const runtimeContext = resolveWalletRuntimeContext(args);
21062
+ const { adapter, disabledResult } = getWalletToolRuntimeAdapterOrDisabledResult('retrieve', runtimeContext);
21063
+ if (!adapter || disabledResult) {
21064
+ return JSON.stringify(disabledResult);
21065
+ }
21066
+ try {
21067
+ const parsedArgs = parseWalletToolArgs.retrieve(args);
21068
+ const records = await adapter.retrieveWalletRecords(parsedArgs, runtimeContext);
21069
+ return JSON.stringify({
21070
+ action: 'retrieve',
21071
+ status: 'ok',
21072
+ query: parsedArgs.query,
21073
+ records,
21074
+ });
21075
+ }
21076
+ catch (error) {
21077
+ return JSON.stringify({
21078
+ action: 'retrieve',
21079
+ status: 'error',
21080
+ records: [],
21081
+ message: error instanceof Error ? error.message : String(error),
21082
+ });
21083
+ }
21084
+ },
21085
+ async [WalletToolNames.store](args) {
21086
+ const runtimeContext = resolveWalletRuntimeContext(args);
21087
+ const { adapter, disabledResult } = getWalletToolRuntimeAdapterOrDisabledResult('store', runtimeContext);
21088
+ if (!adapter || disabledResult) {
21089
+ return JSON.stringify(disabledResult);
21090
+ }
21091
+ try {
21092
+ const parsedArgs = parseWalletToolArgs.store(args);
21093
+ const record = await adapter.storeWalletRecord(parsedArgs, runtimeContext);
21094
+ return JSON.stringify({
21095
+ action: 'store',
21096
+ status: 'stored',
21097
+ record,
21098
+ });
21099
+ }
21100
+ catch (error) {
21101
+ return JSON.stringify({
21102
+ action: 'store',
21103
+ status: 'error',
21104
+ message: error instanceof Error ? error.message : String(error),
21105
+ });
21106
+ }
21107
+ },
21108
+ async [WalletToolNames.update](args) {
21109
+ const runtimeContext = resolveWalletRuntimeContext(args);
21110
+ const { adapter, disabledResult } = getWalletToolRuntimeAdapterOrDisabledResult('update', runtimeContext);
21111
+ if (!adapter || disabledResult) {
21112
+ return JSON.stringify(disabledResult);
21113
+ }
21114
+ try {
21115
+ const parsedArgs = parseWalletToolArgs.update(args);
21116
+ const record = await adapter.updateWalletRecord(parsedArgs, runtimeContext);
21117
+ return JSON.stringify({
21118
+ action: 'update',
21119
+ status: 'updated',
21120
+ record,
21121
+ });
21122
+ }
21123
+ catch (error) {
21124
+ return JSON.stringify({
21125
+ action: 'update',
21126
+ status: 'error',
21127
+ message: error instanceof Error ? error.message : String(error),
21128
+ });
21129
+ }
21130
+ },
21131
+ async [WalletToolNames.delete](args) {
21132
+ const runtimeContext = resolveWalletRuntimeContext(args);
21133
+ const { adapter, disabledResult } = getWalletToolRuntimeAdapterOrDisabledResult('delete', runtimeContext);
21134
+ if (!adapter || disabledResult) {
21135
+ return JSON.stringify(disabledResult);
21136
+ }
21137
+ try {
21138
+ const parsedArgs = parseWalletToolArgs.delete(args);
21139
+ const deleted = await adapter.deleteWalletRecord(parsedArgs, runtimeContext);
21140
+ return JSON.stringify({
21141
+ action: 'delete',
21142
+ status: 'deleted',
21143
+ walletId: deleted.id,
21144
+ });
21145
+ }
21146
+ catch (error) {
21147
+ return JSON.stringify({
21148
+ action: 'delete',
21149
+ status: 'error',
21150
+ message: error instanceof Error ? error.message : String(error),
21151
+ });
21152
+ }
21153
+ },
21154
+ async [WalletToolNames.request](args) {
21155
+ const runtimeContext = resolveWalletRuntimeContext(args);
21156
+ const disabledMessage = resolveWalletDisabledMessage(runtimeContext);
21157
+ if (disabledMessage) {
21158
+ return JSON.stringify({
21159
+ action: 'request',
21160
+ status: 'disabled',
21161
+ message: disabledMessage,
21162
+ });
21163
+ }
21164
+ const request = parseWalletToolArgs.request(args);
21165
+ return JSON.stringify({
21166
+ action: 'request',
21167
+ status: 'requested',
21168
+ request,
21169
+ message: request.message ||
21170
+ `Request user to provide ${request.recordType} credentials for service "${request.service}".`,
21171
+ });
21172
+ },
21173
+ };
21174
+ }
21175
+
21176
+ /**
21177
+ * Creates tool definitions required by WALLET commitment.
21178
+ *
21179
+ * @private function of WalletCommitmentDefinition
21180
+ */
21181
+ function createWalletTools(existingTools) {
21182
+ const tools = [...(existingTools || [])];
21183
+ addWalletToolIfMissing(tools, {
21184
+ name: WalletToolNames.retrieve,
21185
+ description: 'Retrieve wallet records relevant to the current task.',
21186
+ parameters: {
21187
+ type: 'object',
21188
+ properties: {
21189
+ query: { type: 'string', description: 'Optional text query used to filter wallet records.' },
21190
+ recordType: {
21191
+ type: 'string',
21192
+ description: 'Optional record type filter (USERNAME_PASSWORD, SESSION_COOKIE, ACCESS_TOKEN).',
21193
+ },
21194
+ service: { type: 'string', description: 'Optional service filter, for example github.' },
21195
+ key: { type: 'string', description: 'Optional wallet key filter.' },
21196
+ limit: { type: 'integer', description: 'Optional maximum number of records (default 5, max 20).' },
21197
+ },
21198
+ required: [],
21199
+ },
21200
+ });
21201
+ addWalletToolIfMissing(tools, {
21202
+ name: WalletToolNames.store,
21203
+ description: 'Store one wallet record.',
21204
+ parameters: {
21205
+ type: 'object',
21206
+ properties: {
21207
+ recordType: {
21208
+ type: 'string',
21209
+ description: 'Record type: USERNAME_PASSWORD, SESSION_COOKIE, ACCESS_TOKEN.',
21210
+ },
21211
+ service: { type: 'string', description: 'Service identifier, for example github.' },
21212
+ key: { type: 'string', description: 'Logical credential key.' },
21213
+ username: { type: 'string', description: 'Username for USERNAME_PASSWORD.' },
21214
+ password: { type: 'string', description: 'Password for USERNAME_PASSWORD.' },
21215
+ secret: { type: 'string', description: 'Token/API key for ACCESS_TOKEN.' },
21216
+ cookies: { type: 'string', description: 'Cookie header/json for SESSION_COOKIE.' },
21217
+ isUserScoped: { type: 'boolean', description: 'Set true to scope this record to current user.' },
21218
+ isGlobal: { type: 'boolean', description: 'Set true to make this record global.' },
21219
+ },
21220
+ required: ['recordType', 'service'],
21221
+ },
21222
+ });
21223
+ addWalletToolIfMissing(tools, {
21224
+ name: WalletToolNames.update,
21225
+ description: 'Update one existing wallet record.',
21226
+ parameters: {
21227
+ type: 'object',
21228
+ properties: {
21229
+ walletId: { type: 'string', description: 'Wallet record id to update.' },
21230
+ recordType: {
21231
+ type: 'string',
21232
+ description: 'Record type: USERNAME_PASSWORD, SESSION_COOKIE, ACCESS_TOKEN.',
21233
+ },
21234
+ service: { type: 'string', description: 'Service identifier, for example github.' },
21235
+ key: { type: 'string', description: 'Logical credential key.' },
21236
+ username: { type: 'string', description: 'Username for USERNAME_PASSWORD.' },
21237
+ password: { type: 'string', description: 'Password for USERNAME_PASSWORD.' },
21238
+ secret: { type: 'string', description: 'Token/API key for ACCESS_TOKEN.' },
21239
+ cookies: { type: 'string', description: 'Cookie header/json for SESSION_COOKIE.' },
21240
+ isUserScoped: { type: 'boolean', description: 'Set true to scope this record to current user.' },
21241
+ isGlobal: { type: 'boolean', description: 'Set true to make this record global.' },
21242
+ },
21243
+ required: ['walletId', 'recordType', 'service'],
21244
+ },
21245
+ });
21246
+ addWalletToolIfMissing(tools, {
21247
+ name: WalletToolNames.delete,
21248
+ description: 'Delete one wallet record.',
21249
+ parameters: {
21250
+ type: 'object',
21251
+ properties: {
21252
+ walletId: { type: 'string', description: 'Wallet record id to delete.' },
21253
+ },
21254
+ required: ['walletId'],
21255
+ },
21256
+ });
21257
+ addWalletToolIfMissing(tools, {
21258
+ name: WalletToolNames.request,
21259
+ description: 'Request missing credential from user via popup.',
21260
+ parameters: {
21261
+ type: 'object',
21262
+ properties: {
21263
+ recordType: {
21264
+ type: 'string',
21265
+ description: 'Requested record type: USERNAME_PASSWORD, SESSION_COOKIE, ACCESS_TOKEN.',
21266
+ },
21267
+ service: { type: 'string', description: 'Service identifier.' },
21268
+ key: { type: 'string', description: 'Logical credential key.' },
21269
+ message: { type: 'string', description: 'Optional UI message for user.' },
21270
+ isUserScoped: {
21271
+ type: 'boolean',
21272
+ description: 'Set true when record should be scoped to current user.',
21273
+ },
21274
+ isGlobal: { type: 'boolean', description: 'Set true when record should be global.' },
21275
+ },
21276
+ required: [],
21277
+ },
21278
+ });
21279
+ return tools;
21280
+ }
21281
+ /**
21282
+ * Registers one wallet tool when missing in current tool list.
21283
+ *
21284
+ * @private function of WalletCommitmentDefinition
21285
+ */
21286
+ function addWalletToolIfMissing(tools, tool) {
21287
+ if (!tools.some((existingTool) => existingTool.name === tool.name)) {
21288
+ tools.push(tool);
21289
+ }
21290
+ }
21291
+
21292
+ /**
21293
+ * Gets markdown documentation for WALLET commitment.
21294
+ *
21295
+ * @private function of WalletCommitmentDefinition
21296
+ */
21297
+ function getWalletCommitmentDocumentation(type) {
21298
+ return spacetrim.spaceTrim(`
21299
+ # ${type}
21300
+
21301
+ Enables private credential storage for tokens, usernames/passwords, and session cookies.
21302
+ `);
21303
+ }
21304
+
21305
+ /**
21306
+ * Gets human-readable titles for WALLET tool functions.
21307
+ *
21308
+ * @private function of WalletCommitmentDefinition
21309
+ */
21310
+ function getWalletToolTitles() {
21311
+ return {
21312
+ [WalletToolNames.retrieve]: 'Wallet',
21313
+ [WalletToolNames.store]: 'Store wallet record',
21314
+ [WalletToolNames.update]: 'Update wallet record',
21315
+ [WalletToolNames.delete]: 'Delete wallet record',
21316
+ [WalletToolNames.request]: 'Request wallet record',
21317
+ };
21318
+ }
21319
+
21320
+ /**
21321
+ * WALLET commitment definition.
21322
+ *
21323
+ * @private [🪔] Maybe export the commitments through some package
21324
+ */
21325
+ class WalletCommitmentDefinition extends BaseCommitmentDefinition {
21326
+ constructor(type = 'WALLET') {
21327
+ super(type);
21328
+ }
21329
+ get requiresContent() {
21330
+ return false;
21331
+ }
21332
+ get description() {
21333
+ return 'Enable persistent private credential storage (tokens, logins, cookies) scoped per agent or globally.';
21334
+ }
21335
+ get icon() {
21336
+ return '👛';
21337
+ }
21338
+ get documentation() {
21339
+ return getWalletCommitmentDocumentation(this.type);
21340
+ }
21341
+ applyToAgentModelRequirements(requirements, content) {
21342
+ const extraInstructions = formatOptionalInstructionBlock('Wallet instructions', content);
21343
+ return this.appendToSystemMessage({
21344
+ ...requirements,
21345
+ tools: createWalletTools(requirements.tools),
21346
+ _metadata: {
21347
+ ...requirements._metadata,
21348
+ useWallet: content || true,
21349
+ },
21350
+ }, createWalletSystemMessage(extraInstructions));
21351
+ }
21352
+ getToolTitles() {
21353
+ return getWalletToolTitles();
21354
+ }
21355
+ getToolFunctions() {
21356
+ return createWalletToolFunctions();
21357
+ }
21358
+ }
21359
+ // Note: [💞] Ignore a discrepancy between file name and entity name
21360
+
20645
21361
  /**
20646
21362
  * `WRITING RULES` commitment definition.
20647
21363
  *
@@ -20916,6 +21632,8 @@
20916
21632
  new MessageSuffixCommitmentDefinition(),
20917
21633
  new MessageCommitmentDefinition('MESSAGE'),
20918
21634
  new MessageCommitmentDefinition('MESSAGES'),
21635
+ new WalletCommitmentDefinition('WALLET'),
21636
+ new WalletCommitmentDefinition('WALLETS'),
20919
21637
  new ScenarioCommitmentDefinition('SCENARIO'),
20920
21638
  new ScenarioCommitmentDefinition('SCENARIOS'),
20921
21639
  new DeleteCommitmentDefinition('DELETE'),
@@ -21585,7 +22303,6 @@
21585
22303
  if (isVoidPseudoAgentReference(reference)) {
21586
22304
  label = VOID_PSEUDO_AGENT_REFERENCE; // <- {Void} label
21587
22305
  iconName = 'ShieldAlert';
21588
- return null; // <- Note: Do not show `{Void}` in capabilities, it's only used for internal logic
21589
22306
  }
21590
22307
  return {
21591
22308
  type: 'inheritance',
@@ -23796,11 +24513,11 @@
23796
24513
  */
23797
24514
  const IMPORTANT_COMMITMENT_TYPE_SORT_ORDER = new Map([
23798
24515
  ['GOAL', 0],
23799
- ['GOALS', 1],
23800
- ['RULE', 2],
23801
- ['RULES', 3],
23802
- ['KNOWLEDGE', 4],
23803
- ['TEAM', 5],
24516
+ ['RULE', 1],
24517
+ ['KNOWLEDGE', 2],
24518
+ ['TEAM', 3],
24519
+ ['GOALS', 4],
24520
+ ['RULES', 5],
23804
24521
  ]);
23805
24522
  /**
23806
24523
  * Sort rank used when unfinished, low-level, and deprecated commitments should be grouped last.
@@ -24315,6 +25032,16 @@
24315
25032
  const commitmentDefinitions = getAllCommitmentDefinitions();
24316
25033
  const commitmentTypes = [...new Set(commitmentDefinitions.map(({ type }) => type))];
24317
25034
  const commitmentDefinitionByType = new Map(commitmentDefinitions.map((definition) => [definition.type, definition]));
25035
+ const completionCommitmentTypes = [...commitmentTypes].sort((leftType, rightType) => {
25036
+ const leftDefinition = commitmentDefinitionByType.get(leftType);
25037
+ const rightDefinition = commitmentDefinitionByType.get(rightType);
25038
+ const leftRank = (leftDefinition === null || leftDefinition === void 0 ? void 0 : leftDefinition.isUnfinished) ? 1 : 0;
25039
+ const rightRank = (rightDefinition === null || rightDefinition === void 0 ? void 0 : rightDefinition.isUnfinished) ? 1 : 0;
25040
+ if (leftRank !== rightRank) {
25041
+ return leftRank - rightRank;
25042
+ }
25043
+ return commitmentTypes.indexOf(leftType) - commitmentTypes.indexOf(rightType);
25044
+ });
24318
25045
  const noteLikeCommitmentTypeSet = new Set([...TODO_COMMITMENT_TYPES, ...NOTE_COMMITMENT_TYPES]);
24319
25046
  const noteLikeCommitmentStates = createNoteLikeCommitmentStates(commitmentTypes);
24320
25047
  const executableCommitmentTypes = commitmentTypes.filter((type) => !noteLikeCommitmentTypeSet.has(type.toUpperCase()));
@@ -24371,7 +25098,7 @@
24371
25098
  startColumn: word.startColumn,
24372
25099
  endColumn: word.endColumn,
24373
25100
  };
24374
- const suggestions = commitmentTypes.map((type, index) => {
25101
+ const suggestions = completionCommitmentTypes.map((type, index) => {
24375
25102
  var _a;
24376
25103
  const definition = commitmentDefinitionByType.get(type);
24377
25104
  const notice = definition ? getCommitmentNoticeMetadata(definition) : null;
@@ -25077,22 +25804,13 @@
25077
25804
  };
25078
25805
  }
25079
25806
  /**
25080
- * Handles file uploads and placeholder rendering inside `BookEditorMonaco`.
25807
+ * Internal upload item state shared by Monaco upload helpers.
25081
25808
  *
25082
25809
  * @private function of BookEditorMonaco
25083
25810
  */
25084
- function useBookEditorMonacoUploads({ editor, monaco, onFileUpload }) {
25811
+ function useBookEditorMonacoUploadItemsState() {
25085
25812
  const [uploadItems, setUploadItemsState] = react.useState([]);
25086
25813
  const uploadItemsRef = react.useRef([]);
25087
- const uploadFilesRef = react.useRef(new Map());
25088
- const uploadDecorationIdsRef = react.useRef(new Map());
25089
- const uploadControllersRef = react.useRef(new Map());
25090
- const uploadQueueTimerRef = react.useRef(null);
25091
- const editorUpdateTimerRef = react.useRef(null);
25092
- const progressUpdateTimerRef = react.useRef(null);
25093
- const pendingReplacementsRef = react.useRef([]);
25094
- const pendingProgressUpdatesRef = react.useRef(new Map());
25095
- const processUploadQueueRef = react.useRef(() => undefined);
25096
25814
  const setUploadItems = react.useCallback((updater) => {
25097
25815
  const next = updater(uploadItemsRef.current);
25098
25816
  uploadItemsRef.current = next;
@@ -25101,6 +25819,31 @@
25101
25819
  const updateUploadItem = react.useCallback((uploadId, createNextUploadItem) => {
25102
25820
  setUploadItems((currentUploadItems) => replaceUploadItemById(currentUploadItems, uploadId, createNextUploadItem));
25103
25821
  }, [setUploadItems]);
25822
+ const uploadStats = react.useMemo(() => createUploadStats(uploadItems), [uploadItems]);
25823
+ const activeUploadItems = react.useMemo(() => uploadItems.filter((uploadItem) => uploadItem.status !== 'completed'), [uploadItems]);
25824
+ return {
25825
+ uploadItems,
25826
+ uploadItemsRef,
25827
+ setUploadItems,
25828
+ updateUploadItem,
25829
+ uploadStats,
25830
+ activeUploadItems,
25831
+ };
25832
+ }
25833
+ /**
25834
+ * Debounces upload progress updates before they hit React state.
25835
+ *
25836
+ * @private function of BookEditorMonaco
25837
+ */
25838
+ function useBookEditorMonacoUploadProgressQueue({ setUploadItems }) {
25839
+ const progressUpdateTimerRef = react.useRef(null);
25840
+ const pendingProgressUpdatesRef = react.useRef(new Map());
25841
+ const flushProgressUpdates = react.useCallback(() => {
25842
+ progressUpdateTimerRef.current = null;
25843
+ const progressUpdates = pendingProgressUpdatesRef.current;
25844
+ pendingProgressUpdatesRef.current = new Map();
25845
+ setUploadItems((currentUploadItems) => applyProgressUpdates(currentUploadItems, progressUpdates));
25846
+ }, [setUploadItems]);
25104
25847
  const queueProgressUpdate = react.useCallback((uploadId, progress, loadedBytes, totalBytes) => {
25105
25848
  pendingProgressUpdatesRef.current.set(uploadId, {
25106
25849
  progress,
@@ -25111,12 +25854,27 @@
25111
25854
  return;
25112
25855
  }
25113
25856
  progressUpdateTimerRef.current = window.setTimeout(() => {
25114
- progressUpdateTimerRef.current = null;
25115
- const progressUpdates = pendingProgressUpdatesRef.current;
25116
- pendingProgressUpdatesRef.current = new Map();
25117
- setUploadItems((currentUploadItems) => applyProgressUpdates(currentUploadItems, progressUpdates));
25857
+ flushProgressUpdates();
25118
25858
  }, BookEditorMonacoConstants.UPLOAD_PROGRESS_DEBOUNCE_MS);
25119
- }, [setUploadItems]);
25859
+ }, [flushProgressUpdates]);
25860
+ const clearProgressQueue = react.useCallback(() => {
25861
+ clearScheduledTimer(progressUpdateTimerRef.current);
25862
+ progressUpdateTimerRef.current = null;
25863
+ pendingProgressUpdatesRef.current = new Map();
25864
+ }, []);
25865
+ return {
25866
+ queueProgressUpdate,
25867
+ clearProgressQueue,
25868
+ };
25869
+ }
25870
+ /**
25871
+ * Manages Monaco placeholder insertion and later replacement with uploaded URLs.
25872
+ *
25873
+ * @private function of BookEditorMonaco
25874
+ */
25875
+ function useBookEditorMonacoUploadEditorSync({ editor, uploadFilesRef, uploadDecorationIdsRef, }) {
25876
+ const editorUpdateTimerRef = react.useRef(null);
25877
+ const pendingReplacementsRef = react.useRef([]);
25120
25878
  const flushEditorReplacements = react.useCallback(() => {
25121
25879
  if (!editor) {
25122
25880
  return;
@@ -25150,23 +25908,20 @@
25150
25908
  editor.deltaDecorations(decorationsToRemove, []);
25151
25909
  }
25152
25910
  }, [editor]);
25911
+ const registerUploadPlaceholderResources = react.useCallback((placeholders, decorationIds) => {
25912
+ placeholders.forEach((placeholder, index) => {
25913
+ uploadFilesRef.current.set(placeholder.id, placeholder.file);
25914
+ const decorationId = decorationIds[index];
25915
+ if (decorationId) {
25916
+ uploadDecorationIdsRef.current.set(placeholder.id, decorationId);
25917
+ }
25918
+ });
25919
+ }, []);
25153
25920
  const queueEditorReplacement = react.useCallback((uploadId, replacementText) => {
25154
- const decorationId = uploadDecorationIdsRef.current.get(uploadId);
25155
- if (!decorationId) {
25921
+ const hasQueuedReplacement = queueEditorReplacementItem(uploadId, replacementText, uploadDecorationIdsRef, pendingReplacementsRef);
25922
+ if (!hasQueuedReplacement) {
25156
25923
  return;
25157
25924
  }
25158
- const pendingIndex = pendingReplacementsRef.current.findIndex((item) => item.uploadId === uploadId);
25159
- const nextReplacement = {
25160
- uploadId,
25161
- decorationId,
25162
- replacementText,
25163
- };
25164
- if (pendingIndex >= 0) {
25165
- pendingReplacementsRef.current[pendingIndex] = nextReplacement;
25166
- }
25167
- else {
25168
- pendingReplacementsRef.current.push(nextReplacement);
25169
- }
25170
25925
  if (editorUpdateTimerRef.current !== null) {
25171
25926
  return;
25172
25927
  }
@@ -25174,16 +25929,81 @@
25174
25929
  editorUpdateTimerRef.current = null;
25175
25930
  flushEditorReplacements();
25176
25931
  }, BookEditorMonacoConstants.UPLOAD_EDIT_DEBOUNCE_MS);
25177
- }, [flushEditorReplacements]);
25178
- const registerUploadPlaceholderResources = react.useCallback((placeholders, decorationIds) => {
25179
- placeholders.forEach((placeholder, index) => {
25180
- uploadFilesRef.current.set(placeholder.id, placeholder.file);
25181
- const decorationId = decorationIds[index];
25182
- if (decorationId) {
25183
- uploadDecorationIdsRef.current.set(placeholder.id, decorationId);
25184
- }
25185
- });
25932
+ }, [flushEditorReplacements, uploadDecorationIdsRef]);
25933
+ const clearEditorSync = react.useCallback(() => {
25934
+ clearScheduledTimer(editorUpdateTimerRef.current);
25935
+ editorUpdateTimerRef.current = null;
25936
+ pendingReplacementsRef.current = [];
25186
25937
  }, []);
25938
+ return {
25939
+ registerUploadPlaceholderResources,
25940
+ queueEditorReplacement,
25941
+ clearEditorSync,
25942
+ };
25943
+ }
25944
+ /**
25945
+ * Enqueues replacement text for one upload placeholder.
25946
+ *
25947
+ * @private function of BookEditorMonaco
25948
+ */
25949
+ function queueEditorReplacementItem(uploadId, replacementText, uploadDecorationIdsRef, pendingReplacementsRef) {
25950
+ const decorationId = uploadDecorationIdsRef.current.get(uploadId);
25951
+ if (!decorationId) {
25952
+ return false;
25953
+ }
25954
+ const pendingIndex = pendingReplacementsRef.current.findIndex((item) => item.uploadId === uploadId);
25955
+ const nextReplacement = {
25956
+ uploadId,
25957
+ decorationId,
25958
+ replacementText,
25959
+ };
25960
+ if (pendingIndex >= 0) {
25961
+ pendingReplacementsRef.current[pendingIndex] = nextReplacement;
25962
+ }
25963
+ else {
25964
+ pendingReplacementsRef.current.push(nextReplacement);
25965
+ }
25966
+ return true;
25967
+ }
25968
+ /**
25969
+ * Inserts Monaco placeholders and queues matching upload items.
25970
+ *
25971
+ * @private function of BookEditorMonaco
25972
+ */
25973
+ function enqueueFilesForUpload({ editor, monaco, registerUploadPlaceholderResources, setUploadItems, }) {
25974
+ return (files) => {
25975
+ if (!editor || !monaco) {
25976
+ return false;
25977
+ }
25978
+ const model = editor.getModel();
25979
+ if (!model) {
25980
+ return false;
25981
+ }
25982
+ const placeholders = createUploadPlaceholderEntries(files);
25983
+ const insertPlan = createUploadPlaceholderInsertPlan(model, monaco, placeholders);
25984
+ editor.executeEdits('upload-placeholders', [
25985
+ {
25986
+ range: new monaco.Range(insertPlan.insertLine, insertPlan.insertColumn, insertPlan.insertLine, insertPlan.insertColumn),
25987
+ text: insertPlan.textToInsert,
25988
+ forceMoveMarkers: true,
25989
+ },
25990
+ ]);
25991
+ const placeholderDecorations = createUploadPlaceholderDecorations(model, monaco, placeholders, insertPlan.insertStartOffset, insertPlan.prefixLength);
25992
+ const decorationIds = editor.deltaDecorations([], placeholderDecorations);
25993
+ registerUploadPlaceholderResources(placeholders, decorationIds);
25994
+ setUploadItems((currentUploadItems) => [...currentUploadItems, ...createQueuedUploadItems(placeholders)]);
25995
+ return true;
25996
+ };
25997
+ }
25998
+ /**
25999
+ * Manages upload concurrency, retries, pausing and completion side effects.
26000
+ *
26001
+ * @private function of BookEditorMonaco
26002
+ */
26003
+ function useBookEditorMonacoUploadQueue({ onFileUpload, uploadItemsRef, uploadFilesRef, queueProgressUpdate, queueEditorReplacement, setUploadItems, updateUploadItem, }) {
26004
+ const uploadControllersRef = react.useRef(new Map());
26005
+ const uploadQueueTimerRef = react.useRef(null);
26006
+ const processUploadQueueRef = react.useRef(() => undefined);
25187
26007
  const queueUploadProcessing = react.useCallback(() => {
25188
26008
  if (uploadQueueTimerRef.current !== null) {
25189
26009
  return;
@@ -25296,6 +26116,8 @@
25296
26116
  onFileUpload,
25297
26117
  queueProgressUpdate,
25298
26118
  queueUploadProcessing,
26119
+ uploadFilesRef,
26120
+ uploadItemsRef,
25299
26121
  ]);
25300
26122
  const processUploadQueue = react.useCallback(() => {
25301
26123
  if (!onFileUpload) {
@@ -25308,34 +26130,45 @@
25308
26130
  queuedUploadIds.forEach((queuedUploadId) => {
25309
26131
  void startUpload(queuedUploadId);
25310
26132
  });
25311
- }, [onFileUpload, startUpload]);
26133
+ }, [onFileUpload, startUpload, uploadItemsRef]);
25312
26134
  processUploadQueueRef.current = processUploadQueue;
25313
26135
  const pauseUpload = react.useCallback((uploadId) => {
26136
+ var _a;
25314
26137
  pauseQueuedUpload(uploadId);
25315
- const controller = uploadControllersRef.current.get(uploadId);
25316
- controller === null || controller === void 0 ? void 0 : controller.abort();
26138
+ (_a = uploadControllersRef.current.get(uploadId)) === null || _a === void 0 ? void 0 : _a.abort();
25317
26139
  }, [pauseQueuedUpload]);
25318
26140
  const resumeUpload = react.useCallback((uploadId) => {
25319
26141
  resetUploadForRetry(uploadId);
25320
26142
  queueUploadProcessing();
25321
26143
  }, [queueUploadProcessing, resetUploadForRetry]);
25322
- react.useEffect(() => {
25323
- return () => {
25324
- clearScheduledTimer(uploadQueueTimerRef.current);
25325
- clearScheduledTimer(editorUpdateTimerRef.current);
25326
- clearScheduledTimer(progressUpdateTimerRef.current);
25327
- for (const controller of uploadControllersRef.current.values()) {
25328
- controller.abort();
25329
- }
25330
- uploadControllersRef.current.clear();
25331
- };
26144
+ const clearUploadQueue = react.useCallback(() => {
26145
+ clearScheduledTimer(uploadQueueTimerRef.current);
26146
+ uploadQueueTimerRef.current = null;
26147
+ for (const controller of uploadControllersRef.current.values()) {
26148
+ controller.abort();
26149
+ }
26150
+ uploadControllersRef.current.clear();
25332
26151
  }, []);
26152
+ react.useEffect(() => clearUploadQueue, [clearUploadQueue]);
26153
+ return {
26154
+ pauseUpload,
26155
+ resumeUpload,
26156
+ queueUploadProcessing,
26157
+ clearUploadQueue,
26158
+ };
26159
+ }
26160
+ /**
26161
+ * Clears upload UI state shortly after all uploads finish.
26162
+ *
26163
+ * @private function of BookEditorMonaco
26164
+ */
26165
+ function useCompletedUploadsAutoClear({ uploadItems, uploadFilesRef, uploadDecorationIdsRef, setUploadItems, }) {
25333
26166
  react.useEffect(() => {
25334
26167
  if (uploadItems.length === 0) {
25335
26168
  return;
25336
26169
  }
25337
- const hasActive = uploadItems.some((item) => item.status !== 'completed');
25338
- if (hasActive) {
26170
+ const hasActiveUploads = uploadItems.some((item) => item.status !== 'completed');
26171
+ if (hasActiveUploads) {
25339
26172
  return;
25340
26173
  }
25341
26174
  const timer = window.setTimeout(() => {
@@ -25346,45 +26179,61 @@
25346
26179
  return () => {
25347
26180
  clearTimeout(timer);
25348
26181
  };
25349
- }, [setUploadItems, uploadItems]);
25350
- const enqueueFilesForUpload = react.useCallback((files) => {
25351
- if (!editor || !monaco) {
25352
- return false;
25353
- }
25354
- const model = editor.getModel();
25355
- if (!model) {
25356
- return false;
25357
- }
25358
- const placeholders = createUploadPlaceholderEntries(files);
25359
- const insertPlan = createUploadPlaceholderInsertPlan(model, monaco, placeholders);
25360
- editor.executeEdits('upload-placeholders', [
25361
- {
25362
- range: new monaco.Range(insertPlan.insertLine, insertPlan.insertColumn, insertPlan.insertLine, insertPlan.insertColumn),
25363
- text: insertPlan.textToInsert,
25364
- forceMoveMarkers: true,
25365
- },
25366
- ]);
25367
- const placeholderDecorations = createUploadPlaceholderDecorations(model, monaco, placeholders, insertPlan.insertStartOffset, insertPlan.prefixLength);
25368
- const decorationIds = editor.deltaDecorations([], placeholderDecorations);
25369
- registerUploadPlaceholderResources(placeholders, decorationIds);
25370
- setUploadItems((currentUploadItems) => [...currentUploadItems, ...createQueuedUploadItems(placeholders)]);
25371
- return true;
25372
- }, [editor, monaco, registerUploadPlaceholderResources, setUploadItems]);
26182
+ }, [setUploadItems, uploadDecorationIdsRef, uploadFilesRef, uploadItems]);
26183
+ }
26184
+ /**
26185
+ * Handles file uploads and placeholder rendering inside `BookEditorMonaco`.
26186
+ *
26187
+ * @private function of BookEditorMonaco
26188
+ */
26189
+ function useBookEditorMonacoUploads({ editor, monaco, onFileUpload }) {
26190
+ const uploadFilesRef = react.useRef(new Map());
26191
+ const uploadDecorationIdsRef = react.useRef(new Map());
26192
+ const { uploadItems, uploadItemsRef, setUploadItems, updateUploadItem, uploadStats, activeUploadItems } = useBookEditorMonacoUploadItemsState();
26193
+ const { queueProgressUpdate, clearProgressQueue } = useBookEditorMonacoUploadProgressQueue({ setUploadItems });
26194
+ const { registerUploadPlaceholderResources, queueEditorReplacement, clearEditorSync } = useBookEditorMonacoUploadEditorSync({
26195
+ editor,
26196
+ uploadFilesRef,
26197
+ uploadDecorationIdsRef,
26198
+ });
26199
+ const enqueueFiles = react.useMemo(() => enqueueFilesForUpload({
26200
+ editor,
26201
+ monaco,
26202
+ registerUploadPlaceholderResources,
26203
+ setUploadItems,
26204
+ }), [editor, monaco, registerUploadPlaceholderResources, setUploadItems]);
26205
+ const { pauseUpload, resumeUpload, queueUploadProcessing, clearUploadQueue } = useBookEditorMonacoUploadQueue({
26206
+ onFileUpload,
26207
+ uploadItemsRef,
26208
+ uploadFilesRef,
26209
+ queueProgressUpdate,
26210
+ queueEditorReplacement,
26211
+ setUploadItems,
26212
+ updateUploadItem,
26213
+ });
26214
+ useCompletedUploadsAutoClear({
26215
+ uploadItems,
26216
+ uploadFilesRef,
26217
+ uploadDecorationIdsRef,
26218
+ setUploadItems,
26219
+ });
26220
+ react.useEffect(() => {
26221
+ return () => {
26222
+ clearEditorSync();
26223
+ clearProgressQueue();
26224
+ clearUploadQueue();
26225
+ };
26226
+ }, [clearEditorSync, clearProgressQueue, clearUploadQueue]);
25373
26227
  const handleFiles = react.useCallback(async (files) => {
25374
- if (!onFileUpload) {
26228
+ if (!onFileUpload || files.length === 0) {
25375
26229
  return;
25376
26230
  }
25377
- if (files.length === 0) {
25378
- return;
25379
- }
25380
- const hasQueuedUploads = enqueueFilesForUpload(files);
26231
+ const hasQueuedUploads = enqueueFiles(files);
25381
26232
  if (!hasQueuedUploads) {
25382
26233
  return;
25383
26234
  }
25384
26235
  queueUploadProcessing();
25385
- }, [enqueueFilesForUpload, onFileUpload, queueUploadProcessing]);
25386
- const uploadStats = react.useMemo(() => createUploadStats(uploadItems), [uploadItems]);
25387
- const activeUploadItems = react.useMemo(() => uploadItems.filter((uploadItem) => uploadItem.status !== 'completed'), [uploadItems]);
26236
+ }, [enqueueFiles, onFileUpload, queueUploadProcessing]);
25388
26237
  return {
25389
26238
  activeUploadItems,
25390
26239
  uploadItems,
@@ -28360,7 +29209,7 @@
28360
29209
  *
28361
29210
  * @private function of `useChatInputAreaComposer`
28362
29211
  */
28363
- function resolveTextareaSelection(textareaElement, messageContent, selectionStart, selectionEnd) {
29212
+ function resolveTextareaSelection$1(textareaElement, messageContent, selectionStart, selectionEnd) {
28364
29213
  var _a, _b;
28365
29214
  const resolvedSelectionStart = (_a = selectionStart !== null && selectionStart !== void 0 ? selectionStart : textareaElement.selectionStart) !== null && _a !== void 0 ? _a : messageContent.length;
28366
29215
  const resolvedSelectionEnd = (_b = selectionEnd !== null && selectionEnd !== void 0 ? selectionEnd : textareaElement.selectionEnd) !== null && _b !== void 0 ? _b : resolvedSelectionStart;
@@ -28418,13 +29267,21 @@
28418
29267
  areAttachmentSnapshotsEqual(snapshot.attachmentIds, uploadedFiles.map((uploadedFile) => uploadedFile.id)) &&
28419
29268
  getReplyingToMessageId(replyingToMessage) === snapshot.replyingToMessageId);
28420
29269
  }
29270
+ /**
29271
+ * Returns true when the composer contains text or attachments that can be sent.
29272
+ *
29273
+ * @private function of `useChatInputAreaComposer`
29274
+ */
29275
+ function hasSendableComposerContent(messageContent, attachmentCount) {
29276
+ return spacetrim.spaceTrim(messageContent) !== '' || attachmentCount > 0;
29277
+ }
28421
29278
  /**
28422
29279
  * Returns true when the deferred Enter snapshot still contains something sendable.
28423
29280
  *
28424
29281
  * @private function of `useChatInputAreaComposer`
28425
29282
  */
28426
29283
  function hasPendingEnterIntentContent(snapshot) {
28427
- return spacetrim.spaceTrim(snapshot.value) !== '' || snapshot.attachmentIds.length > 0;
29284
+ return hasSendableComposerContent(snapshot.value, snapshot.attachmentIds.length);
28428
29285
  }
28429
29286
  /**
28430
29287
  * Builds the attachment payload expected by `onMessage`.
@@ -28467,16 +29324,36 @@
28467
29324
  handleInsertNewline(snapshot.selectionStart, snapshot.selectionEnd);
28468
29325
  }
28469
29326
  /**
28470
- * Manages textarea state, send/newline behavior, and deferred Enter resolution for `<ChatInputArea/>`.
29327
+ * Returns the required `onMessage` callback or throws when the composer is misconfigured.
28471
29328
  *
28472
- * @private function of `<ChatInputArea/>`
29329
+ * @private function of `useChatInputAreaComposer`
28473
29330
  */
28474
- function useChatInputAreaComposer(props) {
28475
- const { onMessage, onChange, defaultMessage, enterBehavior, resolveEnterBehavior, isFocusedOnLoad, isMobile, uploadedFiles, uploadedFilesRef, clearUploadedFiles, replyingToMessage, onCancelReply, soundSystem, } = props;
28476
- const textareaRef = react.useRef(null);
29331
+ function getRequiredOnMessage(onMessage) {
29332
+ if (!onMessage) {
29333
+ throw new Error(`Can not find onMessage callback`);
29334
+ }
29335
+ return onMessage;
29336
+ }
29337
+ /**
29338
+ * Returns the active composer textarea or throws when the DOM node is missing.
29339
+ *
29340
+ * @private function of `useChatInputAreaComposer`
29341
+ */
29342
+ function getRequiredTextareaElement(textareaRef) {
29343
+ const textareaElement = textareaRef.current;
29344
+ if (!textareaElement) {
29345
+ throw new Error(`Can not find textarea`);
29346
+ }
29347
+ return textareaElement;
29348
+ }
29349
+ /**
29350
+ * Manages controlled message text state and change propagation for the composer.
29351
+ *
29352
+ * @private function of `useChatInputAreaComposer`
29353
+ */
29354
+ function useChatInputAreaMessageContentState({ defaultMessage, onChange }) {
28477
29355
  const [messageContent, setMessageContent] = react.useState(defaultMessage || '');
28478
29356
  const messageContentRef = react.useRef(messageContent);
28479
- const isResolvingEnterBehaviorRef = react.useRef(false);
28480
29357
  const applyMessageContent = react.useCallback((nextContent) => {
28481
29358
  messageContentRef.current = nextContent;
28482
29359
  setMessageContent(nextContent);
@@ -28487,6 +29364,22 @@
28487
29364
  messageContentRef.current = nextDefaultMessage;
28488
29365
  setMessageContent(nextDefaultMessage);
28489
29366
  }, [defaultMessage]);
29367
+ const handleTextInputChange = react.useCallback((event) => {
29368
+ applyMessageContent(event.target.value);
29369
+ }, [applyMessageContent]);
29370
+ return {
29371
+ messageContent,
29372
+ messageContentRef,
29373
+ applyMessageContent,
29374
+ handleTextInputChange,
29375
+ };
29376
+ }
29377
+ /**
29378
+ * Applies initial-focus behavior for the composer textarea.
29379
+ *
29380
+ * @private function of `useChatInputAreaComposer`
29381
+ */
29382
+ function useChatInputAreaComposerFocus({ textareaRef, isFocusedOnLoad, isMobile, }) {
28490
29383
  react.useEffect(( /* Focus textarea on page load */) => {
28491
29384
  if (!textareaRef.current) {
28492
29385
  return;
@@ -28495,16 +29388,20 @@
28495
29388
  if (shouldFocus) {
28496
29389
  textareaRef.current.focus();
28497
29390
  }
28498
- }, [isFocusedOnLoad, isMobile]);
28499
- const handleTextInputChange = react.useCallback((event) => {
28500
- applyMessageContent(event.target.value);
28501
- }, [applyMessageContent]);
28502
- const handleInsertNewline = react.useCallback((selectionStart, selectionEnd) => {
29391
+ }, [isFocusedOnLoad, isMobile, textareaRef]);
29392
+ }
29393
+ /**
29394
+ * Creates the newline insertion action used by the composer Enter handling.
29395
+ *
29396
+ * @private function of `useChatInputAreaComposer`
29397
+ */
29398
+ function useChatInputAreaNewlineHandler({ textareaRef, messageContentRef, applyMessageContent, }) {
29399
+ return react.useCallback((selectionStart, selectionEnd) => {
28503
29400
  const textareaElement = textareaRef.current;
28504
29401
  if (!textareaElement) {
28505
29402
  return;
28506
29403
  }
28507
- const resolvedSelection = resolveTextareaSelection(textareaElement, messageContentRef.current, selectionStart, selectionEnd);
29404
+ const resolvedSelection = resolveTextareaSelection$1(textareaElement, messageContentRef.current, selectionStart, selectionEnd);
28508
29405
  const insertion = insertTextAtSelection({
28509
29406
  currentValue: messageContentRef.current,
28510
29407
  insertedText: '\n',
@@ -28513,20 +29410,22 @@
28513
29410
  });
28514
29411
  applyMessageContent(insertion.nextValue);
28515
29412
  focusTextareaCaret(textareaElement, insertion.caret);
28516
- }, [applyMessageContent]);
28517
- const handleSend = react.useCallback(async () => {
28518
- if (!onMessage) {
28519
- throw new Error(`Can not find onMessage callback`);
28520
- }
28521
- const textareaElement = textareaRef.current;
28522
- if (!textareaElement) {
28523
- throw new Error(`Can not find textarea`);
28524
- }
29413
+ }, [applyMessageContent, messageContentRef, textareaRef]);
29414
+ }
29415
+ /**
29416
+ * Creates the send action while keeping focus restoration and attachments in sync.
29417
+ *
29418
+ * @private function of `useChatInputAreaComposer`
29419
+ */
29420
+ function useChatInputAreaSendAction({ onMessage, textareaRef, uploadedFiles, clearUploadedFiles, messageContentRef, applyMessageContent, replyingToMessage, onCancelReply, soundSystem, }) {
29421
+ return react.useCallback(async () => {
29422
+ const onMessageCallback = getRequiredOnMessage(onMessage);
29423
+ const textareaElement = getRequiredTextareaElement(textareaRef);
28525
29424
  const wasTextareaFocused = document.activeElement === textareaElement;
28526
29425
  try {
28527
29426
  const attachments = createMessageAttachments(uploadedFiles);
28528
29427
  const contentToSend = messageContentRef.current;
28529
- if (spacetrim.spaceTrim(contentToSend) === '' && attachments.length === 0) {
29428
+ if (!hasSendableComposerContent(contentToSend, attachments.length)) {
28530
29429
  throw new Error(`You need to write some text or upload a file`);
28531
29430
  }
28532
29431
  if (soundSystem) {
@@ -28537,7 +29436,7 @@
28537
29436
  if (wasTextareaFocused) {
28538
29437
  textareaElement.focus();
28539
29438
  }
28540
- await onMessage(contentToSend, attachments, replyingToMessage || null);
29439
+ await onMessageCallback(contentToSend, attachments, replyingToMessage || null);
28541
29440
  onCancelReply === null || onCancelReply === void 0 ? void 0 : onCancelReply();
28542
29441
  }
28543
29442
  catch (error) {
@@ -28550,12 +29449,22 @@
28550
29449
  }, [
28551
29450
  applyMessageContent,
28552
29451
  clearUploadedFiles,
29452
+ messageContentRef,
28553
29453
  onCancelReply,
28554
29454
  onMessage,
28555
29455
  replyingToMessage,
28556
29456
  soundSystem,
29457
+ textareaRef,
28557
29458
  uploadedFiles,
28558
29459
  ]);
29460
+ }
29461
+ /**
29462
+ * Creates the keyboard handler that coordinates reply cancel, send, newline, and deferred Enter behavior.
29463
+ *
29464
+ * @private function of `useChatInputAreaComposer`
29465
+ */
29466
+ function useChatInputAreaEnterKeyHandler({ textareaRef, enterBehavior, resolveEnterBehavior, messageContentRef, uploadedFilesRef, replyingToMessage, onCancelReply, handleInsertNewline, handleSend, }) {
29467
+ const isResolvingEnterBehaviorRef = react.useRef(false);
28559
29468
  const handleImmediateEnterAction = react.useCallback((isCtrlPressed) => {
28560
29469
  const resolvedAction = resolveChatEnterAction(enterBehavior || 'SEND', isCtrlPressed);
28561
29470
  if (resolvedAction === 'SEND') {
@@ -28590,30 +29499,78 @@
28590
29499
  }).finally(() => {
28591
29500
  isResolvingEnterBehaviorRef.current = false;
28592
29501
  });
28593
- }, [handleInsertNewline, handleSend, replyingToMessage, uploadedFilesRef]);
28594
- const handleComposerKeyDown = react.useCallback((event) => {
28595
- if (event.key === 'Escape' && replyingToMessage && onCancelReply) {
28596
- event.preventDefault();
28597
- onCancelReply();
28598
- return;
29502
+ }, [handleInsertNewline, handleSend, messageContentRef, replyingToMessage, textareaRef, uploadedFilesRef]);
29503
+ const handleReplyCancelShortcut = react.useCallback((event) => {
29504
+ if (event.key !== 'Escape' || !replyingToMessage || !onCancelReply) {
29505
+ return false;
28599
29506
  }
29507
+ event.preventDefault();
29508
+ onCancelReply();
29509
+ return true;
29510
+ }, [onCancelReply, replyingToMessage]);
29511
+ const handleComposerEnterKeyDown = react.useCallback((event) => {
28600
29512
  if (!isComposerEnterAction(event)) {
28601
- return;
29513
+ return false;
28602
29514
  }
28603
29515
  event.preventDefault();
28604
29516
  if (shouldResolveDeferredEnterBehavior(enterBehavior, event.ctrlKey, resolveEnterBehavior)) {
28605
29517
  handleDeferredEnterAction(resolveEnterBehavior);
28606
- return;
29518
+ return true;
28607
29519
  }
28608
29520
  handleImmediateEnterAction(event.ctrlKey);
28609
- }, [
28610
- enterBehavior,
28611
- handleDeferredEnterAction,
28612
- handleImmediateEnterAction,
28613
- onCancelReply,
29521
+ return true;
29522
+ }, [enterBehavior, handleDeferredEnterAction, handleImmediateEnterAction, resolveEnterBehavior]);
29523
+ return react.useCallback((event) => {
29524
+ if (handleReplyCancelShortcut(event)) {
29525
+ return;
29526
+ }
29527
+ handleComposerEnterKeyDown(event);
29528
+ }, [handleComposerEnterKeyDown, handleReplyCancelShortcut]);
29529
+ }
29530
+ /**
29531
+ * Manages textarea state, send/newline behavior, and deferred Enter resolution for `<ChatInputArea/>`.
29532
+ *
29533
+ * @private function of `<ChatInputArea/>`
29534
+ */
29535
+ function useChatInputAreaComposer(props) {
29536
+ const { onMessage, onChange, defaultMessage, enterBehavior, resolveEnterBehavior, isFocusedOnLoad, isMobile, uploadedFiles, uploadedFilesRef, clearUploadedFiles, replyingToMessage, onCancelReply, soundSystem, } = props;
29537
+ const textareaRef = react.useRef(null);
29538
+ const { messageContent, messageContentRef, applyMessageContent, handleTextInputChange } = useChatInputAreaMessageContentState({
29539
+ defaultMessage,
29540
+ onChange,
29541
+ });
29542
+ useChatInputAreaComposerFocus({
29543
+ textareaRef,
29544
+ isFocusedOnLoad,
29545
+ isMobile,
29546
+ });
29547
+ const handleInsertNewline = useChatInputAreaNewlineHandler({
29548
+ textareaRef,
29549
+ messageContentRef,
29550
+ applyMessageContent,
29551
+ });
29552
+ const handleSend = useChatInputAreaSendAction({
29553
+ onMessage,
29554
+ textareaRef,
29555
+ uploadedFiles,
29556
+ clearUploadedFiles,
29557
+ messageContentRef,
29558
+ applyMessageContent,
28614
29559
  replyingToMessage,
29560
+ onCancelReply,
29561
+ soundSystem,
29562
+ });
29563
+ const handleComposerKeyDown = useChatInputAreaEnterKeyHandler({
29564
+ textareaRef,
29565
+ enterBehavior,
28615
29566
  resolveEnterBehavior,
28616
- ]);
29567
+ messageContentRef,
29568
+ uploadedFilesRef,
29569
+ replyingToMessage,
29570
+ onCancelReply,
29571
+ handleInsertNewline,
29572
+ handleSend,
29573
+ });
28617
29574
  return {
28618
29575
  textareaRef,
28619
29576
  messageContent,
@@ -29129,33 +30086,122 @@
29129
30086
  return `${Date.now()}-${Math.random().toString(36).slice(2)}`;
29130
30087
  }
29131
30088
  /**
29132
- * Manages speech-recognition state, transcript refinement, and correction UI.
30089
+ * Resolves the current textarea selection with a stable fallback to the message length.
29133
30090
  *
29134
- * @private function of `<ChatInputArea/>`
30091
+ * @private function of `useChatInputAreaDictation`
29135
30092
  */
29136
- function useChatInputAreaDictation({ speechRecognition, speechRecognitionLanguage, textareaRef, messageContentRef, applyMessageContent, }) {
29137
- const pendingStopFallbackRef = react.useRef(null);
29138
- const replaceSelectionOnNextFinalRef = react.useRef(false);
29139
- const [dictationUiState, setDictationUiState] = react.useState('idle');
29140
- const [dictationInterimText, setDictationInterimText] = react.useState('');
29141
- const [dictationError, setDictationError] = react.useState(null);
29142
- const [dictationLastFinalChunk, setDictationLastFinalChunk] = react.useState('');
29143
- const [dictationEditableChunk, setDictationEditableChunk] = react.useState('');
29144
- const [dictationChunks, setDictationChunks] = react.useState([]);
29145
- const [isDictationPanelExpanded, setIsDictationPanelExpanded] = react.useState(false);
29146
- const { dictationSettings, setDictationSettings, dictationDictionary, setDictationDictionary } = useChatInputAreaDictationPersistence();
29147
- const { speechRecognitionUiDescriptor, resolvedSpeechRecognitionLanguage, isBrowserSpeechFallbackSupported, microphoneSettingsUrl, } = useChatInputAreaDictationSupport({
29148
- dictationUiState,
29149
- speechRecognitionLanguage,
29150
- });
29151
- const clearPendingStopFallback = react.useCallback(() => {
29152
- if (!pendingStopFallbackRef.current) {
29153
- return;
29154
- }
29155
- clearTimeout(pendingStopFallbackRef.current);
29156
- pendingStopFallbackRef.current = null;
29157
- }, []);
29158
- const focusTextareaSelection = react.useCallback((selectionStart, selectionEnd) => {
30093
+ function resolveTextareaSelection(textareaElement, fallbackLength) {
30094
+ var _a, _b;
30095
+ const selectionStart = (_a = textareaElement.selectionStart) !== null && _a !== void 0 ? _a : fallbackLength;
30096
+ const selectionEnd = (_b = textareaElement.selectionEnd) !== null && _b !== void 0 ? _b : selectionStart;
30097
+ return {
30098
+ selectionStart,
30099
+ selectionEnd,
30100
+ };
30101
+ }
30102
+ /**
30103
+ * Creates the persisted metadata entry for one finalized dictated chunk.
30104
+ *
30105
+ * @private function of `useChatInputAreaDictation`
30106
+ */
30107
+ function createFinalizedDictationChunk(params) {
30108
+ const { beforeValue, finalText, start } = params;
30109
+ return {
30110
+ id: createDictationChunkId(),
30111
+ beforeValue,
30112
+ finalText,
30113
+ start,
30114
+ };
30115
+ }
30116
+ /**
30117
+ * Clears the pending stop fallback timeout when one exists.
30118
+ *
30119
+ * @private function of `useChatInputAreaDictation`
30120
+ */
30121
+ function clearPendingStopFallbackTimeout(pendingStopFallbackRef) {
30122
+ if (!pendingStopFallbackRef.current) {
30123
+ return;
30124
+ }
30125
+ clearTimeout(pendingStopFallbackRef.current);
30126
+ pendingStopFallbackRef.current = null;
30127
+ }
30128
+ /**
30129
+ * Captures whether the next finalized chunk should replace the current selection.
30130
+ *
30131
+ * @private function of `useChatInputAreaDictation`
30132
+ */
30133
+ function captureSelectionReplacementIntent(textareaRef, replaceSelectionOnNextFinalRef) {
30134
+ const textarea = textareaRef.current;
30135
+ if (!textarea) {
30136
+ return;
30137
+ }
30138
+ const { selectionStart, selectionEnd } = resolveTextareaSelection(textarea, 0);
30139
+ replaceSelectionOnNextFinalRef.current = selectionStart !== selectionEnd;
30140
+ }
30141
+ /**
30142
+ * Produces the corrected chunk value only when a real correction should be applied.
30143
+ *
30144
+ * @private function of `useChatInputAreaDictation`
30145
+ */
30146
+ function resolveCorrectedDictationChunk(dictationEditableChunk, dictationLastFinalChunk) {
30147
+ const correctedChunk = normalizeDictationWhitespace(dictationEditableChunk);
30148
+ if (!correctedChunk || !dictationLastFinalChunk || correctedChunk === dictationLastFinalChunk) {
30149
+ return null;
30150
+ }
30151
+ return correctedChunk;
30152
+ }
30153
+ /**
30154
+ * Replaces the last tracked chunk text inside the tracked chunk list.
30155
+ *
30156
+ * @private function of `useChatInputAreaDictation`
30157
+ */
30158
+ function replaceLastDictationChunk(previousChunks, correctedChunk) {
30159
+ if (previousChunks.length === 0) {
30160
+ return previousChunks;
30161
+ }
30162
+ const nextChunks = [...previousChunks];
30163
+ const lastChunk = nextChunks[nextChunks.length - 1];
30164
+ if (!lastChunk) {
30165
+ return previousChunks;
30166
+ }
30167
+ nextChunks[nextChunks.length - 1] = {
30168
+ ...lastChunk,
30169
+ finalText: correctedChunk,
30170
+ };
30171
+ return nextChunks;
30172
+ }
30173
+ /**
30174
+ * Opens browser microphone settings in a new tab when a settings URL is available.
30175
+ *
30176
+ * @private function of `useChatInputAreaDictation`
30177
+ */
30178
+ function openBrowserSettings(microphoneSettingsUrl) {
30179
+ if (!microphoneSettingsUrl) {
30180
+ return;
30181
+ }
30182
+ window.open(microphoneSettingsUrl, '_blank', 'noopener,noreferrer');
30183
+ }
30184
+ /**
30185
+ * Resolves whether the dictation panel should be visible for the current state snapshot.
30186
+ *
30187
+ * @private function of `useChatInputAreaDictation`
30188
+ */
30189
+ function resolveShouldShowDictationPanel(params) {
30190
+ const { speechRecognition, isDictationPanelExpanded, dictationUiState, dictationInterimText, dictationError, dictationLastFinalChunk, } = params;
30191
+ return Boolean(speechRecognition &&
30192
+ (isDictationPanelExpanded ||
30193
+ dictationUiState !== 'idle' ||
30194
+ Boolean(dictationInterimText) ||
30195
+ Boolean(dictationError) ||
30196
+ Boolean(dictationLastFinalChunk)));
30197
+ }
30198
+ /**
30199
+ * Focuses the textarea and restores a given selection range after programmatic edits.
30200
+ *
30201
+ * @private function of `useChatInputAreaDictation`
30202
+ */
30203
+ function useTextareaSelectionFocus(textareaRef) {
30204
+ return react.useCallback((selectionStart, selectionEnd) => {
29159
30205
  requestAnimationFrame(() => {
29160
30206
  const textarea = textareaRef.current;
29161
30207
  if (!textarea) {
@@ -29165,8 +30211,15 @@
29165
30211
  textarea.setSelectionRange(selectionStart, selectionEnd);
29166
30212
  });
29167
30213
  }, [textareaRef]);
29168
- const handleDictationFinalResult = react.useCallback((rawText) => {
29169
- var _a, _b;
30214
+ }
30215
+ /**
30216
+ * Builds the final-result handler that refines text, inserts it, and exposes it for correction.
30217
+ *
30218
+ * @private function of `useChatInputAreaDictation`
30219
+ */
30220
+ function useChatInputAreaDictationFinalResultHandler({ textareaRef, messageContentRef, applyMessageContent, dictationSettings, dictationDictionary, replaceSelectionOnNextFinalRef, focusTextareaSelection, state, }) {
30221
+ const { setDictationUiState, setDictationInterimText, setDictationError, setDictationLastFinalChunk, setDictationEditableChunk, setDictationChunks, setIsDictationPanelExpanded, } = state;
30222
+ return react.useCallback((rawText) => {
29170
30223
  const textarea = textareaRef.current;
29171
30224
  if (!textarea) {
29172
30225
  return;
@@ -29176,8 +30229,7 @@
29176
30229
  if (!refinedText) {
29177
30230
  return;
29178
30231
  }
29179
- const selectionStart = (_a = textarea.selectionStart) !== null && _a !== void 0 ? _a : previousMessageContent.length;
29180
- const selectionEnd = (_b = textarea.selectionEnd) !== null && _b !== void 0 ? _b : selectionStart;
30232
+ const { selectionStart, selectionEnd } = resolveTextareaSelection(textarea, previousMessageContent.length);
29181
30233
  const insertion = insertDictationChunk({
29182
30234
  currentValue: previousMessageContent,
29183
30235
  dictatedText: refinedText,
@@ -29192,12 +30244,11 @@
29192
30244
  applyMessageContent(insertion.nextValue);
29193
30245
  setDictationChunks((previousChunks) => [
29194
30246
  ...previousChunks,
29195
- {
29196
- id: createDictationChunkId(),
30247
+ createFinalizedDictationChunk({
29197
30248
  beforeValue: previousMessageContent,
29198
30249
  finalText: refinedText,
29199
30250
  start: insertion.start,
29200
- },
30251
+ }),
29201
30252
  ]);
29202
30253
  setDictationLastFinalChunk(refinedText);
29203
30254
  setDictationEditableChunk(refinedText);
@@ -29209,9 +30260,25 @@
29209
30260
  dictationSettings,
29210
30261
  focusTextareaSelection,
29211
30262
  messageContentRef,
30263
+ replaceSelectionOnNextFinalRef,
30264
+ setDictationChunks,
30265
+ setDictationEditableChunk,
30266
+ setDictationError,
30267
+ setDictationInterimText,
30268
+ setDictationLastFinalChunk,
30269
+ setDictationUiState,
30270
+ setIsDictationPanelExpanded,
29212
30271
  textareaRef,
29213
30272
  ]);
29214
- const handleSpeechRecognitionEvent = react.useCallback((event) => {
30273
+ }
30274
+ /**
30275
+ * Builds the event handler that translates speech-recognition events into dictation UI updates.
30276
+ *
30277
+ * @private function of `useChatInputAreaDictation`
30278
+ */
30279
+ function useChatInputAreaDictationSpeechRecognitionEventHandler({ clearPendingStopFallback, handleDictationFinalResult, state, }) {
30280
+ const { setDictationUiState, setDictationInterimText, setDictationError, setIsDictationPanelExpanded } = state;
30281
+ return react.useCallback((event) => {
29215
30282
  switch (event.type) {
29216
30283
  case 'START':
29217
30284
  clearPendingStopFallback();
@@ -29245,7 +30312,21 @@
29245
30312
  setDictationInterimText('');
29246
30313
  return;
29247
30314
  }
29248
- }, [clearPendingStopFallback, handleDictationFinalResult]);
30315
+ }, [
30316
+ clearPendingStopFallback,
30317
+ handleDictationFinalResult,
30318
+ setDictationError,
30319
+ setDictationInterimText,
30320
+ setDictationUiState,
30321
+ setIsDictationPanelExpanded,
30322
+ ]);
30323
+ }
30324
+ /**
30325
+ * Wires the speech-recognition subscription and shutdown cleanup for dictation.
30326
+ *
30327
+ * @private function of `useChatInputAreaDictation`
30328
+ */
30329
+ function useChatInputAreaDictationSpeechRecognitionLifecycle({ speechRecognition, clearPendingStopFallback, handleSpeechRecognitionEvent, }) {
29249
30330
  react.useEffect(() => {
29250
30331
  if (!speechRecognition) {
29251
30332
  return;
@@ -29262,26 +30343,37 @@
29262
30343
  speechRecognition === null || speechRecognition === void 0 ? void 0 : speechRecognition.$stop();
29263
30344
  };
29264
30345
  }, [clearPendingStopFallback, speechRecognition]);
30346
+ }
30347
+ /**
30348
+ * Builds start/stop/toggle handlers for the speech-recognition session.
30349
+ *
30350
+ * @private function of `useChatInputAreaDictation`
30351
+ */
30352
+ function useChatInputAreaDictationVoiceInputControls({ speechRecognition, textareaRef, resolvedSpeechRecognitionLanguage, whisperMode, pendingStopFallbackRef, replaceSelectionOnNextFinalRef, clearPendingStopFallback, state, }) {
30353
+ const { dictationUiState, setDictationUiState, setDictationInterimText, setDictationError } = state;
29265
30354
  const startVoiceInput = react.useCallback(() => {
29266
- var _a, _b;
29267
30355
  if (!speechRecognition) {
29268
30356
  return;
29269
30357
  }
29270
- const textarea = textareaRef.current;
29271
- if (textarea) {
29272
- const selectionStart = (_a = textarea.selectionStart) !== null && _a !== void 0 ? _a : 0;
29273
- const selectionEnd = (_b = textarea.selectionEnd) !== null && _b !== void 0 ? _b : selectionStart;
29274
- replaceSelectionOnNextFinalRef.current = selectionStart !== selectionEnd;
29275
- }
30358
+ captureSelectionReplacementIntent(textareaRef, replaceSelectionOnNextFinalRef);
29276
30359
  setDictationError(null);
29277
30360
  setDictationInterimText('');
29278
30361
  setDictationUiState('listening');
29279
30362
  speechRecognition.$start({
29280
30363
  language: resolvedSpeechRecognitionLanguage,
29281
30364
  interimResults: true,
29282
- whisperMode: dictationSettings.whisperMode,
30365
+ whisperMode,
29283
30366
  });
29284
- }, [dictationSettings.whisperMode, resolvedSpeechRecognitionLanguage, speechRecognition, textareaRef]);
30367
+ }, [
30368
+ replaceSelectionOnNextFinalRef,
30369
+ resolvedSpeechRecognitionLanguage,
30370
+ setDictationError,
30371
+ setDictationInterimText,
30372
+ setDictationUiState,
30373
+ speechRecognition,
30374
+ textareaRef,
30375
+ whisperMode,
30376
+ ]);
29285
30377
  const stopVoiceInput = react.useCallback(() => {
29286
30378
  if (!speechRecognition) {
29287
30379
  return;
@@ -29293,7 +30385,13 @@
29293
30385
  setDictationUiState('idle');
29294
30386
  setDictationInterimText('');
29295
30387
  }, STOP_LISTENING_FALLBACK_TIMEOUT_MS);
29296
- }, [clearPendingStopFallback, speechRecognition]);
30388
+ }, [
30389
+ clearPendingStopFallback,
30390
+ pendingStopFallbackRef,
30391
+ setDictationInterimText,
30392
+ setDictationUiState,
30393
+ speechRecognition,
30394
+ ]);
29297
30395
  const handleToggleVoiceInput = react.useCallback(() => {
29298
30396
  if (!speechRecognition) {
29299
30397
  return;
@@ -29304,6 +30402,22 @@
29304
30402
  }
29305
30403
  startVoiceInput();
29306
30404
  }, [dictationUiState, speechRecognition, startVoiceInput, stopVoiceInput]);
30405
+ const handleRetryPermissionRequest = react.useCallback(() => {
30406
+ setDictationError(null);
30407
+ setDictationUiState('idle');
30408
+ handleToggleVoiceInput();
30409
+ }, [handleToggleVoiceInput, setDictationError, setDictationUiState]);
30410
+ return {
30411
+ handleToggleVoiceInput,
30412
+ handleRetryPermissionRequest,
30413
+ };
30414
+ }
30415
+ /**
30416
+ * Builds the correction and backtrack handlers for the latest finalized chunk.
30417
+ *
30418
+ * @private function of `useChatInputAreaDictation`
30419
+ */
30420
+ function useChatInputAreaDictationCorrectionHandlers({ dictationChunks, dictationLastFinalChunk, dictationEditableChunk, dictationDictionary, messageContentRef, applyMessageContent, focusTextareaSelection, setDictationChunks, setDictationLastFinalChunk, setDictationEditableChunk, setDictationDictionary, }) {
29307
30421
  const handleBacktrackLastChunk = react.useCallback(() => {
29308
30422
  var _a;
29309
30423
  const previousChunks = [...dictationChunks];
@@ -29317,50 +30431,123 @@
29317
30431
  setDictationLastFinalChunk(previousFinalChunk);
29318
30432
  setDictationEditableChunk(previousFinalChunk);
29319
30433
  focusTextareaSelection(lastChunk.start, lastChunk.start);
29320
- }, [applyMessageContent, dictationChunks, focusTextareaSelection]);
30434
+ }, [
30435
+ applyMessageContent,
30436
+ dictationChunks,
30437
+ focusTextareaSelection,
30438
+ setDictationChunks,
30439
+ setDictationEditableChunk,
30440
+ setDictationLastFinalChunk,
30441
+ ]);
29321
30442
  const handleApplyCorrection = react.useCallback(() => {
29322
- const correctedChunk = normalizeDictationWhitespace(dictationEditableChunk);
29323
- const previousChunk = dictationLastFinalChunk;
29324
- if (!correctedChunk || !previousChunk || correctedChunk === previousChunk) {
30443
+ const correctedChunk = resolveCorrectedDictationChunk(dictationEditableChunk, dictationLastFinalChunk);
30444
+ if (!correctedChunk) {
29325
30445
  return;
29326
30446
  }
29327
- const nextMessageContent = replaceLastOccurrence(messageContentRef.current, previousChunk, correctedChunk);
30447
+ const nextMessageContent = replaceLastOccurrence(messageContentRef.current, dictationLastFinalChunk, correctedChunk);
29328
30448
  applyMessageContent(nextMessageContent);
29329
30449
  setDictationLastFinalChunk(correctedChunk);
29330
- setDictationChunks((previousChunks) => {
29331
- if (previousChunks.length === 0) {
29332
- return previousChunks;
29333
- }
29334
- const nextChunks = [...previousChunks];
29335
- const lastChunk = nextChunks[nextChunks.length - 1];
29336
- if (!lastChunk) {
29337
- return previousChunks;
29338
- }
29339
- nextChunks[nextChunks.length - 1] = {
29340
- ...lastChunk,
29341
- finalText: correctedChunk,
29342
- };
29343
- return nextChunks;
29344
- });
29345
- setDictationDictionary(learnDictationDictionary(previousChunk, correctedChunk, dictationDictionary));
30450
+ setDictationChunks((previousChunks) => replaceLastDictationChunk(previousChunks, correctedChunk));
30451
+ setDictationDictionary(learnDictationDictionary(dictationLastFinalChunk, correctedChunk, dictationDictionary));
29346
30452
  }, [
29347
30453
  applyMessageContent,
29348
30454
  dictationDictionary,
29349
30455
  dictationEditableChunk,
29350
30456
  dictationLastFinalChunk,
29351
30457
  messageContentRef,
30458
+ setDictationChunks,
29352
30459
  setDictationDictionary,
30460
+ setDictationLastFinalChunk,
29353
30461
  ]);
29354
- const handleRetryPermissionRequest = react.useCallback(() => {
29355
- setDictationError(null);
29356
- setDictationUiState('idle');
29357
- handleToggleVoiceInput();
29358
- }, [handleToggleVoiceInput]);
30462
+ return {
30463
+ handleBacktrackLastChunk,
30464
+ handleApplyCorrection,
30465
+ };
30466
+ }
30467
+ /**
30468
+ * Manages speech-recognition state, transcript refinement, and correction UI.
30469
+ *
30470
+ * @private function of `<ChatInputArea/>`
30471
+ */
30472
+ function useChatInputAreaDictation({ speechRecognition, speechRecognitionLanguage, textareaRef, messageContentRef, applyMessageContent, }) {
30473
+ const pendingStopFallbackRef = react.useRef(null);
30474
+ const replaceSelectionOnNextFinalRef = react.useRef(false);
30475
+ const [dictationUiState, setDictationUiState] = react.useState('idle');
30476
+ const [dictationInterimText, setDictationInterimText] = react.useState('');
30477
+ const [dictationError, setDictationError] = react.useState(null);
30478
+ const [dictationLastFinalChunk, setDictationLastFinalChunk] = react.useState('');
30479
+ const [dictationEditableChunk, setDictationEditableChunk] = react.useState('');
30480
+ const [dictationChunks, setDictationChunks] = react.useState([]);
30481
+ const [isDictationPanelExpanded, setIsDictationPanelExpanded] = react.useState(false);
30482
+ const { dictationSettings, setDictationSettings, dictationDictionary, setDictationDictionary } = useChatInputAreaDictationPersistence();
30483
+ const { speechRecognitionUiDescriptor, resolvedSpeechRecognitionLanguage, isBrowserSpeechFallbackSupported, microphoneSettingsUrl, } = useChatInputAreaDictationSupport({
30484
+ dictationUiState,
30485
+ speechRecognitionLanguage,
30486
+ });
30487
+ const dictationState = {
30488
+ dictationUiState,
30489
+ setDictationUiState,
30490
+ setDictationInterimText,
30491
+ setDictationError,
30492
+ setDictationLastFinalChunk,
30493
+ setDictationEditableChunk,
30494
+ setDictationChunks,
30495
+ setIsDictationPanelExpanded,
30496
+ };
30497
+ const clearPendingStopFallback = react.useCallback(() => {
30498
+ clearPendingStopFallbackTimeout(pendingStopFallbackRef);
30499
+ }, []);
30500
+ const focusTextareaSelection = useTextareaSelectionFocus(textareaRef);
30501
+ const handleDictationFinalResult = useChatInputAreaDictationFinalResultHandler({
30502
+ textareaRef,
30503
+ messageContentRef,
30504
+ applyMessageContent,
30505
+ dictationSettings,
30506
+ dictationDictionary,
30507
+ replaceSelectionOnNextFinalRef,
30508
+ focusTextareaSelection,
30509
+ state: dictationState,
30510
+ });
30511
+ const handleSpeechRecognitionEvent = useChatInputAreaDictationSpeechRecognitionEventHandler({
30512
+ clearPendingStopFallback,
30513
+ handleDictationFinalResult,
30514
+ state: dictationState,
30515
+ });
30516
+ useChatInputAreaDictationSpeechRecognitionLifecycle({
30517
+ speechRecognition,
30518
+ clearPendingStopFallback,
30519
+ handleSpeechRecognitionEvent,
30520
+ });
30521
+ const { handleToggleVoiceInput, handleRetryPermissionRequest } = useChatInputAreaDictationVoiceInputControls({
30522
+ speechRecognition,
30523
+ textareaRef,
30524
+ resolvedSpeechRecognitionLanguage,
30525
+ whisperMode: dictationSettings.whisperMode,
30526
+ pendingStopFallbackRef,
30527
+ replaceSelectionOnNextFinalRef,
30528
+ clearPendingStopFallback,
30529
+ state: {
30530
+ dictationUiState,
30531
+ setDictationUiState,
30532
+ setDictationInterimText,
30533
+ setDictationError,
30534
+ },
30535
+ });
30536
+ const { handleBacktrackLastChunk, handleApplyCorrection } = useChatInputAreaDictationCorrectionHandlers({
30537
+ dictationChunks,
30538
+ dictationLastFinalChunk,
30539
+ dictationEditableChunk,
30540
+ dictationDictionary,
30541
+ messageContentRef,
30542
+ applyMessageContent,
30543
+ focusTextareaSelection,
30544
+ setDictationChunks,
30545
+ setDictationLastFinalChunk,
30546
+ setDictationEditableChunk,
30547
+ setDictationDictionary,
30548
+ });
29359
30549
  const handleOpenBrowserSettings = react.useCallback(() => {
29360
- if (!microphoneSettingsUrl) {
29361
- return;
29362
- }
29363
- window.open(microphoneSettingsUrl, '_blank', 'noopener,noreferrer');
30550
+ openBrowserSettings(microphoneSettingsUrl);
29364
30551
  }, [microphoneSettingsUrl]);
29365
30552
  const toggleDictationPanel = react.useCallback(() => {
29366
30553
  setIsDictationPanelExpanded((value) => !value);
@@ -29374,12 +30561,14 @@
29374
30561
  [settingName]: checked,
29375
30562
  }));
29376
30563
  }, [setDictationSettings]);
29377
- const shouldShowDictationPanel = Boolean(speechRecognition &&
29378
- (isDictationPanelExpanded ||
29379
- dictationUiState !== 'idle' ||
29380
- Boolean(dictationInterimText) ||
29381
- Boolean(dictationError) ||
29382
- Boolean(dictationLastFinalChunk)));
30564
+ const shouldShowDictationPanel = resolveShouldShowDictationPanel({
30565
+ speechRecognition,
30566
+ isDictationPanelExpanded,
30567
+ dictationUiState,
30568
+ dictationInterimText,
30569
+ dictationError,
30570
+ dictationLastFinalChunk,
30571
+ });
29383
30572
  return {
29384
30573
  speechRecognitionUiDescriptor,
29385
30574
  shouldShowDictationPanel,
@@ -29404,13 +30593,206 @@
29404
30593
  };
29405
30594
  }
29406
30595
 
30596
+ /**
30597
+ * Creates the shared color styles used by the composer.
30598
+ *
30599
+ * @private component of `<ChatInputArea/>`
30600
+ */
30601
+ function createChatInputAreaColorModel(participants, buttonColor) {
30602
+ var _a;
30603
+ const myColor = ((_a = participants.find((participant) => participant.isMe)) === null || _a === void 0 ? void 0 : _a.color) || USER_CHAT_COLOR;
30604
+ const inputBgColor = Color.from(myColor).then(lighten(0.4)).then(grayscale(0.7));
30605
+ const inputTextColor = inputBgColor.then(textColor);
30606
+ return {
30607
+ inputContainerStyle: {
30608
+ '--chat-placeholder-color': '#fff',
30609
+ '--input-bg-color': inputBgColor.toHex(),
30610
+ '--input-text-color': inputTextColor.toHex(),
30611
+ '--brand-color': buttonColor.toHex(),
30612
+ },
30613
+ primaryButtonStyle: {
30614
+ backgroundColor: buttonColor.toHex(),
30615
+ color: buttonColor.then(textColor).toHex(),
30616
+ },
30617
+ };
30618
+ }
30619
+ /**
30620
+ * Builds the reply preview view model when the composer is replying to a message.
30621
+ *
30622
+ * @private component of `<ChatInputArea/>`
30623
+ */
30624
+ function createChatInputAreaReplyPreviewModel(params) {
30625
+ const { replyingToMessage, participants, chatUiTranslations, onCancelReply } = params;
30626
+ if (!replyingToMessage) {
30627
+ return null;
30628
+ }
30629
+ const previewText = resolveChatMessageReplyPreviewText(replyingToMessage, {
30630
+ maxLength: 180,
30631
+ emptyLabel: 'Original message',
30632
+ });
30633
+ const senderLabel = resolveChatMessageReplySenderLabel({
30634
+ sender: replyingToMessage.sender,
30635
+ participants,
30636
+ });
30637
+ if (!previewText || !senderLabel) {
30638
+ return null;
30639
+ }
30640
+ return {
30641
+ label: (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.replyingToLabel) || 'Replying to',
30642
+ senderLabel,
30643
+ previewText,
30644
+ dismissLabel: (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.cancelReplyLabel) || 'Cancel reply',
30645
+ onDismiss: onCancelReply || undefined,
30646
+ };
30647
+ }
30648
+ /**
30649
+ * Adds drag-and-drop handlers only when uploads are enabled.
30650
+ *
30651
+ * @private component of `<ChatInputArea/>`
30652
+ */
30653
+ function createChatInputAreaRootProps(params) {
30654
+ const { onFileUpload, handleDrop, handleDragOver, handleDragLeave } = params;
30655
+ if (!onFileUpload) {
30656
+ return {};
30657
+ }
30658
+ return {
30659
+ onDrop: handleDrop,
30660
+ onDragOver: handleDragOver,
30661
+ onDragLeave: handleDragLeave,
30662
+ };
30663
+ }
30664
+ /**
30665
+ * Formats uploaded file sizes for the preview chips.
30666
+ *
30667
+ * @private component of `<ChatInputArea/>`
30668
+ */
30669
+ function formatUploadedFileSizeInKilobytes(fileSizeInBytes) {
30670
+ return `${(fileSizeInBytes / 1024).toFixed(1)} KB`;
30671
+ }
30672
+ /**
30673
+ * Renders the optional reply preview section.
30674
+ *
30675
+ * @private component of `<ChatInputArea/>`
30676
+ */
30677
+ function ChatInputAreaReplyPreviewSection({ replyPreview }) {
30678
+ if (!replyPreview) {
30679
+ return null;
30680
+ }
30681
+ return (jsxRuntime.jsx(ChatReplyPreview, { label: replyPreview.label, senderLabel: replyPreview.senderLabel, previewText: replyPreview.previewText, className: styles$5.replyComposerPreview, dismissLabel: replyPreview.dismissLabel, onDismiss: replyPreview.onDismiss }));
30682
+ }
30683
+ /**
30684
+ * Renders uploaded file previews above the composer.
30685
+ *
30686
+ * @private component of `<ChatInputArea/>`
30687
+ */
30688
+ function ChatInputAreaUploadedFilesSection(props) {
30689
+ const { uploadedFiles, removeUploadedFile } = props;
30690
+ if (uploadedFiles.length === 0) {
30691
+ return null;
30692
+ }
30693
+ return (jsxRuntime.jsx("div", { className: styles$5.filePreviewContainer, children: uploadedFiles.map((uploadedFile) => (jsxRuntime.jsxs("div", { className: styles$5.filePreview, children: [jsxRuntime.jsx("div", { className: styles$5.fileIcon, children: "\uD83D\uDCCE" }), jsxRuntime.jsxs("div", { className: styles$5.fileInfo, children: [jsxRuntime.jsx("div", { className: styles$5.fileName, children: uploadedFile.file.name }), jsxRuntime.jsx("div", { className: styles$5.fileSize, children: formatUploadedFileSizeInKilobytes(uploadedFile.file.size) })] }), jsxRuntime.jsx("button", { className: styles$5.removeFileButton, onClick: () => removeUploadedFile(uploadedFile.id), title: "Remove file", children: jsxRuntime.jsx(CloseIcon, {}) })] }, uploadedFile.id))) }));
30694
+ }
30695
+ /**
30696
+ * Renders the hidden file input and attachment trigger when uploads are enabled.
30697
+ *
30698
+ * @private component of `<ChatInputArea/>`
30699
+ */
30700
+ function ChatInputAreaAttachmentButton(props) {
30701
+ const { onFileUpload, fileInputRef, handleFileInputChange, onButtonClick, openFilePicker, isUploading, primaryButtonStyle, } = props;
30702
+ if (!onFileUpload) {
30703
+ return null;
30704
+ }
30705
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("input", { ref: fileInputRef, type: "file", multiple: true, style: { display: 'none' }, onChange: handleFileInputChange }), jsxRuntime.jsx("button", { type: "button", style: primaryButtonStyle, className: classNames(styles$5.attachmentButton, chatCssClassNames.inputAttachmentButton), onClick: onButtonClick(openFilePicker), disabled: isUploading, title: "Attach file", children: jsxRuntime.jsx(AttachmentIcon, { size: 20 }) })] }));
30706
+ }
30707
+ /**
30708
+ * Creates the voice button colors from the current dictation state.
30709
+ *
30710
+ * @private component of `<ChatInputArea/>`
30711
+ */
30712
+ function createVoiceButtonStyle(params) {
30713
+ const { buttonColor, speechRecognitionUiDescriptor } = params;
30714
+ if (speechRecognitionUiDescriptor.isButtonActive) {
30715
+ return {
30716
+ backgroundColor: Color.from('#ff4444').toHex(),
30717
+ color: Color.from('#ffffff').toHex(),
30718
+ };
30719
+ }
30720
+ return {
30721
+ backgroundColor: buttonColor.toHex(),
30722
+ color: buttonColor.then(textColor).toHex(),
30723
+ };
30724
+ }
30725
+ /**
30726
+ * Renders the voice input trigger when speech recognition is available.
30727
+ *
30728
+ * @private component of `<ChatInputArea/>`
30729
+ */
30730
+ function ChatInputAreaVoiceButton(props) {
30731
+ const { speechRecognition, isVoiceCalling, buttonColor, speechRecognitionUiDescriptor, onButtonClick, handleToggleVoiceInput, } = props;
30732
+ if (!speechRecognition) {
30733
+ return null;
30734
+ }
30735
+ return (jsxRuntime.jsx("button", { "data-button-type": "voice", disabled: speechRecognitionUiDescriptor.isButtonDisabled, style: createVoiceButtonStyle({
30736
+ buttonColor,
30737
+ speechRecognitionUiDescriptor,
30738
+ }), className: classNames(styles$5.voiceButton, chatCssClassNames.inputVoiceButton, (isVoiceCalling || speechRecognitionUiDescriptor.isButtonActive) && styles$5.voiceButtonActive), onClick: onButtonClick((event) => {
30739
+ event.preventDefault();
30740
+ handleToggleVoiceInput();
30741
+ }), title: speechRecognitionUiDescriptor.buttonTitle, "aria-label": speechRecognitionUiDescriptor.buttonTitle, children: jsxRuntime.jsx(MicIcon, { size: 25 }) }));
30742
+ }
30743
+ /**
30744
+ * Renders the composer send button.
30745
+ *
30746
+ * @private component of `<ChatInputArea/>`
30747
+ */
30748
+ function ChatInputAreaSendButton(props) {
30749
+ const { primaryButtonStyle, onButtonClick, handleSend } = props;
30750
+ return (jsxRuntime.jsx("button", { "data-button-type": "call-to-action", className: chatCssClassNames.inputSendButton, style: primaryButtonStyle, onClick: onButtonClick((event) => {
30751
+ event.preventDefault();
30752
+ /* not await */ handleSend();
30753
+ }), children: jsxRuntime.jsx(SendIcon, { size: 25 }) }));
30754
+ }
30755
+ /**
30756
+ * Renders the optional dictation panel below the composer.
30757
+ *
30758
+ * @private component of `<ChatInputArea/>`
30759
+ */
30760
+ function ChatInputAreaDictationPanelSection(props) {
30761
+ const { speechRecognition } = props;
30762
+ if (!speechRecognition) {
30763
+ return null;
30764
+ }
30765
+ return (jsxRuntime.jsx(ChatInputAreaDictationPanel, { bubbleText: props.speechRecognitionUiDescriptor.bubbleText, bubbleTone: props.speechRecognitionUiDescriptor.bubbleTone, shouldShowPanel: props.shouldShowDictationPanel, isExpanded: props.isDictationPanelExpanded, interimText: props.dictationInterimText, error: props.dictationError, lastFinalChunk: props.dictationLastFinalChunk, editableChunk: props.dictationEditableChunk, canBacktrack: props.canBacktrack, dictationSettings: props.dictationSettings, isBrowserSpeechFallbackSupported: props.isBrowserSpeechFallbackSupported, canOpenBrowserSettings: props.canOpenBrowserSettings, onToggleExpanded: props.toggleDictationPanel, onExpand: props.expandDictationPanel, onEditableChunkChange: props.setDictationEditableChunk, onRetryPermissionRequest: props.handleRetryPermissionRequest, onOpenBrowserSettings: props.handleOpenBrowserSettings, onApplyCorrection: props.handleApplyCorrection, onBacktrackLastChunk: props.handleBacktrackLastChunk, onDictationSettingChange: props.handleDictationSettingChange }));
30766
+ }
30767
+ /**
30768
+ * Renders the temporary upload progress indicator.
30769
+ *
30770
+ * @private component of `<ChatInputArea/>`
30771
+ */
30772
+ function ChatInputAreaUploadProgress({ isUploading }) {
30773
+ if (!isUploading) {
30774
+ return null;
30775
+ }
30776
+ return (jsxRuntime.jsxs("div", { className: styles$5.uploadProgress, children: [jsxRuntime.jsx("div", { className: styles$5.uploadProgressBar, children: jsxRuntime.jsx("div", { className: styles$5.uploadProgressFill }) }), jsxRuntime.jsx("span", { children: "Uploading files..." })] }));
30777
+ }
30778
+ /**
30779
+ * Renders the drag-and-drop overlay when uploads are enabled and active.
30780
+ *
30781
+ * @private component of `<ChatInputArea/>`
30782
+ */
30783
+ function ChatInputAreaDragOverlay(props) {
30784
+ const { isDragOver, onFileUpload } = props;
30785
+ if (!isDragOver || !onFileUpload) {
30786
+ return null;
30787
+ }
30788
+ return (jsxRuntime.jsx("div", { className: styles$5.dragOverlay, children: jsxRuntime.jsxs("div", { className: styles$5.dragOverlayContent, children: [jsxRuntime.jsx(AttachmentIcon, { size: 48 }), jsxRuntime.jsx("span", { children: "Drop files here to upload" })] }) }));
30789
+ }
29407
30790
  /**
29408
30791
  * Renders the chat input area with text, file upload, and voice controls.
29409
30792
  *
29410
30793
  * @private component of `<Chat/>`
29411
30794
  */
29412
30795
  function ChatInputArea(props) {
29413
- var _a;
29414
30796
  const { onMessage, onChange, onFileUpload, speechRecognition, speechRecognitionLanguage, defaultMessage, replyingToMessage, onCancelReply, enterBehavior, resolveEnterBehavior, placeholderMessageContent, isFocusedOnLoad, isMobile, isVoiceCalling, participants, buttonColor, soundSystem, onButtonClick, chatInputClassName, chatUiTranslations, } = props;
29415
30797
  const { fileInputRef, uploadedFiles, uploadedFilesRef, isDragOver, isUploading, handleDrop, handleDragOver, handleDragLeave, handlePaste, handleFileInputChange, removeUploadedFile, clearUploadedFiles, openFilePicker, } = useChatInputAreaAttachments({
29416
30798
  onFileUpload,
@@ -29440,52 +30822,20 @@
29440
30822
  if (!onMessage) {
29441
30823
  return null;
29442
30824
  }
29443
- const myColor = ((_a = participants.find((participant) => participant.isMe)) === null || _a === void 0 ? void 0 : _a.color) || USER_CHAT_COLOR;
29444
- const inputBgColor = Color.from(myColor).then(lighten(0.4)).then(grayscale(0.7));
29445
- const inputTextColor = inputBgColor.then(textColor);
29446
- const replyPreviewLabel = (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.replyingToLabel) || 'Replying to';
29447
- const cancelReplyLabel = (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.cancelReplyLabel) || 'Cancel reply';
29448
- const replyPreviewText = replyingToMessage
29449
- ? resolveChatMessageReplyPreviewText(replyingToMessage, { maxLength: 180, emptyLabel: 'Original message' })
29450
- : null;
29451
- const replySenderLabel = replyingToMessage
29452
- ? resolveChatMessageReplySenderLabel({
29453
- sender: replyingToMessage.sender,
29454
- participants,
29455
- })
29456
- : null;
29457
- return (jsxRuntime.jsxs("div", { className: classNames(styles$5.chatInput, chatInputClassName, isDragOver && styles$5.dragOver), ...(onFileUpload
29458
- ? {
29459
- onDrop: handleDrop,
29460
- onDragOver: handleDragOver,
29461
- onDragLeave: handleDragLeave,
29462
- }
29463
- : {}), children: [replyingToMessage && replyPreviewText && replySenderLabel && (jsxRuntime.jsx(ChatReplyPreview, { label: replyPreviewLabel, senderLabel: replySenderLabel, previewText: replyPreviewText, className: styles$5.replyComposerPreview, dismissLabel: cancelReplyLabel, onDismiss: onCancelReply || undefined })), uploadedFiles.length > 0 && (jsxRuntime.jsx("div", { className: styles$5.filePreviewContainer, children: uploadedFiles.map((uploadedFile) => (jsxRuntime.jsxs("div", { className: styles$5.filePreview, children: [jsxRuntime.jsx("div", { className: styles$5.fileIcon, children: "\uD83D\uDCCE" }), jsxRuntime.jsxs("div", { className: styles$5.fileInfo, children: [jsxRuntime.jsx("div", { className: styles$5.fileName, children: uploadedFile.file.name }), jsxRuntime.jsxs("div", { className: styles$5.fileSize, children: [(uploadedFile.file.size / 1024).toFixed(1), " KB"] })] }), jsxRuntime.jsx("button", { className: styles$5.removeFileButton, onClick: () => removeUploadedFile(uploadedFile.id), title: "Remove file", children: jsxRuntime.jsx(CloseIcon, {}) })] }, uploadedFile.id))) })), jsxRuntime.jsxs("div", { className: classNames(styles$5.inputContainer, chatCssClassNames.inputContainer), style: {
29464
- '--chat-placeholder-color': '#fff',
29465
- '--input-bg-color': inputBgColor.toHex(),
29466
- '--input-text-color': inputTextColor.toHex(),
29467
- '--brand-color': buttonColor.toHex(),
29468
- }, children: [jsxRuntime.jsx("textarea", { ref: textareaRef, className: chatCssClassNames.inputTextarea, onPaste: handlePaste, value: messageContent, placeholder: placeholderMessageContent || 'Write a message...', onChange: handleTextInputChange, onKeyDown: handleComposerKeyDown }), onFileUpload && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("input", { ref: fileInputRef, type: "file", multiple: true, style: { display: 'none' }, onChange: handleFileInputChange }), jsxRuntime.jsx("button", { type: "button", style: {
29469
- backgroundColor: buttonColor.toHex(),
29470
- color: buttonColor.then(textColor).toHex(),
29471
- }, className: classNames(styles$5.attachmentButton, chatCssClassNames.inputAttachmentButton), onClick: onButtonClick(openFilePicker), disabled: isUploading, title: "Attach file", children: jsxRuntime.jsx(AttachmentIcon, { size: 20 }) })] })), speechRecognition && (jsxRuntime.jsx("button", { "data-button-type": "voice", disabled: speechRecognitionUiDescriptor.isButtonDisabled, style: {
29472
- backgroundColor: speechRecognitionUiDescriptor.isButtonActive
29473
- ? Color.from('#ff4444').toHex()
29474
- : buttonColor.toHex(),
29475
- color: speechRecognitionUiDescriptor.isButtonActive
29476
- ? Color.from('#ffffff').toHex()
29477
- : buttonColor.then(textColor).toHex(),
29478
- }, className: classNames(styles$5.voiceButton, chatCssClassNames.inputVoiceButton, (isVoiceCalling || speechRecognitionUiDescriptor.isButtonActive) &&
29479
- styles$5.voiceButtonActive), onClick: onButtonClick((event) => {
29480
- event.preventDefault();
29481
- handleToggleVoiceInput();
29482
- }), title: speechRecognitionUiDescriptor.buttonTitle, "aria-label": speechRecognitionUiDescriptor.buttonTitle, children: jsxRuntime.jsx(MicIcon, { size: 25 }) })), jsxRuntime.jsx("button", { "data-button-type": "call-to-action", className: chatCssClassNames.inputSendButton, style: {
29483
- backgroundColor: buttonColor.toHex(),
29484
- color: buttonColor.then(textColor).toHex(),
29485
- }, onClick: onButtonClick((event) => {
29486
- event.preventDefault();
29487
- /* not await */ handleSend();
29488
- }), children: jsxRuntime.jsx(SendIcon, { size: 25 }) })] }), speechRecognition && (jsxRuntime.jsx(ChatInputAreaDictationPanel, { bubbleText: speechRecognitionUiDescriptor.bubbleText, bubbleTone: speechRecognitionUiDescriptor.bubbleTone, shouldShowPanel: shouldShowDictationPanel, isExpanded: isDictationPanelExpanded, interimText: dictationInterimText, error: dictationError, lastFinalChunk: dictationLastFinalChunk, editableChunk: dictationEditableChunk, canBacktrack: canBacktrack, dictationSettings: dictationSettings, isBrowserSpeechFallbackSupported: isBrowserSpeechFallbackSupported, canOpenBrowserSettings: canOpenBrowserSettings, onToggleExpanded: toggleDictationPanel, onExpand: expandDictationPanel, onEditableChunkChange: setDictationEditableChunk, onRetryPermissionRequest: handleRetryPermissionRequest, onOpenBrowserSettings: handleOpenBrowserSettings, onApplyCorrection: handleApplyCorrection, onBacktrackLastChunk: handleBacktrackLastChunk, onDictationSettingChange: handleDictationSettingChange })), isUploading && (jsxRuntime.jsxs("div", { className: styles$5.uploadProgress, children: [jsxRuntime.jsx("div", { className: styles$5.uploadProgressBar, children: jsxRuntime.jsx("div", { className: styles$5.uploadProgressFill }) }), jsxRuntime.jsx("span", { children: "Uploading files..." })] })), isDragOver && onFileUpload && (jsxRuntime.jsx("div", { className: styles$5.dragOverlay, children: jsxRuntime.jsxs("div", { className: styles$5.dragOverlayContent, children: [jsxRuntime.jsx(AttachmentIcon, { size: 48 }), jsxRuntime.jsx("span", { children: "Drop files here to upload" })] }) }))] }));
30825
+ const { inputContainerStyle, primaryButtonStyle } = createChatInputAreaColorModel(participants, buttonColor);
30826
+ const replyPreview = createChatInputAreaReplyPreviewModel({
30827
+ replyingToMessage,
30828
+ participants,
30829
+ chatUiTranslations,
30830
+ onCancelReply,
30831
+ });
30832
+ const rootProps = createChatInputAreaRootProps({
30833
+ onFileUpload,
30834
+ handleDrop,
30835
+ handleDragOver,
30836
+ handleDragLeave,
30837
+ });
30838
+ return (jsxRuntime.jsxs("div", { className: classNames(styles$5.chatInput, chatInputClassName, isDragOver && styles$5.dragOver), ...rootProps, children: [jsxRuntime.jsx(ChatInputAreaReplyPreviewSection, { replyPreview: replyPreview }), jsxRuntime.jsx(ChatInputAreaUploadedFilesSection, { uploadedFiles: uploadedFiles, removeUploadedFile: removeUploadedFile }), jsxRuntime.jsxs("div", { className: classNames(styles$5.inputContainer, chatCssClassNames.inputContainer), style: inputContainerStyle, children: [jsxRuntime.jsx("textarea", { ref: textareaRef, className: chatCssClassNames.inputTextarea, onPaste: handlePaste, value: messageContent, placeholder: placeholderMessageContent || 'Write a message...', onChange: handleTextInputChange, onKeyDown: handleComposerKeyDown }), jsxRuntime.jsx(ChatInputAreaAttachmentButton, { onFileUpload: onFileUpload, fileInputRef: fileInputRef, handleFileInputChange: handleFileInputChange, onButtonClick: onButtonClick, openFilePicker: openFilePicker, isUploading: isUploading, primaryButtonStyle: primaryButtonStyle }), jsxRuntime.jsx(ChatInputAreaVoiceButton, { speechRecognition: speechRecognition, isVoiceCalling: isVoiceCalling, buttonColor: buttonColor, speechRecognitionUiDescriptor: speechRecognitionUiDescriptor, onButtonClick: onButtonClick, handleToggleVoiceInput: handleToggleVoiceInput }), jsxRuntime.jsx(ChatInputAreaSendButton, { primaryButtonStyle: primaryButtonStyle, onButtonClick: onButtonClick, handleSend: handleSend })] }), jsxRuntime.jsx(ChatInputAreaDictationPanelSection, { speechRecognition: speechRecognition, speechRecognitionUiDescriptor: speechRecognitionUiDescriptor, shouldShowDictationPanel: shouldShowDictationPanel, isDictationPanelExpanded: isDictationPanelExpanded, dictationInterimText: dictationInterimText, dictationError: dictationError, dictationLastFinalChunk: dictationLastFinalChunk, dictationEditableChunk: dictationEditableChunk, canBacktrack: canBacktrack, dictationSettings: dictationSettings, isBrowserSpeechFallbackSupported: isBrowserSpeechFallbackSupported, canOpenBrowserSettings: canOpenBrowserSettings, toggleDictationPanel: toggleDictationPanel, expandDictationPanel: expandDictationPanel, setDictationEditableChunk: setDictationEditableChunk, handleRetryPermissionRequest: handleRetryPermissionRequest, handleOpenBrowserSettings: handleOpenBrowserSettings, handleApplyCorrection: handleApplyCorrection, handleBacktrackLastChunk: handleBacktrackLastChunk, handleDictationSettingChange: handleDictationSettingChange }), jsxRuntime.jsx(ChatInputAreaUploadProgress, { isUploading: isUploading }), jsxRuntime.jsx(ChatInputAreaDragOverlay, { isDragOver: isDragOver, onFileUpload: onFileUpload })] }));
29489
30839
  }
29490
30840
 
29491
30841
  // Note: [💞] Ignore a discrepancy between file name and entity name
@@ -36448,6 +37798,12 @@
36448
37798
  ['GOAL', 'GOAL'],
36449
37799
  ['GOALS', 'GOAL'],
36450
37800
  ]);
37801
+ /**
37802
+ * Legacy commitments that should be parsed for compatibility but ignored by the model-requirements pipeline.
37803
+ *
37804
+ * @private internal constant of `filterCommitmentsForAgentModelRequirements`
37805
+ */
37806
+ const IGNORED_COMMITMENT_TYPES = new Set(['WALLET', 'WALLETS']);
36451
37807
  /**
36452
37808
  * Applies the commitment filtering rules used before commitment definitions are executed.
36453
37809
  *
@@ -36496,6 +37852,9 @@
36496
37852
  function filterDeletedCommitments(commitments) {
36497
37853
  const filteredCommitments = [];
36498
37854
  for (const commitment of commitments) {
37855
+ if (isIgnoredCommitmentType(commitment.type)) {
37856
+ continue;
37857
+ }
36499
37858
  if (!isDeleteCommitmentType(commitment.type)) {
36500
37859
  filteredCommitments.push(commitment);
36501
37860
  continue;
@@ -36526,6 +37885,17 @@
36526
37885
  function isDeleteCommitmentType(commitmentType) {
36527
37886
  return DELETE_COMMITMENT_TYPES.has(commitmentType);
36528
37887
  }
37888
+ /**
37889
+ * Checks whether a parsed commitment is intentionally ignored by the current model compiler.
37890
+ *
37891
+ * @param commitmentType - Commitment type to check.
37892
+ * @returns `true` when the commitment should not affect model requirements.
37893
+ *
37894
+ * @private internal utility of `filterDeletedCommitments`
37895
+ */
37896
+ function isIgnoredCommitmentType(commitmentType) {
37897
+ return IGNORED_COMMITMENT_TYPES.has(commitmentType);
37898
+ }
36529
37899
  /**
36530
37900
  * Extracts normalized parameter names used for DELETE-like invalidation matching.
36531
37901
  *
@@ -41223,8 +42593,8 @@
41223
42593
  * Prepares an AgentKit agent with optional knowledge sources and tool definitions.
41224
42594
  */
41225
42595
  async prepareAgentKitAgent(options) {
41226
- var _a, _b, _c;
41227
- const { name, instructions, knowledgeSources, tools, nativeAgentKitTools, vectorStoreId: cachedVectorStoreId, storeAsPrepared, } = options;
42596
+ var _a, _b;
42597
+ const { name, instructions, knowledgeSources, tools, vectorStoreId: cachedVectorStoreId, storeAsPrepared, } = options;
41228
42598
  await this.ensureAgentKitDefaults();
41229
42599
  if (this.options.isVerbose) {
41230
42600
  console.info('[🤰]', 'Preparing OpenAI AgentKit agent', {
@@ -41232,11 +42602,10 @@
41232
42602
  instructionsLength: instructions.length,
41233
42603
  knowledgeSourcesCount: (_a = knowledgeSources === null || knowledgeSources === void 0 ? void 0 : knowledgeSources.length) !== null && _a !== void 0 ? _a : 0,
41234
42604
  toolsCount: (_b = tools === null || tools === void 0 ? void 0 : tools.length) !== null && _b !== void 0 ? _b : 0,
41235
- nativeAgentKitToolsCount: (_c = nativeAgentKitTools === null || nativeAgentKitTools === void 0 ? void 0 : nativeAgentKitTools.length) !== null && _c !== void 0 ? _c : 0,
41236
42605
  });
41237
42606
  }
41238
42607
  let vectorStoreId = cachedVectorStoreId;
41239
- if (!vectorStoreId && knowledgeSources && knowledgeSources.length > 0) {
42608
+ if (this.isNativeKnowledgeSearchEnabled && !vectorStoreId && knowledgeSources && knowledgeSources.length > 0) {
41240
42609
  const vectorStoreResult = await this.createVectorStoreWithKnowledgeSources({
41241
42610
  client: await this.getClient(),
41242
42611
  name,
@@ -41245,13 +42614,19 @@
41245
42614
  });
41246
42615
  vectorStoreId = vectorStoreResult.vectorStoreId;
41247
42616
  }
41248
- else if (vectorStoreId && this.options.isVerbose) {
42617
+ else if (this.isNativeKnowledgeSearchEnabled && vectorStoreId && this.options.isVerbose) {
41249
42618
  console.info('[🤰]', 'Using cached vector store for AgentKit agent', {
41250
42619
  name,
41251
42620
  vectorStoreId,
41252
42621
  });
41253
42622
  }
41254
- const agentKitTools = this.buildAgentKitTools({ tools, nativeAgentKitTools, vectorStoreId });
42623
+ if (!this.isNativeKnowledgeSearchEnabled) {
42624
+ vectorStoreId = undefined;
42625
+ }
42626
+ const agentKitTools = this.buildAgentKitTools({
42627
+ tools,
42628
+ vectorStoreId,
42629
+ });
41255
42630
  const openAiAgentKitAgent = new agents.Agent({
41256
42631
  name,
41257
42632
  model: this.agentKitModelName,
@@ -41270,7 +42645,7 @@
41270
42645
  name,
41271
42646
  model: this.agentKitModelName,
41272
42647
  toolCount: agentKitTools.length,
41273
- hasVectorStore: Boolean(vectorStoreId),
42648
+ hasVectorStore: this.isNativeKnowledgeSearchEnabled && Boolean(vectorStoreId),
41274
42649
  });
41275
42650
  }
41276
42651
  return preparedAgent;
@@ -41290,14 +42665,11 @@
41290
42665
  * Builds the tool list for AgentKit, including hosted file search when applicable.
41291
42666
  */
41292
42667
  buildAgentKitTools(options) {
41293
- const { tools, nativeAgentKitTools, vectorStoreId } = options;
42668
+ const { tools, vectorStoreId } = options;
41294
42669
  const agentKitTools = [];
41295
42670
  if (vectorStoreId) {
41296
42671
  agentKitTools.push(agents.fileSearchTool(vectorStoreId));
41297
42672
  }
41298
- if (nativeAgentKitTools && nativeAgentKitTools.length > 0) {
41299
- agentKitTools.push(...nativeAgentKitTools);
41300
- }
41301
42673
  if (tools && tools.length > 0) {
41302
42674
  let scriptTools = null;
41303
42675
  for (const toolDefinition of tools) {
@@ -41772,6 +43144,12 @@
41772
43144
  get agentKitOptions() {
41773
43145
  return this.options;
41774
43146
  }
43147
+ /**
43148
+ * Returns true when hosted OpenAI vector-store search should back `knowledgeSources`.
43149
+ */
43150
+ get isNativeKnowledgeSearchEnabled() {
43151
+ return this.agentKitOptions.isNativeKnowledgeSearchEnabled !== false;
43152
+ }
41775
43153
  /**
41776
43154
  * Discriminant for type guards.
41777
43155
  */
@@ -47499,6 +48877,7 @@
47499
48877
  request_wallet_record: { title: 'Requesting wallet', emoji: '👛' },
47500
48878
  web_search: { title: 'Searching the web', emoji: '🔎' },
47501
48879
  deep_search: { title: 'Deep research', emoji: '🔬' },
48880
+ knowledge_search: { title: 'Searching knowledge', emoji: '📚' },
47502
48881
  useSearchEngine: { title: 'Searching the web', emoji: '🔎' },
47503
48882
  search: { title: 'Searching the web', emoji: '🔎' },
47504
48883
  useBrowser: { title: 'Browsing the web', emoji: '🌐' },
@@ -48374,6 +49753,13 @@
48374
49753
  content: sanitizedContentWithoutButtons,
48375
49754
  citations: message.citations,
48376
49755
  }), [message.citations, sanitizedContentWithoutButtons]);
49756
+ const structuredSourceCitations = react.useMemo(() => {
49757
+ const footnoteSourceKeys = new Set(citationFootnoteRenderModel.footnotes.map((footnote) => footnote.citation.source.trim().toLowerCase()));
49758
+ return (message.sources || []).filter((source) => {
49759
+ const sourceKey = source.source.trim().toLowerCase();
49760
+ return sourceKey && !footnoteSourceKeys.has(sourceKey);
49761
+ });
49762
+ }, [citationFootnoteRenderModel.footnotes, message.sources]);
48377
49763
  const contentSegments = react.useMemo(() => splitMessageContentIntoSegments(citationFootnoteRenderModel.content), [citationFootnoteRenderModel.content]);
48378
49764
  const hasMapSegment = react.useMemo(() => contentSegments.some((segment) => segment.type === 'map'), [contentSegments]);
48379
49765
  const [localHoveredRating, setLocalHoveredRating] = react.useState(0);
@@ -48645,7 +50031,7 @@
48645
50031
  '--message-bg-color': isAgentArticleMode ? articleModeBackgroundColor : color.toHex(),
48646
50032
  '--message-text-color': isAgentArticleMode ? articleModeTextColor : colorOfText.toHex(),
48647
50033
  '--chat-message-swipe-offset': swipeTranslation,
48648
- }, onPointerDown: handleReplyPointerDown, onPointerMove: handleReplyPointerMove, onPointerUp: handleReplyPointerEnd, onPointerCancel: resetReplySwipe, children: [isReplyActionEnabled && (jsxRuntime.jsx("div", { className: classNames(styles$5.replySwipeIndicator, isMe && styles$5.replySwipeIndicatorRight, isReplySwipeArmed && styles$5.replySwipeIndicatorActive), "aria-hidden": "true", children: jsxRuntime.jsx(lucideReact.Reply, { className: styles$5.replySwipeIndicatorIcon }) })), !shouldRenderArticleActionsBar && renderMessageReadAndCopyControls(), message.isVoiceCall && (jsxRuntime.jsx("div", { className: styles$5.voiceCallIndicator, children: jsxRuntime.jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor", children: jsxRuntime.jsx("path", { d: "M6.62 10.79c1.44 2.83 3.76 5.14 6.59 6.59l2.2-2.2c.27-.27.67-.36 1.02-.24 1.12.37 2.33.57 3.57.57.55 0 1 .45 1 1V20c0 .55-.45 1-1 1-9.39 0-17-7.61-17-17 0-.55.45-1 1-1h3.5c.55 0 1 .45 1 1 0 1.25.2 2.45.57 3.57.11.35.03.74-.25 1.02l-2.2 2.2z" }) }) })), replyingToMessage && replyPreviewText && replySenderLabel && (jsxRuntime.jsx(ChatReplyPreview, { label: replyPreviewLabel, senderLabel: replySenderLabel, previewText: replyPreviewText, className: styles$5.replyBubblePreview })), jsxRuntime.jsx("div", { ref: contentWithoutButtonsRef, children: jsxRuntime.jsx(ChatMessageRichContent, { content: message.content, contentSegments: contentSegments, streamingFeaturePlaceholderKind: streamingFeaturePlaceholderKind, onCreateAgent: onCreateAgent, mode: mode }) }), message.attachments && message.attachments.length > 0 && (jsxRuntime.jsx("div", { className: styles$5.attachments, children: message.attachments.map((attachment, index) => (jsxRuntime.jsxs("a", { href: attachment.url, target: "_blank", rel: "noopener noreferrer", className: styles$5.attachment, title: attachment.name, children: [jsxRuntime.jsx("span", { className: styles$5.attachmentIcon, children: "\uD83D\uDCCE" }), jsxRuntime.jsx("span", { className: styles$5.attachmentName, children: attachment.name })] }, index))) })), jsxRuntime.jsx(ChatMessageToolCallChips, { chips: toolCallChips, onToolCallClick: onToolCallClick }), citationFootnoteRenderModel.footnotes.length > 0 && (jsxRuntime.jsx("div", { className: styles$5.citationFootnotes, children: citationFootnoteRenderModel.footnotes.map((footnote) => (jsxRuntime.jsxs("div", { className: styles$5.citationFootnoteItem, children: [jsxRuntime.jsx("span", { className: styles$5.citationFootnoteNumber, children: footnote.number }), jsxRuntime.jsx(SourceChip, { citation: footnote.citation, onClick: onCitationClick, isCitationIdVisible: false })] }, `citation-footnote-${footnote.number}-${footnote.citation.source}`))) })), transitiveCitations.length > 0 && (jsxRuntime.jsx("div", { className: styles$5.sourceCitations, children: transitiveCitations.map((citation, index) => (jsxRuntime.jsx(SourceChip, { citation: citation, suffix: `by ${citation.origin.label}`, onClick: onCitationClick }, `team-source-${citation.source}-${index}`))) })), shouldShowButtons && (jsxRuntime.jsx("div", { className: styles$5.messageButtons, children: renderableButtons.map(({ button, buttonIndex }) => (jsxRuntime.jsx("button", { type: "button", className: classNames(styles$5.messageButton, button.type === 'action' && styles$5.actionMessageButton), onClick: (event) => {
50034
+ }, onPointerDown: handleReplyPointerDown, onPointerMove: handleReplyPointerMove, onPointerUp: handleReplyPointerEnd, onPointerCancel: resetReplySwipe, children: [isReplyActionEnabled && (jsxRuntime.jsx("div", { className: classNames(styles$5.replySwipeIndicator, isMe && styles$5.replySwipeIndicatorRight, isReplySwipeArmed && styles$5.replySwipeIndicatorActive), "aria-hidden": "true", children: jsxRuntime.jsx(lucideReact.Reply, { className: styles$5.replySwipeIndicatorIcon }) })), !shouldRenderArticleActionsBar && renderMessageReadAndCopyControls(), message.isVoiceCall && (jsxRuntime.jsx("div", { className: styles$5.voiceCallIndicator, children: jsxRuntime.jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor", children: jsxRuntime.jsx("path", { d: "M6.62 10.79c1.44 2.83 3.76 5.14 6.59 6.59l2.2-2.2c.27-.27.67-.36 1.02-.24 1.12.37 2.33.57 3.57.57.55 0 1 .45 1 1V20c0 .55-.45 1-1 1-9.39 0-17-7.61-17-17 0-.55.45-1 1-1h3.5c.55 0 1 .45 1 1 0 1.25.2 2.45.57 3.57.11.35.03.74-.25 1.02l-2.2 2.2z" }) }) })), replyingToMessage && replyPreviewText && replySenderLabel && (jsxRuntime.jsx(ChatReplyPreview, { label: replyPreviewLabel, senderLabel: replySenderLabel, previewText: replyPreviewText, className: styles$5.replyBubblePreview })), jsxRuntime.jsx("div", { ref: contentWithoutButtonsRef, children: jsxRuntime.jsx(ChatMessageRichContent, { content: message.content, contentSegments: contentSegments, streamingFeaturePlaceholderKind: streamingFeaturePlaceholderKind, onCreateAgent: onCreateAgent, mode: mode }) }), message.attachments && message.attachments.length > 0 && (jsxRuntime.jsx("div", { className: styles$5.attachments, children: message.attachments.map((attachment, index) => (jsxRuntime.jsxs("a", { href: attachment.url, target: "_blank", rel: "noopener noreferrer", className: styles$5.attachment, title: attachment.name, children: [jsxRuntime.jsx("span", { className: styles$5.attachmentIcon, children: "\uD83D\uDCCE" }), jsxRuntime.jsx("span", { className: styles$5.attachmentName, children: attachment.name })] }, index))) })), jsxRuntime.jsx(ChatMessageToolCallChips, { chips: toolCallChips, onToolCallClick: onToolCallClick }), citationFootnoteRenderModel.footnotes.length > 0 && (jsxRuntime.jsx("div", { className: styles$5.citationFootnotes, children: citationFootnoteRenderModel.footnotes.map((footnote) => (jsxRuntime.jsxs("div", { className: styles$5.citationFootnoteItem, children: [jsxRuntime.jsx("span", { className: styles$5.citationFootnoteNumber, children: footnote.number }), jsxRuntime.jsx(SourceChip, { citation: footnote.citation, onClick: onCitationClick, isCitationIdVisible: false })] }, `citation-footnote-${footnote.number}-${footnote.citation.source}`))) })), structuredSourceCitations.length > 0 && (jsxRuntime.jsx("div", { className: styles$5.sourceCitations, children: structuredSourceCitations.map((citation, index) => (jsxRuntime.jsx(SourceChip, { citation: citation, onClick: onCitationClick, isCitationIdVisible: false }, `message-source-${citation.source}-${citation.id}-${index}`))) })), transitiveCitations.length > 0 && (jsxRuntime.jsx("div", { className: styles$5.sourceCitations, children: transitiveCitations.map((citation, index) => (jsxRuntime.jsx(SourceChip, { citation: citation, suffix: `by ${citation.origin.label}`, onClick: onCitationClick }, `team-source-${citation.source}-${index}`))) })), shouldShowButtons && (jsxRuntime.jsx("div", { className: styles$5.messageButtons, children: renderableButtons.map(({ button, buttonIndex }) => (jsxRuntime.jsx("button", { type: "button", className: classNames(styles$5.messageButton, button.type === 'action' && styles$5.actionMessageButton), onClick: (event) => {
48649
50035
  event.stopPropagation();
48650
50036
  if (button.type === 'message') {
48651
50037
  const quickMessageHandler = onQuickMessageButton || onMessage;
@@ -48697,6 +50083,9 @@
48697
50083
  if (prev.message.citations !== next.message.citations) {
48698
50084
  return false;
48699
50085
  }
50086
+ if (prev.message.sources !== next.message.sources) {
50087
+ return false;
50088
+ }
48700
50089
  if (JSON.stringify(prev.message.attachments) !== JSON.stringify(next.message.attachments)) {
48701
50090
  return false;
48702
50091
  }
@@ -48811,68 +50200,201 @@
48811
50200
  return message.clientMessageId || message.id || message.content;
48812
50201
  }
48813
50202
 
50203
+ /**
50204
+ * Supported star values rendered by the rating UI.
50205
+ *
50206
+ * @private component of `<ChatRatingModal/>`
50207
+ */
50208
+ const STAR_RATINGS = [1, 2, 3, 4, 5];
50209
+ /**
50210
+ * Resolves the key used to persist message rating state.
50211
+ *
50212
+ * @private function of `<ChatRatingModal/>`
50213
+ */
50214
+ function resolveChatRatingMessageKey(message) {
50215
+ return message.id || message.content /* <-[??] */;
50216
+ }
50217
+ /**
50218
+ * Returns whether the modal should render the report-issue flow.
50219
+ *
50220
+ * @private function of `<ChatRatingModal/>`
50221
+ */
50222
+ function isReportIssueFeedbackMode(feedbackMode) {
50223
+ return feedbackMode === 'report_issue';
50224
+ }
50225
+ /**
50226
+ * Finds the previous user question from postprocessed messages when it directly precedes the selected response.
50227
+ *
50228
+ * @private function of `<ChatRatingModal/>`
50229
+ */
50230
+ function resolveUserQuestionFromPostprocessedMessages(postprocessedMessages, selectedMessage) {
50231
+ const selectedMessageIndex = postprocessedMessages.findIndex((message) => message.id === selectedMessage.id);
50232
+ if (selectedMessageIndex <= 0) {
50233
+ return null;
50234
+ }
50235
+ const previousMessage = postprocessedMessages[selectedMessageIndex - 1];
50236
+ if ((previousMessage === null || previousMessage === void 0 ? void 0 : previousMessage.sender) !== 'USER') {
50237
+ return null;
50238
+ }
50239
+ return previousMessage.content;
50240
+ }
50241
+ /**
50242
+ * Finds the nearest earlier user question in the original message list.
50243
+ *
50244
+ * @private function of `<ChatRatingModal/>`
50245
+ */
50246
+ function resolveUserQuestionFromMessages(messages, selectedMessage) {
50247
+ const selectedMessageIndex = messages.findIndex((message) => message.id === selectedMessage.id);
50248
+ for (let index = selectedMessageIndex - 1; index >= 0; index--) {
50249
+ const currentMessage = messages[index];
50250
+ if ((currentMessage === null || currentMessage === void 0 ? void 0 : currentMessage.sender) === 'USER') {
50251
+ return currentMessage.content;
50252
+ }
50253
+ }
50254
+ return null;
50255
+ }
50256
+ /**
50257
+ * Resolves the user question shown above the feedback fields.
50258
+ *
50259
+ * @private function of `<ChatRatingModal/>`
50260
+ */
50261
+ function resolveChatRatingUserQuestion(params) {
50262
+ const { postprocessedMessages, messages, selectedMessage } = params;
50263
+ return (resolveUserQuestionFromPostprocessedMessages(postprocessedMessages, selectedMessage) ||
50264
+ resolveUserQuestionFromMessages(messages, selectedMessage) ||
50265
+ '');
50266
+ }
50267
+ /**
50268
+ * Builds the localized text rendered by the modal.
50269
+ *
50270
+ * @private function of `<ChatRatingModal/>`
50271
+ */
50272
+ function createChatRatingModalCopy(params) {
50273
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
50274
+ const { feedbackMode, feedbackTranslations } = params;
50275
+ const isReportIssueMode = isReportIssueFeedbackMode(feedbackMode);
50276
+ return {
50277
+ title: isReportIssueMode
50278
+ ? (_a = feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.reportIssueModalTitle) !== null && _a !== void 0 ? _a : 'Report issue'
50279
+ : (_b = feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.rateResponseModalTitle) !== null && _b !== void 0 ? _b : 'Rate this response',
50280
+ userQuestionLabel: (_c = feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.userQuestionLabel) !== null && _c !== void 0 ? _c : 'Your question:',
50281
+ expectedAnswerLabel: isReportIssueMode
50282
+ ? (_d = feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.reportIssueExpectedAnswerLabel) !== null && _d !== void 0 ? _d : 'What should the answer include?'
50283
+ : (_e = feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.expectedAnswerLabel) !== null && _e !== void 0 ? _e : 'Expected answer:',
50284
+ expectedAnswerPlaceholder: (_f = feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.expectedAnswerPlaceholder) !== null && _f !== void 0 ? _f : 'Expected answer (optional)',
50285
+ noteLabel: isReportIssueMode
50286
+ ? (_g = feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.reportIssueDetailsLabel) !== null && _g !== void 0 ? _g : 'Issue details:'
50287
+ : (_h = feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.noteLabel) !== null && _h !== void 0 ? _h : 'Note:',
50288
+ notePlaceholder: isReportIssueMode
50289
+ ? (_j = feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.reportIssueDetailsPlaceholder) !== null && _j !== void 0 ? _j : 'Describe what went wrong (optional)'
50290
+ : (_k = feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.notePlaceholder) !== null && _k !== void 0 ? _k : 'Add a note (optional)',
50291
+ cancelLabel: (feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.cancelLabel) || 'Cancel',
50292
+ submitLabel: isReportIssueMode
50293
+ ? (_l = feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.reportIssueSubmitLabel) !== null && _l !== void 0 ? _l : 'Report issue'
50294
+ : (_m = feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.submitLabel) !== null && _m !== void 0 ? _m : 'Submit',
50295
+ };
50296
+ }
50297
+ /**
50298
+ * Resolves the active rating, preferring hover preview over persisted value.
50299
+ *
50300
+ * @private function of `<ChatRatingModal/>`
50301
+ */
50302
+ function resolveSelectedChatRating(params) {
50303
+ const { hoveredRating, messageRatings, selectedMessage } = params;
50304
+ return hoveredRating || messageRatings.get(resolveChatRatingMessageKey(selectedMessage)) || 0;
50305
+ }
50306
+ /**
50307
+ * Resolves the color for one star in the rating picker.
50308
+ *
50309
+ * @private function of `<ChatRatingModal/>`
50310
+ */
50311
+ function resolveChatRatingStarColor(star, selectedRating, mode) {
50312
+ if (star <= selectedRating) {
50313
+ return '#FFD700';
50314
+ }
50315
+ return mode === 'LIGHT' ? '#ccc' : '#555';
50316
+ }
50317
+ /**
50318
+ * Returns the placeholder used in the expected-answer field.
50319
+ *
50320
+ * @private function of `<ChatRatingModal/>`
50321
+ */
50322
+ function resolveExpectedAnswerPlaceholder(selectedMessage, modalCopy) {
50323
+ return selectedMessage.content || modalCopy.expectedAnswerPlaceholder;
50324
+ }
50325
+ /**
50326
+ * Closes the modal when the mobile backdrop itself is clicked.
50327
+ *
50328
+ * @private function of `<ChatRatingModal/>`
50329
+ */
50330
+ function createChatRatingBackdropClickHandler(params) {
50331
+ const { isMobile, onClose } = params;
50332
+ return (event) => {
50333
+ if (event.target === event.currentTarget && isMobile) {
50334
+ onClose();
50335
+ }
50336
+ };
50337
+ }
50338
+ /**
50339
+ * Renders the optional star selector for the rating flow.
50340
+ *
50341
+ * @private component of `<ChatRatingModal/>`
50342
+ */
50343
+ function ChatRatingModalStarsSection(props) {
50344
+ const { feedbackMode, hoveredRating, messageRatings, mode, selectedMessage, setHoveredRating, setMessageRatings } = props;
50345
+ if (isReportIssueFeedbackMode(feedbackMode)) {
50346
+ return null;
50347
+ }
50348
+ const selectedRating = resolveSelectedChatRating({
50349
+ hoveredRating,
50350
+ messageRatings,
50351
+ selectedMessage,
50352
+ });
50353
+ return (jsxRuntime.jsx("div", { className: styles$5.stars, children: STAR_RATINGS.map((star) => (jsxRuntime.jsx("span", { onClick: () => setMessageRatings((previousRatings) => {
50354
+ const nextRatings = new Map(previousRatings);
50355
+ nextRatings.set(resolveChatRatingMessageKey(selectedMessage), star);
50356
+ return nextRatings;
50357
+ }), onMouseEnter: () => setHoveredRating(star), onMouseLeave: () => setHoveredRating(0), className: classNames(styles$5.ratingModalStar), style: {
50358
+ color: resolveChatRatingStarColor(star, selectedRating, mode),
50359
+ }, children: "\u2B50" }, star))) }));
50360
+ }
50361
+ /**
50362
+ * Renders the footer action buttons for the rating modal.
50363
+ *
50364
+ * @private component of `<ChatRatingModal/>`
50365
+ */
50366
+ function ChatRatingModalActions(props) {
50367
+ const { cancelLabel, onClose, submitLabel, submitRating } = props;
50368
+ return (jsxRuntime.jsxs("div", { className: styles$5.ratingActions, children: [jsxRuntime.jsx("button", { onClick: onClose, children: cancelLabel }), jsxRuntime.jsx("button", { onClick: submitRating, children: submitLabel })] }));
50369
+ }
48814
50370
  /**
48815
50371
  * Modal that captures per-message rating feedback.
48816
50372
  *
48817
50373
  * @private component of `<Chat/>`
48818
50374
  */
48819
50375
  function ChatRatingModal(props) {
48820
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
48821
50376
  const { isOpen, selectedMessage, postprocessedMessages, messages, hoveredRating, messageRatings, textRating, feedbackMode, feedbackTranslations, mode, isMobile, onClose, setHoveredRating, setMessageRatings, setSelectedMessage, setTextRating, submitRating, } = props;
48822
50377
  if (!isOpen || !selectedMessage) {
48823
50378
  return null;
48824
50379
  }
48825
- const isReportIssueMode = feedbackMode === 'report_issue';
48826
- const userQuestion = (() => {
48827
- var _a, _b;
48828
- const idx = postprocessedMessages.findIndex((message) => message.id === selectedMessage.id);
48829
- if (idx > 0) {
48830
- const prev = postprocessedMessages[idx - 1];
48831
- if ((prev === null || prev === void 0 ? void 0 : prev.sender) === 'USER') {
48832
- return prev.content;
48833
- }
48834
- }
48835
- for (let i = messages.findIndex((message) => message.id === selectedMessage.id) - 1; i >= 0; i--) {
48836
- if (((_a = messages[i]) === null || _a === void 0 ? void 0 : _a.sender) === 'USER') {
48837
- return ((_b = messages[i]) === null || _b === void 0 ? void 0 : _b.content) || '';
48838
- }
48839
- }
48840
- return '';
48841
- })();
48842
- return (jsxRuntime.jsx("div", { className: styles$5.ratingModal, "data-chat-modal": "rating", "data-chat-theme": mode.toLowerCase(), onClick: (event) => {
48843
- if (event.target === event.currentTarget && isMobile) {
48844
- onClose();
48845
- }
48846
- }, children: jsxRuntime.jsxs("div", { className: styles$5.ratingModalContent, children: [jsxRuntime.jsx("h3", { children: isReportIssueMode
48847
- ? (_a = feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.reportIssueModalTitle) !== null && _a !== void 0 ? _a : 'Report issue'
48848
- : (_b = feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.rateResponseModalTitle) !== null && _b !== void 0 ? _b : 'Rate this response' }), !isReportIssueMode && (jsxRuntime.jsx("div", { className: styles$5.stars, children: [1, 2, 3, 4, 5].map((star) => (jsxRuntime.jsx("span", { onClick: () => setMessageRatings((previousRatings) => {
48849
- const nextRatings = new Map(previousRatings);
48850
- nextRatings.set(selectedMessage.id || selectedMessage.content /* <-[??] */, star);
48851
- return nextRatings;
48852
- }), onMouseEnter: () => setHoveredRating(star), onMouseLeave: () => setHoveredRating(0), className: classNames(styles$5.ratingModalStar), style: {
48853
- color: star <=
48854
- (hoveredRating ||
48855
- messageRatings.get(selectedMessage.id || selectedMessage.content /* <-[??] */) ||
48856
- 0)
48857
- ? '#FFD700'
48858
- : mode === 'LIGHT'
48859
- ? '#ccc'
48860
- : '#555',
48861
- }, children: "\u2B50" }, star))) })), (_c = feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.userQuestionLabel) !== null && _c !== void 0 ? _c : 'Your question:', jsxRuntime.jsx("textarea", { readOnly: true, value: userQuestion, className: styles$5.ratingInput }), isReportIssueMode
48862
- ? (_d = feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.reportIssueExpectedAnswerLabel) !== null && _d !== void 0 ? _d : 'What should the answer include?'
48863
- : (_e = feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.expectedAnswerLabel) !== null && _e !== void 0 ? _e : 'Expected answer:', jsxRuntime.jsx("textarea", { placeholder: selectedMessage.content ||
48864
- (feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.expectedAnswerPlaceholder) ||
48865
- 'Expected answer (optional)', defaultValue: selectedMessage.expectedAnswer || selectedMessage.content, onChange: (event) => {
50380
+ const modalCopy = createChatRatingModalCopy({
50381
+ feedbackMode,
50382
+ feedbackTranslations,
50383
+ });
50384
+ const userQuestion = resolveChatRatingUserQuestion({
50385
+ postprocessedMessages,
50386
+ messages,
50387
+ selectedMessage,
50388
+ });
50389
+ const handleBackdropClick = createChatRatingBackdropClickHandler({
50390
+ isMobile,
50391
+ onClose,
50392
+ });
50393
+ return (jsxRuntime.jsx("div", { className: styles$5.ratingModal, "data-chat-modal": "rating", "data-chat-theme": mode.toLowerCase(), onClick: handleBackdropClick, children: jsxRuntime.jsxs("div", { className: styles$5.ratingModalContent, children: [jsxRuntime.jsx("h3", { children: modalCopy.title }), jsxRuntime.jsx(ChatRatingModalStarsSection, { feedbackMode: feedbackMode, hoveredRating: hoveredRating, messageRatings: messageRatings, mode: mode, selectedMessage: selectedMessage, setHoveredRating: setHoveredRating, setMessageRatings: setMessageRatings }), modalCopy.userQuestionLabel, jsxRuntime.jsx("textarea", { readOnly: true, value: userQuestion, className: styles$5.ratingInput }), modalCopy.expectedAnswerLabel, jsxRuntime.jsx("textarea", { placeholder: resolveExpectedAnswerPlaceholder(selectedMessage, modalCopy), defaultValue: selectedMessage.expectedAnswer || selectedMessage.content, onChange: (event) => {
48866
50394
  if (selectedMessage) {
48867
50395
  setSelectedMessage({ ...selectedMessage, expectedAnswer: event.target.value });
48868
50396
  }
48869
- }, className: styles$5.ratingInput }), isReportIssueMode
48870
- ? (_f = feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.reportIssueDetailsLabel) !== null && _f !== void 0 ? _f : 'Issue details:'
48871
- : (_g = feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.noteLabel) !== null && _g !== void 0 ? _g : 'Note:', jsxRuntime.jsx("textarea", { placeholder: isReportIssueMode
48872
- ? (_h = feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.reportIssueDetailsPlaceholder) !== null && _h !== void 0 ? _h : 'Describe what went wrong (optional)'
48873
- : (_j = feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.notePlaceholder) !== null && _j !== void 0 ? _j : 'Add a note (optional)', defaultValue: textRating, onChange: (event) => setTextRating(event.target.value), className: styles$5.ratingInput }), jsxRuntime.jsxs("div", { className: styles$5.ratingActions, children: [jsxRuntime.jsx("button", { onClick: onClose, children: (feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.cancelLabel) || 'Cancel' }), jsxRuntime.jsx("button", { onClick: submitRating, children: isReportIssueMode
48874
- ? (_k = feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.reportIssueSubmitLabel) !== null && _k !== void 0 ? _k : 'Report issue'
48875
- : (_l = feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.submitLabel) !== null && _l !== void 0 ? _l : 'Submit' })] })] }) }));
50397
+ }, className: styles$5.ratingInput }), modalCopy.noteLabel, jsxRuntime.jsx("textarea", { placeholder: modalCopy.notePlaceholder, defaultValue: textRating, onChange: (event) => setTextRating(event.target.value), className: styles$5.ratingInput }), jsxRuntime.jsx(ChatRatingModalActions, { cancelLabel: modalCopy.cancelLabel, onClose: onClose, submitLabel: modalCopy.submitLabel, submitRating: submitRating })] }) }));
48876
50398
  }
48877
50399
 
48878
50400
  /**
@@ -49978,7 +51500,7 @@
49978
51500
  const { results, rawText } = extractSearchResults(resultRaw);
49979
51501
  const hasResults = results.length > 0;
49980
51502
  const hasRawText = !hasResults && !!rawText && rawText.trim().length > 0;
49981
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: styles$5.searchModalHeader, children: [jsxRuntime.jsx("span", { className: styles$5.searchModalIcon, children: "\uD83D\uDD0E" }), jsxRuntime.jsx("h3", { className: styles$5.searchModalQuery, children: args.query || args.searchText || 'Search Results' })] }), jsxRuntime.jsx("div", { className: styles$5.searchModalContent, children: hasResults ? (jsxRuntime.jsx("div", { className: styles$5.searchResultsList, children: results.map((item, index) => (jsxRuntime.jsxs("div", { className: styles$5.searchResultItem, children: [jsxRuntime.jsx("div", { className: styles$5.searchResultUrl, children: item.url && (jsxRuntime.jsx("a", { href: item.url, target: "_blank", rel: "noreferrer", children: item.url })) }), jsxRuntime.jsx("h4", { className: styles$5.searchResultTitle, children: item.url ? (jsxRuntime.jsx("a", { href: item.url, target: "_blank", rel: "noreferrer", children: item.title || 'Untitled' })) : (item.title || 'Untitled') }), jsxRuntime.jsx("p", { className: styles$5.searchResultSnippet, children: item.snippet || item.content || '' })] }, index))) })) : hasRawText ? (jsxRuntime.jsx(MarkdownContent, { className: styles$5.searchResultsRaw, content: rawText })) : toolCallState !== 'COMPLETE' ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [renderToolCallProgressPlaceholder({
51503
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: styles$5.searchModalHeader, children: [jsxRuntime.jsx("span", { className: styles$5.searchModalIcon, children: "\uD83D\uDD0E" }), jsxRuntime.jsx("h3", { className: styles$5.searchModalQuery, children: args.query || args.searchText || 'Search Results' })] }), jsxRuntime.jsx("div", { className: styles$5.searchModalContent, children: hasResults ? (jsxRuntime.jsx("div", { className: styles$5.searchResultsList, children: results.map((item, index) => (jsxRuntime.jsxs("div", { className: styles$5.searchResultItem, children: [jsxRuntime.jsx("div", { className: styles$5.searchResultUrl, children: item.url && (jsxRuntime.jsx("a", { href: item.url, target: "_blank", rel: "noreferrer", children: item.url })) }), jsxRuntime.jsx("h4", { className: styles$5.searchResultTitle, children: item.url ? (jsxRuntime.jsx("a", { href: item.url, target: "_blank", rel: "noreferrer", children: item.title || item.source || 'Untitled' })) : (item.title || item.source || 'Untitled') }), jsxRuntime.jsx("p", { className: styles$5.searchResultSnippet, children: item.snippet || item.content || item.excerpt || '' })] }, index))) })) : hasRawText ? (jsxRuntime.jsx(MarkdownContent, { className: styles$5.searchResultsRaw, content: rawText })) : toolCallState !== 'COMPLETE' ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [renderToolCallProgressPlaceholder({
49982
51504
  title: 'Search results pending',
49983
51505
  message: resolveToolCallProgressMessage(toolCall),
49984
51506
  }), jsxRuntime.jsxs("div", { className: styles$5.toolCallDetailsCard, children: [jsxRuntime.jsxs("div", { className: styles$5.toolCallDetailsCardRow, children: [jsxRuntime.jsx("strong", { children: "Query" }), jsxRuntime.jsx("span", { children: String(args.query || args.searchText || 'Search query is being prepared.') })] }), args.location && (jsxRuntime.jsxs("div", { className: styles$5.toolCallDetailsCardRow, children: [jsxRuntime.jsx("strong", { children: "Location" }), jsxRuntime.jsx("span", { children: String(args.location) })] })), args.engine && (jsxRuntime.jsxs("div", { className: styles$5.toolCallDetailsCardRow, children: [jsxRuntime.jsx("strong", { children: "Engine" }), jsxRuntime.jsx("span", { children: String(args.engine) })] }))] })] })) : (jsxRuntime.jsx("div", { className: styles$5.noResults, children: resultRaw ? 'No search results found.' : 'Search results are not available.' })) })] }));
@@ -50183,6 +51705,18 @@
50183
51705
  * @private function of ChatToolCallModal
50184
51706
  */
50185
51707
  function renderTimeoutToolCallDetails(options) {
51708
+ const viewModel = createTimeoutToolCallDetailsViewModel(options);
51709
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: styles$5.searchModalHeader, children: [jsxRuntime.jsx("span", { className: styles$5.searchModalIcon, children: "\u23F1\uFE0F" }), jsxRuntime.jsx("h3", { className: styles$5.searchModalQuery, children: viewModel.title })] }), jsxRuntime.jsxs("div", { className: styles$5.searchModalContent, children: [renderTimeoutToolCallClock(viewModel), renderTimeoutToolCallSummary(viewModel), renderTimeoutToolCallActions(viewModel)] })] }));
51710
+ }
51711
+ /**
51712
+ * Prepares the values rendered by the timeout detail view.
51713
+ *
51714
+ * @param options - Raw timeout tool call data.
51715
+ * @returns Derived timeout detail view model.
51716
+ *
51717
+ * @private function of ChatToolCallModal
51718
+ */
51719
+ function createTimeoutToolCallDetailsViewModel(options) {
50186
51720
  const { toolCallName, args, resultRaw, toolCallDate, onRequestAdvancedView, locale, chatUiTranslations } = options;
50187
51721
  const timeoutPresentation = resolveTimeoutToolCallPresentation({
50188
51722
  toolCallName,
@@ -50191,40 +51725,120 @@
50191
51725
  currentDate: new Date(),
50192
51726
  locale,
50193
51727
  });
50194
- const clockDate = (timeoutPresentation === null || timeoutPresentation === void 0 ? void 0 : timeoutPresentation.dueAtDate) || toolCallDate;
50195
- const isValidClockDate = !!clockDate && !Number.isNaN(clockDate.getTime());
50196
- const title = (timeoutPresentation === null || timeoutPresentation === void 0 ? void 0 : timeoutPresentation.action) === 'cancel'
50197
- ? timeoutPresentation.status === 'cancelled'
50198
- ? (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallTimeoutCancelledTitle) || 'Timeout cancelled'
50199
- : (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallTimeoutUpdateTitle) || 'Timeout update'
50200
- : (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallTimeoutTitle) || 'Timeout scheduled';
50201
- const primarySentence = timeoutPresentation
50202
- ? buildTimeoutToolPrimarySentence(timeoutPresentation, chatUiTranslations)
50203
- : (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallTimeoutLoadingMessage) || 'Timeout details are still loading.';
50204
- const scheduleSentence = timeoutPresentation
50205
- ? buildTimeoutToolScheduleSentence(timeoutPresentation, chatUiTranslations)
50206
- : null;
50207
- const cancelCommand = (timeoutPresentation === null || timeoutPresentation === void 0 ? void 0 : timeoutPresentation.timeoutId) && timeoutPresentation.action !== 'cancel'
50208
- ? createCancelTimeoutQuickActionCommand(timeoutPresentation.timeoutId)
50209
- : null;
50210
- const snoozeCommand = createSnoozeTimeoutQuickActionCommand((timeoutPresentation === null || timeoutPresentation === void 0 ? void 0 : timeoutPresentation.message) || undefined);
50211
- const timezoneLine = (timeoutPresentation === null || timeoutPresentation === void 0 ? void 0 : timeoutPresentation.localTimezone)
50212
- ? `${(chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallTimeoutTimezoneLabel) || 'Timezone:'} ${timeoutPresentation.localTimezone}`
50213
- : null;
50214
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: styles$5.searchModalHeader, children: [jsxRuntime.jsx("span", { className: styles$5.searchModalIcon, children: "\u23F1\uFE0F" }), jsxRuntime.jsx("h3", { className: styles$5.searchModalQuery, children: title })] }), jsxRuntime.jsxs("div", { className: styles$5.searchModalContent, children: [isValidClockDate && clockDate ? (renderToolCallClockPanel({
50215
- date: clockDate,
50216
- relativeLabel: (timeoutPresentation === null || timeoutPresentation === void 0 ? void 0 : timeoutPresentation.relativeDueLabel) || null,
50217
- timezoneLabel: timezoneLine,
50218
- locale,
50219
- })) : (jsxRuntime.jsx("p", { className: styles$5.toolCallEmpty, children: (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallTimeoutUnavailableMessage) || 'Scheduled time is unavailable.' })), jsxRuntime.jsxs("div", { className: styles$5.timeoutToolSummary, children: [jsxRuntime.jsx("p", { className: styles$5.timeoutToolPrimarySentence, children: primarySentence }), scheduleSentence && jsxRuntime.jsx("p", { className: styles$5.timeoutToolSecondarySentence, children: scheduleSentence }), (timeoutPresentation === null || timeoutPresentation === void 0 ? void 0 : timeoutPresentation.localDueDateLabel) && (jsxRuntime.jsxs("p", { className: styles$5.timeoutToolSecondarySentence, children: [(chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallTimeoutDateLabel) || 'Date:', ' ', timeoutPresentation.localDueDateLabel] })), (timeoutPresentation === null || timeoutPresentation === void 0 ? void 0 : timeoutPresentation.message) && (jsxRuntime.jsxs("p", { className: styles$5.timeoutToolSecondarySentence, children: [(chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallTimeoutMessageLabel) || 'Message:', ' ', timeoutPresentation.message] }))] }), jsxRuntime.jsxs("div", { className: styles$5.timeoutToolActionRow, role: "group", "aria-label": (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallTimeoutActionGroupLabel) || 'Timeout quick actions', children: [jsxRuntime.jsx("button", { type: "button", className: styles$5.timeoutToolActionButton, onClick: () => {
50220
- if (!cancelCommand) {
50221
- return;
50222
- }
50223
- copyTimeoutQuickActionCommand(cancelCommand);
50224
- }, disabled: !cancelCommand, "aria-label": (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallTimeoutCancelAriaLabel) || 'Cancel timeout', children: (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallTimeoutCancelButton) || 'Cancel' }), jsxRuntime.jsx("button", { type: "button", className: styles$5.timeoutToolActionButton, onClick: () => {
50225
- copyTimeoutQuickActionCommand(snoozeCommand);
50226
- }, "aria-label": (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallTimeoutSnoozeAriaLabel) || 'Snooze timeout', children: (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallTimeoutSnoozeButton) || 'Snooze' }), onRequestAdvancedView && (jsxRuntime.jsx("button", { type: "button", className: styles$5.timeoutToolActionButton, onClick: onRequestAdvancedView, "aria-label": (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallTimeoutViewAdvancedAriaLabel) ||
50227
- 'View advanced timeout details', children: (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallTimeoutViewAdvancedButton) || 'View advanced' }))] })] })] }));
51728
+ const labels = resolveTimeoutToolCallDetailLabels(chatUiTranslations);
51729
+ return {
51730
+ labels,
51731
+ locale,
51732
+ onRequestAdvancedView,
51733
+ title: resolveTimeoutToolCallTitle(timeoutPresentation, labels),
51734
+ clockDate: (timeoutPresentation === null || timeoutPresentation === void 0 ? void 0 : timeoutPresentation.dueAtDate) || toolCallDate,
51735
+ primarySentence: timeoutPresentation
51736
+ ? buildTimeoutToolPrimarySentence(timeoutPresentation, chatUiTranslations)
51737
+ : labels.loadingMessage,
51738
+ scheduleSentence: timeoutPresentation
51739
+ ? buildTimeoutToolScheduleSentence(timeoutPresentation, chatUiTranslations)
51740
+ : null,
51741
+ relativeDueLabel: (timeoutPresentation === null || timeoutPresentation === void 0 ? void 0 : timeoutPresentation.relativeDueLabel) || null,
51742
+ timezoneLine: (timeoutPresentation === null || timeoutPresentation === void 0 ? void 0 : timeoutPresentation.localTimezone)
51743
+ ? `${labels.timezoneLabel} ${timeoutPresentation.localTimezone}`
51744
+ : null,
51745
+ localDueDateLabel: (timeoutPresentation === null || timeoutPresentation === void 0 ? void 0 : timeoutPresentation.localDueDateLabel) || null,
51746
+ message: (timeoutPresentation === null || timeoutPresentation === void 0 ? void 0 : timeoutPresentation.message) || null,
51747
+ cancelCommand: (timeoutPresentation === null || timeoutPresentation === void 0 ? void 0 : timeoutPresentation.timeoutId) && timeoutPresentation.action !== 'cancel'
51748
+ ? createCancelTimeoutQuickActionCommand(timeoutPresentation.timeoutId)
51749
+ : null,
51750
+ snoozeCommand: createSnoozeTimeoutQuickActionCommand((timeoutPresentation === null || timeoutPresentation === void 0 ? void 0 : timeoutPresentation.message) || undefined),
51751
+ };
51752
+ }
51753
+ /**
51754
+ * Resolves the timeout detail title for the current presentation state.
51755
+ *
51756
+ * @param timeoutPresentation - Friendly timeout presentation data.
51757
+ * @param labels - Localized labels with fallbacks.
51758
+ * @returns One timeout modal title.
51759
+ *
51760
+ * @private function of ChatToolCallModal
51761
+ */
51762
+ function resolveTimeoutToolCallTitle(timeoutPresentation, labels) {
51763
+ if ((timeoutPresentation === null || timeoutPresentation === void 0 ? void 0 : timeoutPresentation.action) !== 'cancel') {
51764
+ return labels.titleScheduled;
51765
+ }
51766
+ return timeoutPresentation.status === 'cancelled' ? labels.titleCancelled : labels.titleUpdated;
51767
+ }
51768
+ /**
51769
+ * Resolves timeout-detail translations with stable fallback strings.
51770
+ *
51771
+ * @param chatUiTranslations - Optional translation overrides.
51772
+ * @returns Complete timeout detail labels.
51773
+ *
51774
+ * @private function of ChatToolCallModal
51775
+ */
51776
+ function resolveTimeoutToolCallDetailLabels(chatUiTranslations) {
51777
+ return {
51778
+ titleScheduled: (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallTimeoutTitle) || 'Timeout scheduled',
51779
+ titleCancelled: (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallTimeoutCancelledTitle) || 'Timeout cancelled',
51780
+ titleUpdated: (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallTimeoutUpdateTitle) || 'Timeout update',
51781
+ loadingMessage: (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallTimeoutLoadingMessage) || 'Timeout details are still loading.',
51782
+ unavailableMessage: (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallTimeoutUnavailableMessage) || 'Scheduled time is unavailable.',
51783
+ dateLabel: (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallTimeoutDateLabel) || 'Date:',
51784
+ messageLabel: (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallTimeoutMessageLabel) || 'Message:',
51785
+ timezoneLabel: (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallTimeoutTimezoneLabel) || 'Timezone:',
51786
+ actionGroupLabel: (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallTimeoutActionGroupLabel) || 'Timeout quick actions',
51787
+ cancelAriaLabel: (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallTimeoutCancelAriaLabel) || 'Cancel timeout',
51788
+ cancelButton: (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallTimeoutCancelButton) || 'Cancel',
51789
+ snoozeAriaLabel: (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallTimeoutSnoozeAriaLabel) || 'Snooze timeout',
51790
+ snoozeButton: (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallTimeoutSnoozeButton) || 'Snooze',
51791
+ viewAdvancedAriaLabel: (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallTimeoutViewAdvancedAriaLabel) || 'View advanced timeout details',
51792
+ viewAdvancedButton: (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallTimeoutViewAdvancedButton) || 'View advanced',
51793
+ };
51794
+ }
51795
+ /**
51796
+ * Renders the timeout clock panel or its unavailable fallback.
51797
+ *
51798
+ * @param viewModel - Prepared timeout detail state.
51799
+ * @returns Clock section element.
51800
+ *
51801
+ * @private function of ChatToolCallModal
51802
+ */
51803
+ function renderTimeoutToolCallClock(viewModel) {
51804
+ if (viewModel.clockDate && !Number.isNaN(viewModel.clockDate.getTime())) {
51805
+ return renderToolCallClockPanel({
51806
+ date: viewModel.clockDate,
51807
+ relativeLabel: viewModel.relativeDueLabel,
51808
+ timezoneLabel: viewModel.timezoneLine,
51809
+ locale: viewModel.locale,
51810
+ });
51811
+ }
51812
+ return jsxRuntime.jsx("p", { className: styles$5.toolCallEmpty, children: viewModel.labels.unavailableMessage });
51813
+ }
51814
+ /**
51815
+ * Renders the timeout summary sentences and optional metadata lines.
51816
+ *
51817
+ * @param viewModel - Prepared timeout detail state.
51818
+ * @returns Summary section element.
51819
+ *
51820
+ * @private function of ChatToolCallModal
51821
+ */
51822
+ function renderTimeoutToolCallSummary(viewModel) {
51823
+ return (jsxRuntime.jsxs("div", { className: styles$5.timeoutToolSummary, children: [jsxRuntime.jsx("p", { className: styles$5.timeoutToolPrimarySentence, children: viewModel.primarySentence }), viewModel.scheduleSentence && (jsxRuntime.jsx("p", { className: styles$5.timeoutToolSecondarySentence, children: viewModel.scheduleSentence })), viewModel.localDueDateLabel && (jsxRuntime.jsxs("p", { className: styles$5.timeoutToolSecondarySentence, children: [viewModel.labels.dateLabel, " ", viewModel.localDueDateLabel] })), viewModel.message && (jsxRuntime.jsxs("p", { className: styles$5.timeoutToolSecondarySentence, children: [viewModel.labels.messageLabel, " ", viewModel.message] }))] }));
51824
+ }
51825
+ /**
51826
+ * Renders timeout quick-action buttons.
51827
+ *
51828
+ * @param viewModel - Prepared timeout detail state.
51829
+ * @returns Quick actions element.
51830
+ *
51831
+ * @private function of ChatToolCallModal
51832
+ */
51833
+ function renderTimeoutToolCallActions(viewModel) {
51834
+ return (jsxRuntime.jsxs("div", { className: styles$5.timeoutToolActionRow, role: "group", "aria-label": viewModel.labels.actionGroupLabel, children: [jsxRuntime.jsx("button", { type: "button", className: styles$5.timeoutToolActionButton, onClick: () => {
51835
+ if (!viewModel.cancelCommand) {
51836
+ return;
51837
+ }
51838
+ copyTimeoutQuickActionCommand(viewModel.cancelCommand);
51839
+ }, disabled: !viewModel.cancelCommand, "aria-label": viewModel.labels.cancelAriaLabel, children: viewModel.labels.cancelButton }), jsxRuntime.jsx("button", { type: "button", className: styles$5.timeoutToolActionButton, onClick: () => {
51840
+ copyTimeoutQuickActionCommand(viewModel.snoozeCommand);
51841
+ }, "aria-label": viewModel.labels.snoozeAriaLabel, children: viewModel.labels.snoozeButton }), viewModel.onRequestAdvancedView && (jsxRuntime.jsx("button", { type: "button", className: styles$5.timeoutToolActionButton, onClick: viewModel.onRequestAdvancedView, "aria-label": viewModel.labels.viewAdvancedAriaLabel, children: viewModel.labels.viewAdvancedButton }))] }));
50228
51842
  }
50229
51843
  /**
50230
51844
  * Copies one quick action command to clipboard when supported.
@@ -50466,6 +52080,7 @@
50466
52080
  function isSearchToolCallName(toolName) {
50467
52081
  return (toolName === 'web_search' ||
50468
52082
  toolName === 'deep_search' ||
52083
+ toolName === 'knowledge_search' ||
50469
52084
  toolName === 'useSearchEngine' ||
50470
52085
  toolName === 'search');
50471
52086
  }
@@ -50812,6 +52427,279 @@
50812
52427
  */
50813
52428
  const PlayIcon = ({ size = 16 }) => (jsxRuntime.jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "currentColor", role: "img", "aria-label": "Play", children: jsxRuntime.jsx("path", { d: "M8 5v14l11-7L8 5z" }) }));
50814
52429
 
52430
+ /**
52431
+ * Resolves one delay value, including random ranges.
52432
+ *
52433
+ * @private function of `MockedChat`
52434
+ */
52435
+ function getDelay(val, fallback) {
52436
+ if (Array.isArray(val) && val.length === 2) {
52437
+ const [min, max] = val;
52438
+ return Math.floor(Math.random() * (max - min + 1)) + min;
52439
+ }
52440
+ if (typeof val === 'number') {
52441
+ return val;
52442
+ }
52443
+ return fallback;
52444
+ }
52445
+ /**
52446
+ * Normalizes message offsets into a monotonic deterministic timeline.
52447
+ *
52448
+ * @private function of `MockedChat`
52449
+ */
52450
+ function normalizeMessageOffsets(messageOffsetsMs, originalMessages) {
52451
+ if (!messageOffsetsMs || messageOffsetsMs.length === 0) {
52452
+ return null;
52453
+ }
52454
+ let previousOffset = 0;
52455
+ return originalMessages.map((_message, messageIndex) => {
52456
+ const rawOffset = messageOffsetsMs[messageIndex];
52457
+ const nextOffset = Number.isFinite(rawOffset) ? Math.max(0, Math.floor(rawOffset || 0)) : previousOffset;
52458
+ previousOffset = Math.max(previousOffset, nextOffset);
52459
+ return previousOffset;
52460
+ });
52461
+ }
52462
+ /**
52463
+ * Resolves the deterministic wait before one message when fixed offsets are supplied.
52464
+ *
52465
+ * @private function of `MockedChat`
52466
+ */
52467
+ function resolveOffsetDelayBeforeMessage(normalizedMessageOffsetsMs, messageIndex) {
52468
+ if (!normalizedMessageOffsetsMs) {
52469
+ return 0;
52470
+ }
52471
+ const currentOffset = normalizedMessageOffsetsMs[messageIndex] || 0;
52472
+ const previousOffset = messageIndex > 0 ? normalizedMessageOffsetsMs[messageIndex - 1] || 0 : 0;
52473
+ return Math.max(0, currentOffset - previousOffset);
52474
+ }
52475
+ /**
52476
+ * Creates the message shape rendered while the mocked transcript is being simulated.
52477
+ *
52478
+ * @private function of `MockedChat`
52479
+ */
52480
+ function createDisplayedMockedChatMessage(originalMessage, content, isComplete) {
52481
+ return {
52482
+ id: originalMessage.id,
52483
+ createdAt: originalMessage.createdAt,
52484
+ sender: originalMessage.sender,
52485
+ content,
52486
+ isComplete,
52487
+ expectedAnswer: originalMessage.expectedAnswer,
52488
+ isVoiceCall: originalMessage.isVoiceCall,
52489
+ };
52490
+ }
52491
+ /**
52492
+ * Appends one new rendered message to the mocked transcript.
52493
+ *
52494
+ * @private function of `MockedChat`
52495
+ */
52496
+ function appendDisplayedMessage(setDisplayedMessages, nextMessage) {
52497
+ setDisplayedMessages((previousMessages) => [...previousMessages, nextMessage]);
52498
+ }
52499
+ /**
52500
+ * Replaces the most recently rendered message in the mocked transcript.
52501
+ *
52502
+ * @private function of `MockedChat`
52503
+ */
52504
+ function replaceLastDisplayedMessage(setDisplayedMessages, nextMessage) {
52505
+ setDisplayedMessages((previousMessages) => {
52506
+ const nextMessages = [...previousMessages];
52507
+ nextMessages[nextMessages.length - 1] = nextMessage;
52508
+ return nextMessages;
52509
+ });
52510
+ }
52511
+ /**
52512
+ * Resets all simulation-owned UI state before one playback run starts.
52513
+ *
52514
+ * @private function of `MockedChat`
52515
+ */
52516
+ function resetSimulationState(params) {
52517
+ const { setDisplayedMessages, setLocalAppendedMessages, setIsSimulationComplete } = params;
52518
+ setDisplayedMessages([]);
52519
+ setLocalAppendedMessages([]);
52520
+ setIsSimulationComplete(false);
52521
+ }
52522
+ /**
52523
+ * Finalizes the playback run and notifies the caller.
52524
+ *
52525
+ * @private function of `MockedChat`
52526
+ */
52527
+ function completeSimulation(params) {
52528
+ var _a;
52529
+ const { setIsSimulationComplete, onSimulationCompleteRef } = params;
52530
+ setIsSimulationComplete(true);
52531
+ (_a = onSimulationCompleteRef.current) === null || _a === void 0 ? void 0 : _a.call(onSimulationCompleteRef);
52532
+ }
52533
+ /**
52534
+ * Waits for one delay and reports whether the current playback was cancelled.
52535
+ *
52536
+ * @private function of `MockedChat`
52537
+ */
52538
+ async function waitForDelay(delayMs, isCancelledRef) {
52539
+ if (delayMs > 0) {
52540
+ await waitasecond.forTime(delayMs);
52541
+ }
52542
+ return isCancelledRef();
52543
+ }
52544
+ /**
52545
+ * Waits for a paused simulation to resume when a pause has been requested.
52546
+ *
52547
+ * @private function of `MockedChat`
52548
+ */
52549
+ async function waitForPauseIfRequested(params) {
52550
+ const { pauseRequestedRef, waitIfPaused, isCancelledRef } = params;
52551
+ if (!pauseRequestedRef.current) {
52552
+ return false;
52553
+ }
52554
+ await waitIfPaused(isCancelledRef);
52555
+ return isCancelledRef();
52556
+ }
52557
+ /**
52558
+ * Resolves the first wait before the simulated transcript starts rendering.
52559
+ *
52560
+ * @private function of `MockedChat`
52561
+ */
52562
+ function resolveInitialSimulationDelay(params) {
52563
+ const { delays, normalizedMessageOffsetsMs, firstMessageIndex } = params;
52564
+ if (normalizedMessageOffsetsMs) {
52565
+ return resolveOffsetDelayBeforeMessage(normalizedMessageOffsetsMs, firstMessageIndex);
52566
+ }
52567
+ return getDelay(delays.beforeFirstMessage, 1000);
52568
+ }
52569
+ /**
52570
+ * Returns true when the next inter-message wait should use a longer pause.
52571
+ *
52572
+ * @private function of `MockedChat`
52573
+ */
52574
+ function shouldUseLongPause(params) {
52575
+ const { delays, originalMessages, messageIndex } = params;
52576
+ return (!!delays.longPauseChance &&
52577
+ Math.random() < delays.longPauseChance &&
52578
+ messageIndex > 0 &&
52579
+ originalMessages[messageIndex].sender !== originalMessages[messageIndex - 1].sender);
52580
+ }
52581
+ /**
52582
+ * Resolves the wait between two rendered messages.
52583
+ *
52584
+ * @private function of `MockedChat`
52585
+ */
52586
+ function resolveInterMessageDelay(params) {
52587
+ const { delays, originalMessages, normalizedMessageOffsetsMs, messageIndex } = params;
52588
+ if (normalizedMessageOffsetsMs) {
52589
+ return resolveOffsetDelayBeforeMessage(normalizedMessageOffsetsMs, messageIndex);
52590
+ }
52591
+ if (shouldUseLongPause({
52592
+ delays,
52593
+ originalMessages,
52594
+ messageIndex,
52595
+ })) {
52596
+ return getDelay(delays.longPauseDuration, 2000);
52597
+ }
52598
+ return getDelay(delays.thinkingBetweenMessages, 2000);
52599
+ }
52600
+ /**
52601
+ * Types out one message word by word and marks it complete when finished.
52602
+ *
52603
+ * @private function of `MockedChat`
52604
+ */
52605
+ async function typeMockedChatMessage(params) {
52606
+ const { currentMessage, delays, normalizedMessageOffsetsMs, setDisplayedMessages, isCancelledRef } = params;
52607
+ const incompleteMessage = createDisplayedMockedChatMessage(currentMessage, '', false);
52608
+ appendDisplayedMessage(setDisplayedMessages, incompleteMessage);
52609
+ const words = currentMessage.content.split(' ');
52610
+ let currentContent = '';
52611
+ for (let wordIndex = 0; wordIndex < words.length; wordIndex++) {
52612
+ if (isCancelledRef()) {
52613
+ return true;
52614
+ }
52615
+ const word = words[wordIndex];
52616
+ currentContent += (wordIndex > 0 ? ' ' : '') + word;
52617
+ replaceLastDisplayedMessage(setDisplayedMessages, createDisplayedMockedChatMessage(currentMessage, currentContent, false));
52618
+ const wordDelayMs = getDelay(delays.waitAfterWord, 100) + getDelay(delays.extraWordDelay, 50);
52619
+ if (await waitForDelay(wordDelayMs, isCancelledRef)) {
52620
+ return true;
52621
+ }
52622
+ }
52623
+ replaceLastDisplayedMessage(setDisplayedMessages, createDisplayedMockedChatMessage(currentMessage, currentMessage.content, true));
52624
+ if (!normalizedMessageOffsetsMs && (await waitForDelay(200, isCancelledRef))) {
52625
+ return true;
52626
+ }
52627
+ return false;
52628
+ }
52629
+ /**
52630
+ * Runs one full mocked-chat playback cycle.
52631
+ *
52632
+ * @private function of `MockedChat`
52633
+ */
52634
+ async function simulateMockedChatPlayback(props) {
52635
+ const { delays, originalMessages, normalizedMessageOffsetsMs, pauseRequestedRef, waitIfPaused, setDisplayedMessages, setLocalAppendedMessages, setIsSimulationComplete, onSimulationCompleteRef, isCancelledRef, } = props;
52636
+ resetSimulationState({
52637
+ setDisplayedMessages,
52638
+ setLocalAppendedMessages,
52639
+ setIsSimulationComplete,
52640
+ });
52641
+ if (originalMessages.length === 0) {
52642
+ completeSimulation({
52643
+ setIsSimulationComplete,
52644
+ onSimulationCompleteRef,
52645
+ });
52646
+ return;
52647
+ }
52648
+ const showIntermediateMessages = delays.showIntermediateMessages || 0;
52649
+ if (showIntermediateMessages > 0) {
52650
+ setDisplayedMessages(originalMessages.slice(0, showIntermediateMessages));
52651
+ }
52652
+ if (await waitForDelay(resolveInitialSimulationDelay({
52653
+ delays,
52654
+ normalizedMessageOffsetsMs,
52655
+ firstMessageIndex: showIntermediateMessages,
52656
+ }), isCancelledRef)) {
52657
+ return;
52658
+ }
52659
+ for (let messageIndex = showIntermediateMessages; messageIndex < originalMessages.length; messageIndex++) {
52660
+ if (await waitForPauseIfRequested({
52661
+ pauseRequestedRef,
52662
+ waitIfPaused,
52663
+ isCancelledRef,
52664
+ })) {
52665
+ return;
52666
+ }
52667
+ const currentMessage = originalMessages[messageIndex];
52668
+ if (!currentMessage) {
52669
+ continue;
52670
+ }
52671
+ if (messageIndex > showIntermediateMessages) {
52672
+ if (await waitForDelay(resolveInterMessageDelay({
52673
+ delays,
52674
+ originalMessages,
52675
+ normalizedMessageOffsetsMs,
52676
+ messageIndex,
52677
+ }), isCancelledRef)) {
52678
+ return;
52679
+ }
52680
+ if (await waitForPauseIfRequested({
52681
+ pauseRequestedRef,
52682
+ waitIfPaused,
52683
+ isCancelledRef,
52684
+ })) {
52685
+ return;
52686
+ }
52687
+ }
52688
+ if (await typeMockedChatMessage({
52689
+ currentMessage,
52690
+ delays,
52691
+ normalizedMessageOffsetsMs,
52692
+ setDisplayedMessages,
52693
+ isCancelledRef,
52694
+ })) {
52695
+ return;
52696
+ }
52697
+ }
52698
+ completeSimulation({
52699
+ setIsSimulationComplete,
52700
+ onSimulationCompleteRef,
52701
+ });
52702
+ }
50815
52703
  /**
50816
52704
  * MockedChat component that shows the same chat as Chat but emulates ongoing discussion
50817
52705
  * with realistic typing delays and thinking pauses.
@@ -50820,16 +52708,6 @@
50820
52708
  */
50821
52709
  function MockedChat(props) {
50822
52710
  const { delayConfig, messages: originalMessages, isResettable = true, isPausable = true, messageOffsetsMs, appendMessagesLocallyOnSend = false, onSimulationComplete, isSaveButtonEnabled = true, ...chatProps } = props;
50823
- // Helper to get random delay from config
50824
- function getDelay(val, fallback) {
50825
- if (Array.isArray(val) && val.length === 2) {
50826
- const [min, max] = val;
50827
- return Math.floor(Math.random() * (max - min + 1)) + min;
50828
- }
50829
- if (typeof val === 'number')
50830
- return val;
50831
- return fallback;
50832
- }
50833
52711
  const delays = {
50834
52712
  ...MOCKED_CHAT_DELAY_CONFIGS.NORMAL_FLOW,
50835
52713
  ...delayConfig,
@@ -50837,18 +52715,7 @@
50837
52715
  const [displayedMessages, setDisplayedMessages] = react.useState([]);
50838
52716
  const [localAppendedMessages, setLocalAppendedMessages] = react.useState([]);
50839
52717
  const [isSimulationComplete, setIsSimulationComplete] = react.useState(false);
50840
- const normalizedMessageOffsetsMs = react.useMemo(() => {
50841
- if (!messageOffsetsMs || messageOffsetsMs.length === 0) {
50842
- return null;
50843
- }
50844
- let previousOffset = 0;
50845
- return originalMessages.map((_message, messageIndex) => {
50846
- const rawOffset = messageOffsetsMs[messageIndex];
50847
- const nextOffset = Number.isFinite(rawOffset) ? Math.max(0, Math.floor(rawOffset || 0)) : previousOffset;
50848
- previousOffset = Math.max(previousOffset, nextOffset);
50849
- return previousOffset;
50850
- });
50851
- }, [messageOffsetsMs, originalMessages]);
52718
+ const normalizedMessageOffsetsMs = react.useMemo(() => normalizeMessageOffsets(messageOffsetsMs, originalMessages), [messageOffsetsMs, originalMessages]);
50852
52719
  // Playback state machine
50853
52720
  // RUNNING -> (user clicks Pause) -> PAUSING (finish current message) -> PAUSED
50854
52721
  // PAUSED -> (user clicks Resume) -> RUNNING
@@ -50869,193 +52736,48 @@
50869
52736
  setIsSimulationComplete(false);
50870
52737
  setResetNonce((nonce) => nonce + 1);
50871
52738
  };
50872
- }, [resetNonce, isResettable]);
52739
+ }, [isResettable]);
50873
52740
  // Helper: Wait while paused (entered only between messages, never mid-typing)
50874
52741
  const waitIfPaused = async (isCancelledRef) => {
50875
- if (!pauseRequestedRef.current)
52742
+ if (!pauseRequestedRef.current) {
50876
52743
  return;
52744
+ }
50877
52745
  setPlaybackState('PAUSED');
50878
52746
  // Busy wait with small sleeps until resume
50879
52747
  while (pauseRequestedRef.current) {
50880
- if (isCancelledRef())
52748
+ if (isCancelledRef()) {
50881
52749
  return;
52750
+ }
50882
52751
  await waitasecond.forTime(100);
50883
52752
  }
50884
- // Resumed
50885
52753
  setPlaybackState('RUNNING');
50886
52754
  };
50887
52755
  const requestPause = () => {
50888
52756
  if (playbackState === 'RUNNING') {
50889
52757
  pauseRequestedRef.current = true;
50890
- // Will flip to PAUSING when current message completes
50891
52758
  setPlaybackState('PAUSING');
50892
52759
  }
50893
52760
  };
50894
52761
  const resume = () => {
50895
52762
  pauseRequestedRef.current = false;
50896
52763
  if (playbackState !== 'RUNNING') {
50897
- // Actual state will become RUNNING after loop exits waitIfPaused
50898
52764
  setPlaybackState('RUNNING');
50899
52765
  }
50900
52766
  };
50901
- /**
50902
- * Resolves deterministic waiting time before one message when fixed offsets are supplied.
50903
- */
50904
- const resolveOffsetDelayBeforeMessage = react.useCallback((messageIndex) => {
50905
- if (!normalizedMessageOffsetsMs) {
50906
- return 0;
50907
- }
50908
- const currentOffset = normalizedMessageOffsetsMs[messageIndex] || 0;
50909
- const previousOffset = messageIndex > 0 ? normalizedMessageOffsetsMs[messageIndex - 1] || 0 : 0;
50910
- return Math.max(0, currentOffset - previousOffset);
50911
- }, [normalizedMessageOffsetsMs]);
50912
52767
  react.useEffect(() => {
50913
52768
  let isCancelled = false;
50914
- const simulateChat = async () => {
50915
- var _a, _b;
50916
- // Reset state
50917
- setDisplayedMessages([]);
50918
- setLocalAppendedMessages([]);
50919
- setIsSimulationComplete(false);
50920
- if (originalMessages.length === 0) {
50921
- setIsSimulationComplete(true);
50922
- (_a = onSimulationCompleteRef.current) === null || _a === void 0 ? void 0 : _a.call(onSimulationCompleteRef);
50923
- return;
50924
- }
50925
- // Show intermediate messages immediately
50926
- const showIntermediateMessages = delays.showIntermediateMessages || 0;
50927
- if (showIntermediateMessages > 0) {
50928
- setDisplayedMessages(originalMessages.slice(0, showIntermediateMessages));
50929
- }
50930
- // Wait before first rendered message.
50931
- const firstMessageIndex = showIntermediateMessages;
50932
- const initialDelay = normalizedMessageOffsetsMs
50933
- ? resolveOffsetDelayBeforeMessage(firstMessageIndex)
50934
- : getDelay(delays.beforeFirstMessage, 1000);
50935
- if (initialDelay > 0) {
50936
- await waitasecond.forTime(initialDelay);
50937
- }
50938
- if (isCancelled)
50939
- return;
50940
- for (let i = showIntermediateMessages; i < originalMessages.length; i++) {
50941
- // If a pause was requested earlier, we only pause between messages
50942
- if (pauseRequestedRef.current) {
50943
- await waitIfPaused(() => isCancelled);
50944
- if (isCancelled)
50945
- return;
50946
- }
50947
- if (isCancelled)
50948
- return;
50949
- const currentMessage = originalMessages[i];
50950
- if (!currentMessage)
50951
- continue;
50952
- // Add delay between rendered messages.
50953
- if (i > showIntermediateMessages) {
50954
- if (normalizedMessageOffsetsMs) {
50955
- const deterministicDelay = resolveOffsetDelayBeforeMessage(i);
50956
- if (deterministicDelay > 0) {
50957
- await waitasecond.forTime(deterministicDelay);
50958
- if (isCancelled)
50959
- return;
50960
- }
50961
- }
50962
- else {
50963
- // Sometimes do a longer pause (agent switch or random)
50964
- let didLongPause = false;
50965
- if (delays.longPauseChance &&
50966
- Math.random() < delays.longPauseChance &&
50967
- i > 0 &&
50968
- originalMessages[i].sender !== originalMessages[i - 1].sender) {
50969
- await waitasecond.forTime(getDelay(delays.longPauseDuration, 2000));
50970
- didLongPause = true;
50971
- if (isCancelled)
50972
- return;
50973
- }
50974
- // Otherwise normal thinking delay
50975
- if (!didLongPause) {
50976
- await waitasecond.forTime(getDelay(delays.thinkingBetweenMessages, 2000));
50977
- if (isCancelled)
50978
- return;
50979
- }
50980
- }
50981
- // Pause check (still between messages)
50982
- if (pauseRequestedRef.current) {
50983
- await waitIfPaused(() => isCancelled);
50984
- if (isCancelled)
50985
- return;
50986
- }
50987
- }
50988
- // Show incomplete message first (for typing effect)
50989
- const incompleteMessage = {
50990
- // channel: 'PROMPTBOOK_CHAT',
50991
- id: currentMessage.id,
50992
- createdAt: currentMessage.createdAt,
50993
- sender: currentMessage.sender,
50994
- content: '',
50995
- isComplete: false,
50996
- expectedAnswer: currentMessage.expectedAnswer,
50997
- isVoiceCall: currentMessage.isVoiceCall,
50998
- };
50999
- setDisplayedMessages((prev) => [...prev, incompleteMessage]);
51000
- // Split message content into words
51001
- const words = currentMessage.content.split(' ');
51002
- let currentContent = '';
51003
- // Type each word with delay (randomized)
51004
- for (let wordIndex = 0; wordIndex < words.length; wordIndex++) {
51005
- if (isCancelled)
51006
- return;
51007
- const word = words[wordIndex];
51008
- currentContent += (wordIndex > 0 ? ' ' : '') + word;
51009
- // Update the message with current content
51010
- const updatingMessage = {
51011
- // channel: 'PROMPTBOOK_CHAT',
51012
- id: currentMessage.id,
51013
- createdAt: currentMessage.createdAt,
51014
- sender: currentMessage.sender,
51015
- content: currentContent,
51016
- isComplete: false,
51017
- expectedAnswer: currentMessage.expectedAnswer,
51018
- isVoiceCall: currentMessage.isVoiceCall,
51019
- };
51020
- setDisplayedMessages((prev) => {
51021
- const newMessages = [...prev];
51022
- newMessages[newMessages.length - 1] = updatingMessage;
51023
- return newMessages;
51024
- });
51025
- // Wait after word with extra delay (randomized)
51026
- await waitasecond.forTime(getDelay(delays.waitAfterWord, 100) + getDelay(delays.extraWordDelay, 50));
51027
- if (isCancelled)
51028
- return;
51029
- }
51030
- // Mark message as complete
51031
- const completeMessage = {
51032
- // channel: 'PROMPTBOOK_CHAT',
51033
- id: currentMessage.id,
51034
- createdAt: currentMessage.createdAt,
51035
- sender: currentMessage.sender,
51036
- content: currentMessage.content,
51037
- isComplete: true,
51038
- expectedAnswer: currentMessage.expectedAnswer,
51039
- isVoiceCall: currentMessage.isVoiceCall,
51040
- };
51041
- setDisplayedMessages((prev) => {
51042
- const newMessages = [...prev];
51043
- newMessages[newMessages.length - 1] = completeMessage;
51044
- return newMessages;
51045
- });
51046
- if (!normalizedMessageOffsetsMs) {
51047
- // Small pause after completing the message
51048
- await waitasecond.forTime(200);
51049
- if (isCancelled)
51050
- return;
51051
- }
51052
- // Transition PAUSING -> PAUSED (after finishing current message)
51053
- if (pauseRequestedRef.current && playbackState === 'PAUSING') ;
51054
- }
51055
- setIsSimulationComplete(true);
51056
- (_b = onSimulationCompleteRef.current) === null || _b === void 0 ? void 0 : _b.call(onSimulationCompleteRef);
51057
- };
51058
- simulateChat().catch((error) => {
52769
+ simulateMockedChatPlayback({
52770
+ delays,
52771
+ originalMessages,
52772
+ normalizedMessageOffsetsMs,
52773
+ pauseRequestedRef,
52774
+ waitIfPaused,
52775
+ setDisplayedMessages,
52776
+ setLocalAppendedMessages,
52777
+ setIsSimulationComplete,
52778
+ onSimulationCompleteRef,
52779
+ isCancelledRef: () => isCancelled,
52780
+ }).catch((error) => {
51059
52781
  var _a;
51060
52782
  if (!isCancelled) {
51061
52783
  console.error('Error in MockedChat simulation:', error);
@@ -51075,7 +52797,6 @@
51075
52797
  delays.thinkingBetweenMessages,
51076
52798
  delays.waitAfterWord,
51077
52799
  delays.extraWordDelay,
51078
- resolveOffsetDelayBeforeMessage,
51079
52800
  resetNonce,
51080
52801
  ]);
51081
52802
  const renderedMessages = react.useMemo(() => [...displayedMessages, ...localAppendedMessages], [displayedMessages, localAppendedMessages]);
@@ -51123,103 +52844,273 @@
51123
52844
  }
51124
52845
 
51125
52846
  /**
51126
- * Renders TEAM conversation details, nested actions, and citations.
52847
+ * Default label used when the agent name is unavailable.
51127
52848
  *
51128
- * @param options - TEAM modal rendering options.
52849
+ * @private function of ChatToolCallModal
52850
+ */
52851
+ const DEFAULT_AGENT_LABEL = 'Agent';
52852
+ /**
52853
+ * Default label used when the teammate name is unavailable.
51129
52854
  *
51130
52855
  * @private function of ChatToolCallModal
51131
52856
  */
51132
- function TeamToolCallModalContent(options) {
51133
- var _a, _b, _c, _d, _e, _f;
51134
- const { teamResult, toolCallDate, teamToolCallSummary, selectedTeamToolCall, onSelectTeamToolCall, onClearSelectedTeamToolCall, teamProfiles, toolTitles, agentParticipant, buttonColor, } = options;
51135
- const teammateUrl = ((_a = teamResult.teammate) === null || _a === void 0 ? void 0 : _a.url) || '';
51136
- const baseTime = toolCallDate ? toolCallDate.getTime() : Date.now();
51137
- const teamToolCalls = teamToolCallSummary.toolCalls;
51138
- const teamCitations = teamToolCallSummary.citations;
51139
- const hasTeamToolCalls = teamToolCalls.length > 0;
51140
- const hasTeamCitations = teamCitations.length > 0;
51141
- const messages = (teamResult.conversation || [])
51142
- .filter((entry) => entry && entry.content)
51143
- .map((entry, index) => ({
52857
+ const DEFAULT_TEAMMATE_LABEL = 'Teammate';
52858
+ /**
52859
+ * Default header color used when the agent does not define one.
52860
+ *
52861
+ * @private function of ChatToolCallModal
52862
+ */
52863
+ const DEFAULT_AGENT_HEADER_COLOR = '#64748b';
52864
+ /**
52865
+ * Default teammate accent color used across the TEAM modal.
52866
+ *
52867
+ * @private function of ChatToolCallModal
52868
+ */
52869
+ const DEFAULT_TEAMMATE_COLOR = '#0ea5e9';
52870
+ /**
52871
+ * Returns whether one TEAM conversation entry has renderable content.
52872
+ *
52873
+ * @private function of ChatToolCallModal
52874
+ */
52875
+ function hasTeamConversationContent(entry) {
52876
+ return Boolean(entry && entry.content);
52877
+ }
52878
+ /**
52879
+ * Resolves which side of the TEAM conversation one entry belongs to.
52880
+ *
52881
+ * @private function of ChatToolCallModal
52882
+ */
52883
+ function resolveTeamConversationSender(entry) {
52884
+ return entry.sender === 'TEAMMATE' || entry.role === 'TEAMMATE' ? 'TEAMMATE' : 'AGENT';
52885
+ }
52886
+ /**
52887
+ * Creates one conversation message for the TEAM preview chat.
52888
+ *
52889
+ * @private function of ChatToolCallModal
52890
+ */
52891
+ function createTeamConversationMessage(entry, index, baseTime) {
52892
+ return {
51144
52893
  id: `team-${index}`,
51145
52894
  createdAt: new Date(baseTime + index * 1000).toISOString(),
51146
- sender: entry.sender === 'TEAMMATE' || entry.role === 'TEAMMATE' ? 'TEAMMATE' : 'AGENT',
51147
- content: entry.content || '',
52895
+ sender: resolveTeamConversationSender(entry),
52896
+ content: entry.content,
51148
52897
  isComplete: true,
51149
- }));
51150
- if (messages.length === 0) {
51151
- if (teamResult.request) {
51152
- messages.push({
51153
- id: 'team-request',
51154
- createdAt: new Date(baseTime).toISOString(),
51155
- sender: 'AGENT',
51156
- content: teamResult.request,
51157
- isComplete: true,
51158
- });
51159
- }
51160
- if (teamResult.response) {
51161
- messages.push({
51162
- id: 'team-response',
51163
- createdAt: new Date(baseTime + 1000).toISOString(),
51164
- sender: 'TEAMMATE',
51165
- content: teamResult.response,
51166
- isComplete: true,
51167
- });
51168
- }
52898
+ };
52899
+ }
52900
+ /**
52901
+ * Appends the request/response fallback pair when no structured conversation is available.
52902
+ *
52903
+ * @private function of ChatToolCallModal
52904
+ */
52905
+ function appendFallbackConversationMessages(messages, teamResult, baseTime) {
52906
+ if (messages.length > 0) {
52907
+ return;
52908
+ }
52909
+ if (teamResult.request) {
52910
+ messages.push({
52911
+ id: 'team-request',
52912
+ createdAt: new Date(baseTime).toISOString(),
52913
+ sender: 'AGENT',
52914
+ content: teamResult.request,
52915
+ isComplete: true,
52916
+ });
52917
+ }
52918
+ if (teamResult.response) {
52919
+ messages.push({
52920
+ id: 'team-response',
52921
+ createdAt: new Date(baseTime + 1000).toISOString(),
52922
+ sender: 'TEAMMATE',
52923
+ content: teamResult.response,
52924
+ isComplete: true,
52925
+ });
51169
52926
  }
51170
- const agentName = ((_c = (_b = teamResult.conversation) === null || _b === void 0 ? void 0 : _b.find((entry) => entry.sender === 'AGENT' || entry.role === 'AGENT')) === null || _c === void 0 ? void 0 : _c.name) || 'Agent';
51171
- const teammateConversationName = ((_e = (_d = teamResult.conversation) === null || _d === void 0 ? void 0 : _d.find((entry) => entry.sender === 'TEAMMATE' || entry.role === 'TEAMMATE')) === null || _e === void 0 ? void 0 : _e.name) || '';
52927
+ }
52928
+ /**
52929
+ * Builds the conversation transcript shown in the TEAM modal.
52930
+ *
52931
+ * @private function of ChatToolCallModal
52932
+ */
52933
+ function createTeamConversationMessages(teamResult, baseTime) {
52934
+ const messages = (teamResult.conversation || [])
52935
+ .filter(hasTeamConversationContent)
52936
+ .map((entry, index) => createTeamConversationMessage(entry, index, baseTime));
52937
+ appendFallbackConversationMessages(messages, teamResult, baseTime);
52938
+ return messages;
52939
+ }
52940
+ /**
52941
+ * Finds the first conversation name used by a specific sender.
52942
+ *
52943
+ * @private function of ChatToolCallModal
52944
+ */
52945
+ function resolveConversationName(teamResult, sender, fallbackName) {
52946
+ var _a, _b;
52947
+ return (((_b = (_a = teamResult.conversation) === null || _a === void 0 ? void 0 : _a.find((entry) => resolveTeamConversationSender(entry) === sender)) === null || _b === void 0 ? void 0 : _b.name) || fallbackName);
52948
+ }
52949
+ /**
52950
+ * Resolves the public teammate link when it points to a real agent.
52951
+ *
52952
+ * @private function of ChatToolCallModal
52953
+ */
52954
+ function resolveTeammateLink(teammateUrl) {
52955
+ return teammateUrl && teammateUrl !== 'VOID' && !isPseudoAgentUrl(teammateUrl) ? teammateUrl : undefined;
52956
+ }
52957
+ /**
52958
+ * Builds the TEAM modal header data and chat participants.
52959
+ *
52960
+ * @private function of ChatToolCallModal
52961
+ */
52962
+ function createTeamConversationViewModel({ agentParticipant, teamProfiles, teamResult, }) {
52963
+ var _a, _b;
52964
+ const teammateUrl = ((_a = teamResult.teammate) === null || _a === void 0 ? void 0 : _a.url) || '';
51172
52965
  const teammateProfile = teammateUrl ? teamProfiles[teammateUrl] : undefined;
51173
52966
  const teammateFallbackProfile = teammateUrl
51174
52967
  ? resolveAgentProfileFallback({
51175
52968
  url: teammateUrl,
51176
- label: (_f = teamResult.teammate) === null || _f === void 0 ? void 0 : _f.label,
52969
+ label: (_b = teamResult.teammate) === null || _b === void 0 ? void 0 : _b.label,
51177
52970
  })
51178
- : { label: 'Teammate', imageUrl: null };
52971
+ : { label: DEFAULT_TEAMMATE_LABEL, imageUrl: null };
52972
+ const agentName = resolveConversationName(teamResult, 'AGENT', DEFAULT_AGENT_LABEL);
52973
+ const teammateConversationName = resolveConversationName(teamResult, 'TEAMMATE', '');
51179
52974
  const resolvedAgentLabel = resolvePreferredAgentLabel([agentParticipant === null || agentParticipant === void 0 ? void 0 : agentParticipant.fullname, agentName], agentName);
51180
- const resolvedAgentAvatar = (agentParticipant === null || agentParticipant === void 0 ? void 0 : agentParticipant.avatarSrc) || null;
51181
- const resolvedAgentAvatarDefinition = agentParticipant === null || agentParticipant === void 0 ? void 0 : agentParticipant.avatarDefinition;
51182
- const resolvedAgentAvatarVisualId = agentParticipant === null || agentParticipant === void 0 ? void 0 : agentParticipant.avatarVisualId;
51183
- const resolvedAgentHeaderColor = (agentParticipant === null || agentParticipant === void 0 ? void 0 : agentParticipant.color)
51184
- ? Color.fromSafe(agentParticipant.color).toHex()
51185
- : '#64748b';
51186
52975
  const resolvedTeammateLabel = resolvePreferredAgentLabel([teammateConversationName, teammateProfile === null || teammateProfile === void 0 ? void 0 : teammateProfile.label, teammateFallbackProfile.label], teammateFallbackProfile.label);
51187
- const resolvedTeammateAvatar = (teammateProfile === null || teammateProfile === void 0 ? void 0 : teammateProfile.imageUrl) || teammateFallbackProfile.imageUrl || null;
51188
- const teammateLink = teammateUrl && teammateUrl !== 'VOID' && !isPseudoAgentUrl(teammateUrl) ? teammateUrl : undefined;
51189
- const participants = [
51190
- {
51191
- name: 'AGENT',
51192
- fullname: resolvedAgentLabel,
51193
- color: (agentParticipant === null || agentParticipant === void 0 ? void 0 : agentParticipant.color) || '#64748b',
51194
- avatarSrc: resolvedAgentAvatar || undefined,
51195
- avatarDefinition: resolvedAgentAvatarDefinition,
51196
- avatarVisualId: resolvedAgentAvatarVisualId,
51197
- isMe: true,
51198
- },
51199
- {
51200
- name: 'TEAMMATE',
51201
- fullname: resolvedTeammateLabel,
51202
- color: '#0ea5e9',
51203
- avatarSrc: resolvedTeammateAvatar || undefined,
51204
- },
51205
- ];
51206
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: classNames(styles$5.searchModalHeader, styles$5.teamModalHeader), children: jsxRuntime.jsxs("div", { className: styles$5.teamHeaderParticipants, children: [jsxRuntime.jsx(TeamHeaderProfile, { label: resolvedAgentLabel, avatarSrc: resolvedAgentAvatar, avatarDefinition: resolvedAgentAvatarDefinition, avatarVisualId: resolvedAgentAvatarVisualId, fallbackColor: resolvedAgentHeaderColor }), jsxRuntime.jsx("span", { className: styles$5.teamHeaderDivider, children: "talking with" }), jsxRuntime.jsx(TeamHeaderProfile, { label: resolvedTeammateLabel, avatarSrc: resolvedTeammateAvatar, fallbackColor: "#0ea5e9", href: teammateLink })] }) }), jsxRuntime.jsxs("div", { className: styles$5.searchModalContent, children: [messages.length > 0 ? (jsxRuntime.jsx("div", { className: styles$5.teamChatContainer, children: jsxRuntime.jsx(MockedChat, { title: `Chat between ${resolvedAgentLabel} and ${resolvedTeammateLabel}`, messages: messages, participants: participants, isResettable: false, isPausable: false, isSaveButtonEnabled: false, isCopyButtonEnabled: false, layout: "STANDALONE", delayConfig: {
51207
- // Note+TODO: For some strange reason, <MockedChat/> is not running and stays static on the initial frame, so doing this hack to force it to show the entire chat at once. Need to investigate why the animation is not running as expected and then just use `delayConfig={FAST_FLOW}`
51208
- ...FAST_FLOW,
51209
- beforeFirstMessage: 0,
51210
- showIntermediateMessages: messages.length,
51211
- }, visualMode: "BUBBLE_MODE" }) })) : (jsxRuntime.jsx("div", { className: styles$5.noResults, children: "No teammate conversation available." })), (hasTeamToolCalls || hasTeamCitations) && (jsxRuntime.jsxs("div", { className: styles$5.teamToolCallSection, children: [hasTeamToolCalls && (jsxRuntime.jsxs("div", { className: styles$5.teamToolCallGroup, children: [jsxRuntime.jsx("div", { className: styles$5.teamToolCallHeading, children: "Actions" }), jsxRuntime.jsx("div", { className: styles$5.teamToolCallChips, children: teamToolCalls.map((toolCallEntry, index) => {
51212
- const chipletInfo = getToolCallChipletInfo(toolCallEntry.toolCall, undefined, toolTitles);
51213
- const chipletText = buildToolCallChipText(chipletInfo);
51214
- return (jsxRuntime.jsxs("button", { className: styles$5.completedToolCall, onClick: () => {
51215
- onSelectTeamToolCall(toolCallEntry);
51216
- }, children: [jsxRuntime.jsx("span", { children: chipletText }), jsxRuntime.jsxs("span", { className: styles$5.toolCallOrigin, children: ["by ", toolCallEntry.origin.label] })] }, `team-tool-${index}`));
51217
- }) })] })), hasTeamCitations && (jsxRuntime.jsxs("div", { className: styles$5.teamToolCallGroup, children: [jsxRuntime.jsx("div", { className: styles$5.teamToolCallHeading, children: "Sources" }), jsxRuntime.jsx("div", { className: styles$5.teamToolCallChips, children: teamCitations.map((citation, index) => (jsxRuntime.jsx(SourceChip, { citation: citation, suffix: `by ${citation.origin.label}` }, `team-source-${citation.source}-${index}`))) })] })), selectedTeamToolCall && (jsxRuntime.jsxs("div", { className: styles$5.teamToolCallDetails, children: [jsxRuntime.jsxs("div", { className: styles$5.teamToolCallDetailsHeader, children: [jsxRuntime.jsxs("span", { className: styles$5.teamToolCallDetailsTitle, children: ["Action details", jsxRuntime.jsxs("span", { className: styles$5.toolCallOrigin, children: ["by ", selectedTeamToolCall.origin.label] })] }), jsxRuntime.jsx("button", { type: "button", className: styles$5.teamToolCallDetailsClear, onClick: onClearSelectedTeamToolCall, children: "Clear" })] }), renderToolCallDetails({
51218
- toolCall: selectedTeamToolCall.toolCall,
51219
- toolTitles,
51220
- agentParticipant,
51221
- buttonColor,
51222
- })] }))] }))] })] }));
52976
+ const agentHeader = {
52977
+ label: resolvedAgentLabel,
52978
+ avatarSrc: (agentParticipant === null || agentParticipant === void 0 ? void 0 : agentParticipant.avatarSrc) || null,
52979
+ avatarDefinition: agentParticipant === null || agentParticipant === void 0 ? void 0 : agentParticipant.avatarDefinition,
52980
+ avatarVisualId: agentParticipant === null || agentParticipant === void 0 ? void 0 : agentParticipant.avatarVisualId,
52981
+ fallbackColor: (agentParticipant === null || agentParticipant === void 0 ? void 0 : agentParticipant.color)
52982
+ ? Color.fromSafe(agentParticipant.color).toHex()
52983
+ : DEFAULT_AGENT_HEADER_COLOR,
52984
+ };
52985
+ const teammateHeader = {
52986
+ label: resolvedTeammateLabel,
52987
+ avatarSrc: (teammateProfile === null || teammateProfile === void 0 ? void 0 : teammateProfile.imageUrl) || teammateFallbackProfile.imageUrl || null,
52988
+ fallbackColor: DEFAULT_TEAMMATE_COLOR,
52989
+ href: resolveTeammateLink(teammateUrl),
52990
+ };
52991
+ return {
52992
+ agentHeader,
52993
+ teammateHeader,
52994
+ participants: [
52995
+ {
52996
+ name: 'AGENT',
52997
+ fullname: agentHeader.label,
52998
+ color: (agentParticipant === null || agentParticipant === void 0 ? void 0 : agentParticipant.color) || DEFAULT_AGENT_HEADER_COLOR,
52999
+ avatarSrc: agentHeader.avatarSrc || undefined,
53000
+ avatarDefinition: agentHeader.avatarDefinition,
53001
+ avatarVisualId: agentHeader.avatarVisualId,
53002
+ isMe: true,
53003
+ },
53004
+ {
53005
+ name: 'TEAMMATE',
53006
+ fullname: teammateHeader.label,
53007
+ color: DEFAULT_TEAMMATE_COLOR,
53008
+ avatarSrc: teammateHeader.avatarSrc || undefined,
53009
+ },
53010
+ ],
53011
+ };
53012
+ }
53013
+ /**
53014
+ * Renders the TEAM conversation header profiles.
53015
+ *
53016
+ * @private component of ChatToolCallModal
53017
+ */
53018
+ function TeamConversationHeader(props) {
53019
+ const { agentHeader, teammateHeader } = props;
53020
+ return (jsxRuntime.jsx("div", { className: classNames(styles$5.searchModalHeader, styles$5.teamModalHeader), children: jsxRuntime.jsxs("div", { className: styles$5.teamHeaderParticipants, children: [jsxRuntime.jsx(TeamHeaderProfile, { label: agentHeader.label, avatarSrc: agentHeader.avatarSrc, avatarDefinition: agentHeader.avatarDefinition, avatarVisualId: agentHeader.avatarVisualId, fallbackColor: agentHeader.fallbackColor }), jsxRuntime.jsx("span", { className: styles$5.teamHeaderDivider, children: "talking with" }), jsxRuntime.jsx(TeamHeaderProfile, { label: teammateHeader.label, avatarSrc: teammateHeader.avatarSrc, fallbackColor: teammateHeader.fallbackColor, href: teammateHeader.href })] }) }));
53021
+ }
53022
+ /**
53023
+ * Renders the TEAM conversation transcript or the empty-state fallback.
53024
+ *
53025
+ * @private component of ChatToolCallModal
53026
+ */
53027
+ function TeamConversationSection(props) {
53028
+ const { agentLabel, teammateLabel, messages, participants } = props;
53029
+ if (messages.length === 0) {
53030
+ return jsxRuntime.jsx("div", { className: styles$5.noResults, children: "No teammate conversation available." });
53031
+ }
53032
+ return (jsxRuntime.jsx("div", { className: styles$5.teamChatContainer, children: jsxRuntime.jsx(MockedChat, { title: `Chat between ${agentLabel} and ${teammateLabel}`, messages: messages, participants: participants, isResettable: false, isPausable: false, isSaveButtonEnabled: false, isCopyButtonEnabled: false, layout: "STANDALONE", delayConfig: {
53033
+ // Note+TODO: For some strange reason, <MockedChat/> is not running and stays static on the initial frame, so doing this hack to force it to show the entire chat at once. Need to investigate why the animation is not running as expected and then just use `delayConfig={FAST_FLOW}`
53034
+ ...FAST_FLOW,
53035
+ beforeFirstMessage: 0,
53036
+ showIntermediateMessages: messages.length,
53037
+ }, visualMode: "BUBBLE_MODE" }) }));
53038
+ }
53039
+ /**
53040
+ * Renders action chips for teammate-executed tool calls.
53041
+ *
53042
+ * @private component of ChatToolCallModal
53043
+ */
53044
+ function TeamToolCallActionsGroup(props) {
53045
+ const { onSelectTeamToolCall, teamToolCalls, toolTitles } = props;
53046
+ if (teamToolCalls.length === 0) {
53047
+ return null;
53048
+ }
53049
+ return (jsxRuntime.jsxs("div", { className: styles$5.teamToolCallGroup, children: [jsxRuntime.jsx("div", { className: styles$5.teamToolCallHeading, children: "Actions" }), jsxRuntime.jsx("div", { className: styles$5.teamToolCallChips, children: teamToolCalls.map((toolCallEntry, index) => {
53050
+ const chipletInfo = getToolCallChipletInfo(toolCallEntry.toolCall, undefined, toolTitles);
53051
+ const chipletText = buildToolCallChipText(chipletInfo);
53052
+ return (jsxRuntime.jsxs("button", { className: styles$5.completedToolCall, onClick: () => {
53053
+ onSelectTeamToolCall(toolCallEntry);
53054
+ }, children: [jsxRuntime.jsx("span", { children: chipletText }), jsxRuntime.jsxs("span", { className: styles$5.toolCallOrigin, children: ["by ", toolCallEntry.origin.label] })] }, `team-tool-${index}`));
53055
+ }) })] }));
53056
+ }
53057
+ /**
53058
+ * Renders citations surfaced from the teammate exchange.
53059
+ *
53060
+ * @private component of ChatToolCallModal
53061
+ */
53062
+ function TeamToolCallSourcesGroup(props) {
53063
+ const { teamCitations } = props;
53064
+ if (teamCitations.length === 0) {
53065
+ return null;
53066
+ }
53067
+ return (jsxRuntime.jsxs("div", { className: styles$5.teamToolCallGroup, children: [jsxRuntime.jsx("div", { className: styles$5.teamToolCallHeading, children: "Sources" }), jsxRuntime.jsx("div", { className: styles$5.teamToolCallChips, children: teamCitations.map((citation, index) => (jsxRuntime.jsx(SourceChip, { citation: citation, suffix: `by ${citation.origin.label}` }, `team-source-${citation.source}-${index}`))) })] }));
53068
+ }
53069
+ /**
53070
+ * Renders the nested action details for the selected teammate tool call.
53071
+ *
53072
+ * @private component of ChatToolCallModal
53073
+ */
53074
+ function TeamToolCallDetailsPanel(props) {
53075
+ const { agentParticipant, buttonColor, onClearSelectedTeamToolCall, selectedTeamToolCall, toolTitles } = props;
53076
+ if (!selectedTeamToolCall) {
53077
+ return null;
53078
+ }
53079
+ return (jsxRuntime.jsxs("div", { className: styles$5.teamToolCallDetails, children: [jsxRuntime.jsxs("div", { className: styles$5.teamToolCallDetailsHeader, children: [jsxRuntime.jsxs("span", { className: styles$5.teamToolCallDetailsTitle, children: ["Action details", jsxRuntime.jsxs("span", { className: styles$5.toolCallOrigin, children: ["by ", selectedTeamToolCall.origin.label] })] }), jsxRuntime.jsx("button", { type: "button", className: styles$5.teamToolCallDetailsClear, onClick: onClearSelectedTeamToolCall, children: "Clear" })] }), renderToolCallDetails({
53080
+ toolCall: selectedTeamToolCall.toolCall,
53081
+ toolTitles,
53082
+ agentParticipant,
53083
+ buttonColor,
53084
+ })] }));
53085
+ }
53086
+ /**
53087
+ * Renders the nested tool-call and citation summary below the TEAM transcript.
53088
+ *
53089
+ * @private component of ChatToolCallModal
53090
+ */
53091
+ function TeamToolCallSummarySection(props) {
53092
+ const { agentParticipant, buttonColor, onClearSelectedTeamToolCall, onSelectTeamToolCall, selectedTeamToolCall, teamToolCallSummary, toolTitles, } = props;
53093
+ const hasSummaryContent = teamToolCallSummary.toolCalls.length > 0 || teamToolCallSummary.citations.length > 0;
53094
+ if (!hasSummaryContent) {
53095
+ return null;
53096
+ }
53097
+ return (jsxRuntime.jsxs("div", { className: styles$5.teamToolCallSection, children: [jsxRuntime.jsx(TeamToolCallActionsGroup, { teamToolCalls: teamToolCallSummary.toolCalls, onSelectTeamToolCall: onSelectTeamToolCall, toolTitles: toolTitles }), jsxRuntime.jsx(TeamToolCallSourcesGroup, { teamCitations: teamToolCallSummary.citations }), jsxRuntime.jsx(TeamToolCallDetailsPanel, { selectedTeamToolCall: selectedTeamToolCall, onClearSelectedTeamToolCall: onClearSelectedTeamToolCall, toolTitles: toolTitles, agentParticipant: agentParticipant, buttonColor: buttonColor })] }));
53098
+ }
53099
+ /**
53100
+ * Renders TEAM conversation details, nested actions, and citations.
53101
+ *
53102
+ * @private function of ChatToolCallModal
53103
+ */
53104
+ function TeamToolCallModalContent(options) {
53105
+ const { teamResult, toolCallDate, teamToolCallSummary, selectedTeamToolCall, onSelectTeamToolCall, onClearSelectedTeamToolCall, teamProfiles, toolTitles, agentParticipant, buttonColor, } = options;
53106
+ const baseTime = toolCallDate ? toolCallDate.getTime() : Date.now();
53107
+ const messages = createTeamConversationMessages(teamResult, baseTime);
53108
+ const { agentHeader, teammateHeader, participants } = createTeamConversationViewModel({
53109
+ teamResult,
53110
+ teamProfiles,
53111
+ agentParticipant,
53112
+ });
53113
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(TeamConversationHeader, { agentHeader: agentHeader, teammateHeader: teammateHeader }), jsxRuntime.jsxs("div", { className: styles$5.searchModalContent, children: [jsxRuntime.jsx(TeamConversationSection, { agentLabel: agentHeader.label, teammateLabel: teammateHeader.label, messages: messages, participants: participants }), jsxRuntime.jsx(TeamToolCallSummarySection, { teamToolCallSummary: teamToolCallSummary, selectedTeamToolCall: selectedTeamToolCall, onSelectTeamToolCall: onSelectTeamToolCall, onClearSelectedTeamToolCall: onClearSelectedTeamToolCall, toolTitles: toolTitles, agentParticipant: agentParticipant, buttonColor: buttonColor })] })] }));
51223
53114
  }
51224
53115
 
51225
53116
  /**
@@ -51671,8 +53562,314 @@
51671
53562
 
51672
53563
  /**
51673
53564
  * Debounce window for synchronizing `isAutoScrolling` with the latest scroll position.
53565
+ *
53566
+ * @private function of `useChatAutoScroll`
51674
53567
  */
51675
53568
  const SCROLL_EVENT_DEBOUNCE_MS = 50;
53569
+ /**
53570
+ * Faster follow-up used while the latest assistant message is still streaming.
53571
+ *
53572
+ * @private function of `useChatAutoScroll`
53573
+ */
53574
+ const STREAMING_SCROLL_CHECK_DELAY_MS = 10;
53575
+ /**
53576
+ * Viewport width treated as mobile for chat scrolling behavior.
53577
+ *
53578
+ * @private function of `useChatAutoScroll`
53579
+ */
53580
+ const MOBILE_BREAKPOINT_PX = 768;
53581
+ /**
53582
+ * Mobile-device user agent matcher used by the chat viewport detection.
53583
+ *
53584
+ * @private function of `useChatAutoScroll`
53585
+ */
53586
+ const MOBILE_DEVICE_USER_AGENT_PATTERN = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
53587
+ /**
53588
+ * Detects whether the current viewport should use the mobile scrolling path.
53589
+ *
53590
+ * @returns `true` when the viewport or device matches the mobile heuristics.
53591
+ *
53592
+ * @private function of `useChatAutoScroll`
53593
+ */
53594
+ function isMobileChatViewport() {
53595
+ return window.innerWidth <= MOBILE_BREAKPOINT_PX || MOBILE_DEVICE_USER_AGENT_PATTERN.test(navigator.userAgent);
53596
+ }
53597
+ /**
53598
+ * Tracks whether the chat should currently use mobile scrolling behavior.
53599
+ *
53600
+ * @private function of `useChatAutoScroll`
53601
+ */
53602
+ function useMobileChatViewport() {
53603
+ const [isMobile, setIsMobile] = react.useState(false);
53604
+ react.useEffect(() => {
53605
+ const handleViewportChange = () => {
53606
+ setIsMobile(isMobileChatViewport());
53607
+ };
53608
+ handleViewportChange();
53609
+ window.addEventListener('resize', handleViewportChange);
53610
+ return () => window.removeEventListener('resize', handleViewportChange);
53611
+ }, []);
53612
+ return isMobile;
53613
+ }
53614
+ /**
53615
+ * Checks whether the messages container is effectively scrolled to the bottom.
53616
+ *
53617
+ * @param element - Scrollable chat messages container.
53618
+ * @param bottomThreshold - Distance from the bottom still considered "at bottom".
53619
+ * @returns `true` when the container is within the threshold from the bottom.
53620
+ *
53621
+ * @private function of `useChatAutoScroll`
53622
+ */
53623
+ function isChatScrolledToBottom(element, bottomThreshold) {
53624
+ const { scrollTop, scrollHeight, clientHeight } = element;
53625
+ return scrollTop + clientHeight >= scrollHeight - bottomThreshold;
53626
+ }
53627
+ /**
53628
+ * Clears a scheduled timeout when one is currently active.
53629
+ *
53630
+ * @private function of `useChatAutoScroll`
53631
+ */
53632
+ function clearScheduledTimeout(timeoutRef) {
53633
+ if (timeoutRef.current) {
53634
+ clearTimeout(timeoutRef.current);
53635
+ timeoutRef.current = null;
53636
+ }
53637
+ }
53638
+ /**
53639
+ * Performs the concrete scroll-to-bottom operation for the current platform.
53640
+ *
53641
+ * @private function of `useChatAutoScroll`
53642
+ */
53643
+ function performScrollToBottom({ behavior, chatMessagesElement, isMobile, smoothScroll, }) {
53644
+ if (isMobile) {
53645
+ chatMessagesElement.scrollTo({
53646
+ top: chatMessagesElement.scrollHeight,
53647
+ behavior: smoothScroll ? behavior : 'auto',
53648
+ });
53649
+ return;
53650
+ }
53651
+ if (smoothScroll && behavior === 'smooth') {
53652
+ chatMessagesElement.style.scrollBehavior = 'smooth';
53653
+ chatMessagesElement.scrollTop = chatMessagesElement.scrollHeight;
53654
+ chatMessagesElement.style.scrollBehavior = 'auto';
53655
+ return;
53656
+ }
53657
+ chatMessagesElement.scrollTop = chatMessagesElement.scrollHeight;
53658
+ }
53659
+ /**
53660
+ * Detects whether the user currently has a non-collapsed selection inside the chat container.
53661
+ *
53662
+ * @private function of `useChatAutoScroll`
53663
+ */
53664
+ function hasExpandedSelectionInChat(chatMessagesElement) {
53665
+ const selection = window.getSelection();
53666
+ if (!selection || selection.rangeCount === 0 || selection.isCollapsed) {
53667
+ return false;
53668
+ }
53669
+ const range = selection.getRangeAt(0);
53670
+ return chatMessagesElement.contains(range.startContainer) || chatMessagesElement.contains(range.endContainer);
53671
+ }
53672
+ /**
53673
+ * Computes the scroll metrics needed to decide whether new content should trigger auto-scroll.
53674
+ *
53675
+ * @private function of `useChatAutoScroll`
53676
+ */
53677
+ function getMessagesChangeMetrics({ bottomThreshold, chatMessagesElement, previousScrollHeight, }) {
53678
+ const currentScrollHeight = chatMessagesElement.scrollHeight;
53679
+ return {
53680
+ currentScrollHeight,
53681
+ hasNewContent: currentScrollHeight > previousScrollHeight,
53682
+ wasAtBottomBeforeNewContent: chatMessagesElement.scrollTop + chatMessagesElement.clientHeight >= previousScrollHeight - bottomThreshold,
53683
+ };
53684
+ }
53685
+ /**
53686
+ * Evaluates whether new content should keep the chat pinned to the latest message.
53687
+ *
53688
+ * @private function of `useChatAutoScroll`
53689
+ */
53690
+ function shouldAutoScrollForMessagesChange({ hasManualScroll, hasNewContent, hasSelectionInChat, isAutoScrolling, wasAtBottomBeforeNewContent, }) {
53691
+ return hasNewContent && isAutoScrolling && wasAtBottomBeforeNewContent && !hasSelectionInChat && !hasManualScroll;
53692
+ }
53693
+ /**
53694
+ * Evaluates whether the latest DOM update should schedule a follow-up scroll.
53695
+ *
53696
+ * @private function of `useChatAutoScroll`
53697
+ */
53698
+ function evaluateMessagesChangeAutoScroll({ bottomThreshold, chatMessagesElement, hasManualScroll, isAutoScrolling, previousScrollHeight, }) {
53699
+ const metrics = getMessagesChangeMetrics({
53700
+ bottomThreshold,
53701
+ chatMessagesElement,
53702
+ previousScrollHeight,
53703
+ });
53704
+ return {
53705
+ currentScrollHeight: metrics.currentScrollHeight,
53706
+ shouldScheduleScroll: shouldAutoScrollForMessagesChange({
53707
+ hasManualScroll,
53708
+ hasNewContent: metrics.hasNewContent,
53709
+ hasSelectionInChat: hasExpandedSelectionInChat(chatMessagesElement),
53710
+ isAutoScrolling,
53711
+ wasAtBottomBeforeNewContent: metrics.wasAtBottomBeforeNewContent,
53712
+ }),
53713
+ };
53714
+ }
53715
+ /**
53716
+ * Chooses the follow-up delay for the next automatic scroll attempt.
53717
+ *
53718
+ * @private function of `useChatAutoScroll`
53719
+ */
53720
+ function getMessagesChangeDelay(isStreaming, scrollCheckDelay) {
53721
+ return isStreaming ? STREAMING_SCROLL_CHECK_DELAY_MS : scrollCheckDelay;
53722
+ }
53723
+ /**
53724
+ * Chooses the scroll behavior for the next automatic scroll attempt.
53725
+ *
53726
+ * @private function of `useChatAutoScroll`
53727
+ */
53728
+ function getMessagesChangeScrollBehavior(isStreaming) {
53729
+ return isStreaming ? 'auto' : 'smooth';
53730
+ }
53731
+ /**
53732
+ * Updates the manual-scroll and auto-scroll flags from the latest direct scroll event.
53733
+ *
53734
+ * @private function of `useChatAutoScroll`
53735
+ */
53736
+ function updateAutoScrollStateFromScroll({ hasManualScrollRef, isAtBottom, isAutoScrolling, setIsAutoScrolling, }) {
53737
+ hasManualScrollRef.current = !isAtBottom;
53738
+ if (!isAtBottom && isAutoScrolling) {
53739
+ setIsAutoScrolling(false);
53740
+ }
53741
+ }
53742
+ /**
53743
+ * Schedules the debounced reconciliation that keeps `isAutoScrolling` in sync with the viewport.
53744
+ *
53745
+ * @private function of `useChatAutoScroll`
53746
+ */
53747
+ function scheduleAutoScrollStateSync({ element, scrollTimeoutRef, syncAutoScrollState, }) {
53748
+ clearScheduledTimeout(scrollTimeoutRef);
53749
+ scrollTimeoutRef.current = setTimeout(() => {
53750
+ syncAutoScrollState(element);
53751
+ }, SCROLL_EVENT_DEBOUNCE_MS);
53752
+ }
53753
+ /**
53754
+ * Schedules the delayed scroll that follows a new message or streaming chunk.
53755
+ *
53756
+ * @private function of `useChatAutoScroll`
53757
+ */
53758
+ function scheduleMessagesChangeAutoScroll({ hasManualScrollRef, isStreaming, messagesChangeTimeoutRef, scrollCheckDelay, scrollToBottom, }) {
53759
+ clearScheduledTimeout(messagesChangeTimeoutRef);
53760
+ messagesChangeTimeoutRef.current = setTimeout(() => {
53761
+ if (hasManualScrollRef.current) {
53762
+ return;
53763
+ }
53764
+ scrollToBottom(getMessagesChangeScrollBehavior(isStreaming));
53765
+ }, getMessagesChangeDelay(isStreaming, scrollCheckDelay));
53766
+ }
53767
+ /**
53768
+ * Initializes the chat container reference and restores the pinned-to-bottom state when needed.
53769
+ *
53770
+ * @private function of `useChatAutoScroll`
53771
+ */
53772
+ function initializeChatMessagesElement({ element, isAutoScrolling, lastScrollHeightRef, scrollToBottom, }) {
53773
+ lastScrollHeightRef.current = element.scrollHeight;
53774
+ if (!isAutoScrolling) {
53775
+ return;
53776
+ }
53777
+ requestAnimationFrame(() => {
53778
+ scrollToBottom('auto');
53779
+ });
53780
+ }
53781
+ /**
53782
+ * Creates the debounced scroll handler that reacts to direct user scrolling.
53783
+ *
53784
+ * @private function of `useChatAutoScroll`
53785
+ */
53786
+ function useChatScrollHandler({ checkIfAtBottom, hasManualScrollRef, isAutoScrolling, scrollTimeoutRef, setIsAutoScrolling, }) {
53787
+ const syncAutoScrollState = react.useCallback((element) => {
53788
+ const isAtBottom = checkIfAtBottom(element);
53789
+ setIsAutoScrolling((currentValue) => (currentValue === isAtBottom ? currentValue : isAtBottom));
53790
+ }, [checkIfAtBottom, setIsAutoScrolling]);
53791
+ return react.useCallback((event) => {
53792
+ const element = event.currentTarget;
53793
+ const isAtBottom = checkIfAtBottom(element);
53794
+ updateAutoScrollStateFromScroll({
53795
+ hasManualScrollRef,
53796
+ isAtBottom,
53797
+ isAutoScrolling,
53798
+ setIsAutoScrolling,
53799
+ });
53800
+ scheduleAutoScrollStateSync({
53801
+ element,
53802
+ scrollTimeoutRef,
53803
+ syncAutoScrollState,
53804
+ });
53805
+ }, [
53806
+ checkIfAtBottom,
53807
+ hasManualScrollRef,
53808
+ isAutoScrolling,
53809
+ scrollTimeoutRef,
53810
+ setIsAutoScrolling,
53811
+ syncAutoScrollState,
53812
+ ]);
53813
+ }
53814
+ /**
53815
+ * Creates the message-change handler that decides whether the chat should follow new content.
53816
+ *
53817
+ * @private function of `useChatAutoScroll`
53818
+ */
53819
+ function useChatMessagesChangeHandler({ bottomThreshold, chatMessagesRef, hasManualScrollRef, isAutoScrolling, lastScrollHeightRef, messagesChangeTimeoutRef, scrollCheckDelay, scrollToBottom, }) {
53820
+ return react.useCallback((isStreaming = false) => {
53821
+ const chatMessagesElement = chatMessagesRef.current;
53822
+ if (!chatMessagesElement) {
53823
+ return;
53824
+ }
53825
+ const autoScrollDecision = evaluateMessagesChangeAutoScroll({
53826
+ bottomThreshold,
53827
+ chatMessagesElement,
53828
+ hasManualScroll: hasManualScrollRef.current,
53829
+ isAutoScrolling,
53830
+ previousScrollHeight: lastScrollHeightRef.current,
53831
+ });
53832
+ lastScrollHeightRef.current = autoScrollDecision.currentScrollHeight;
53833
+ if (!autoScrollDecision.shouldScheduleScroll) {
53834
+ return;
53835
+ }
53836
+ scheduleMessagesChangeAutoScroll({
53837
+ hasManualScrollRef,
53838
+ isStreaming,
53839
+ messagesChangeTimeoutRef,
53840
+ scrollCheckDelay,
53841
+ scrollToBottom,
53842
+ });
53843
+ }, [
53844
+ bottomThreshold,
53845
+ chatMessagesRef,
53846
+ hasManualScrollRef,
53847
+ isAutoScrolling,
53848
+ lastScrollHeightRef,
53849
+ messagesChangeTimeoutRef,
53850
+ scrollCheckDelay,
53851
+ scrollToBottom,
53852
+ ]);
53853
+ }
53854
+ /**
53855
+ * Creates the ref callback that captures the chat container and initializes its scroll state.
53856
+ *
53857
+ * @private function of `useChatAutoScroll`
53858
+ */
53859
+ function useChatMessagesRefCallback({ chatMessagesRef, isAutoScrolling, lastScrollHeightRef, scrollToBottom, }) {
53860
+ return react.useCallback((element) => {
53861
+ chatMessagesRef.current = element;
53862
+ if (!element) {
53863
+ return;
53864
+ }
53865
+ initializeChatMessagesElement({
53866
+ element,
53867
+ isAutoScrolling,
53868
+ lastScrollHeightRef,
53869
+ scrollToBottom,
53870
+ });
53871
+ }, [chatMessagesRef, isAutoScrolling, lastScrollHeightRef, scrollToBottom]);
53872
+ }
51676
53873
  /**
51677
53874
  * Hook for managing auto-scroll behavior in chat components
51678
53875
  *
@@ -51686,153 +53883,61 @@
51686
53883
  */
51687
53884
  function useChatAutoScroll(config = {}) {
51688
53885
  const { bottomThreshold = 100, smoothScroll = true, scrollCheckDelay = 100 } = config;
53886
+ const isMobile = useMobileChatViewport();
51689
53887
  const [isAutoScrolling, setIsAutoScrolling] = react.useState(true);
51690
- const [isMobile, setIsMobile] = react.useState(false);
51691
53888
  const chatMessagesRef = react.useRef(null);
51692
53889
  const scrollTimeoutRef = react.useRef(null);
51693
53890
  const lastScrollHeightRef = react.useRef(0);
51694
- // Tracks whether the user moved away from the bottom so we can suspend auto-scroll.
51695
53891
  const hasManualScrollRef = react.useRef(false);
51696
- // Detect mobile device
51697
- react.useEffect(() => {
51698
- const checkMobile = () => {
51699
- const isMobileDevice = window.innerWidth <= 768 ||
51700
- /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
51701
- setIsMobile(isMobileDevice);
51702
- };
51703
- checkMobile();
51704
- window.addEventListener('resize', checkMobile);
51705
- return () => window.removeEventListener('resize', checkMobile);
51706
- }, []);
51707
- // Check if user is at the bottom of the chat
51708
- const checkIfAtBottom = react.useCallback((element) => {
51709
- const { scrollTop, scrollHeight, clientHeight } = element;
51710
- return scrollTop + clientHeight >= scrollHeight - bottomThreshold;
51711
- }, [bottomThreshold]);
51712
- // Scroll to bottom function
53892
+ const messagesChangeTimeoutRef = react.useRef(null);
53893
+ const checkIfAtBottom = react.useCallback((element) => isChatScrolledToBottom(element, bottomThreshold), [bottomThreshold]);
51713
53894
  const scrollToBottom = react.useCallback((behavior = 'smooth') => {
51714
53895
  const chatMessagesElement = chatMessagesRef.current;
51715
- if (!chatMessagesElement)
53896
+ if (!chatMessagesElement) {
51716
53897
  return;
51717
- if (isMobile) {
51718
- // Mobile-optimized scrolling
51719
- chatMessagesElement.scrollTo({
51720
- top: chatMessagesElement.scrollHeight,
51721
- behavior: smoothScroll ? behavior : 'auto',
51722
- });
51723
- }
51724
- else {
51725
- // Desktop scrolling
51726
- if (smoothScroll && behavior === 'smooth') {
51727
- chatMessagesElement.style.scrollBehavior = 'smooth';
51728
- chatMessagesElement.scrollTop = chatMessagesElement.scrollHeight;
51729
- chatMessagesElement.style.scrollBehavior = 'auto';
51730
- }
51731
- else {
51732
- chatMessagesElement.scrollTop = chatMessagesElement.scrollHeight;
51733
- }
51734
53898
  }
53899
+ performScrollToBottom({
53900
+ behavior,
53901
+ chatMessagesElement,
53902
+ isMobile,
53903
+ smoothScroll,
53904
+ });
51735
53905
  hasManualScrollRef.current = false;
51736
53906
  }, [isMobile, smoothScroll]);
51737
- // Handle scroll events
51738
- const handleScroll = react.useCallback((event) => {
51739
- const element = event.target;
51740
- if (!element)
51741
- return;
51742
- const atBottom = checkIfAtBottom(element);
51743
- hasManualScrollRef.current = !atBottom;
51744
- // User scroll should take precedence over app-driven scroll intent.
51745
- if (!atBottom && isAutoScrolling) {
51746
- setIsAutoScrolling(false);
51747
- }
51748
- // Clear any pending scroll timeout
51749
- if (scrollTimeoutRef.current) {
51750
- clearTimeout(scrollTimeoutRef.current);
51751
- }
51752
- // Debounce scroll position check to avoid too frequent updates
51753
- scrollTimeoutRef.current = setTimeout(() => {
51754
- const isAtBottom = checkIfAtBottom(element);
51755
- setIsAutoScrolling((currentState) => (currentState === isAtBottom ? currentState : isAtBottom));
51756
- }, SCROLL_EVENT_DEBOUNCE_MS);
51757
- }, [checkIfAtBottom, isAutoScrolling]);
51758
- const messagesChangeTimeoutRef = react.useRef(null);
51759
- // Auto-scroll when messages change (if user is at bottom)
51760
- const handleMessagesChange = react.useCallback((isStreaming = false) => {
51761
- const chatMessagesElement = chatMessagesRef.current;
51762
- if (!chatMessagesElement)
51763
- return;
51764
- // Check if this is a new message (scroll height increased)
51765
- const previousScrollHeight = lastScrollHeightRef.current;
51766
- const currentScrollHeight = chatMessagesElement.scrollHeight;
51767
- const hasNewContent = currentScrollHeight > previousScrollHeight;
51768
- const wasAtBottomBeforeNewContent = chatMessagesElement.scrollTop + chatMessagesElement.clientHeight >=
51769
- previousScrollHeight - bottomThreshold;
51770
- lastScrollHeightRef.current = currentScrollHeight;
51771
- if (!hasNewContent)
51772
- return;
51773
- // Only auto-scroll if user does NOT have a selection inside chat container
51774
- const selection = window.getSelection();
51775
- let hasSelectionInChat = false;
51776
- if (selection && selection.rangeCount > 0) {
51777
- const range = selection.getRangeAt(0);
51778
- if (chatMessagesElement.contains(range.startContainer) ||
51779
- chatMessagesElement.contains(range.endContainer)) {
51780
- if (!selection.isCollapsed) {
51781
- hasSelectionInChat = true;
51782
- }
51783
- }
51784
- }
51785
- if (isAutoScrolling && wasAtBottomBeforeNewContent && !hasSelectionInChat && !hasManualScrollRef.current) {
51786
- if (messagesChangeTimeoutRef.current) {
51787
- clearTimeout(messagesChangeTimeoutRef.current);
51788
- }
51789
- // Delay scroll slightly to ensure DOM has updated
51790
- messagesChangeTimeoutRef.current = setTimeout(() => {
51791
- if (hasManualScrollRef.current) {
51792
- return;
51793
- }
51794
- scrollToBottom(isStreaming ? 'auto' : 'smooth');
51795
- }, isStreaming ? 10 : scrollCheckDelay);
51796
- }
51797
- }, [bottomThreshold, isAutoScrolling, scrollToBottom, scrollCheckDelay]);
51798
- // Ref callback for chat messages container
51799
- const chatMessagesRefCallback = react.useCallback((element) => {
51800
- chatMessagesRef.current = element;
51801
- if (element) {
51802
- // Update last scroll height
51803
- lastScrollHeightRef.current = element.scrollHeight;
51804
- // If auto-scrolling is enabled, scroll to bottom
51805
- if (isAutoScrolling) {
51806
- // Use requestAnimationFrame for smoother initial scroll
51807
- requestAnimationFrame(() => {
51808
- scrollToBottom('auto');
51809
- });
51810
- }
51811
- }
51812
- }, [isAutoScrolling, scrollToBottom]);
51813
- // Manual scroll to bottom (for button click)
51814
- const handleScrollToBottomClick = react.useCallback(() => {
51815
- setIsAutoScrolling(true);
51816
- scrollToBottom('smooth');
51817
- }, [scrollToBottom]);
51818
- // Force auto-scroll back on (useful for programmatic control)
51819
- const enableAutoScroll = react.useCallback(() => {
53907
+ const handleScroll = useChatScrollHandler({
53908
+ checkIfAtBottom,
53909
+ hasManualScrollRef,
53910
+ isAutoScrolling,
53911
+ scrollTimeoutRef,
53912
+ setIsAutoScrolling,
53913
+ });
53914
+ const handleMessagesChange = useChatMessagesChangeHandler({
53915
+ bottomThreshold,
53916
+ chatMessagesRef,
53917
+ hasManualScrollRef,
53918
+ isAutoScrolling,
53919
+ lastScrollHeightRef,
53920
+ messagesChangeTimeoutRef,
53921
+ scrollCheckDelay,
53922
+ scrollToBottom,
53923
+ });
53924
+ const chatMessagesRefCallback = useChatMessagesRefCallback({
53925
+ chatMessagesRef,
53926
+ isAutoScrolling,
53927
+ lastScrollHeightRef,
53928
+ scrollToBottom,
53929
+ });
53930
+ const activateAutoScroll = react.useCallback(() => {
51820
53931
  setIsAutoScrolling(true);
51821
53932
  scrollToBottom('smooth');
51822
53933
  }, [scrollToBottom]);
51823
- // Disable auto-scroll (useful for programmatic control)
51824
53934
  const disableAutoScroll = react.useCallback(() => {
51825
53935
  setIsAutoScrolling(false);
51826
53936
  }, []);
51827
- // Cleanup timeout on unmount
51828
53937
  react.useEffect(() => {
51829
53938
  return () => {
51830
- if (scrollTimeoutRef.current) {
51831
- clearTimeout(scrollTimeoutRef.current);
51832
- }
51833
- if (messagesChangeTimeoutRef.current) {
51834
- clearTimeout(messagesChangeTimeoutRef.current);
51835
- }
53939
+ clearScheduledTimeout(scrollTimeoutRef);
53940
+ clearScheduledTimeout(messagesChangeTimeoutRef);
51836
53941
  };
51837
53942
  }, []);
51838
53943
  return {
@@ -51840,8 +53945,8 @@
51840
53945
  chatMessagesRef: chatMessagesRefCallback,
51841
53946
  handleScroll,
51842
53947
  handleMessagesChange,
51843
- scrollToBottom: handleScrollToBottomClick,
51844
- enableAutoScroll,
53948
+ scrollToBottom: activateAutoScroll,
53949
+ enableAutoScroll: activateAutoScroll,
51845
53950
  disableAutoScroll,
51846
53951
  isMobile,
51847
53952
  };