@promptbook/components 0.112.0-61 → 0.112.0-63
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/index.es.js +2880 -775
- package/esm/index.es.js.map +1 -1
- package/esm/src/_packages/components.index.d.ts +4 -0
- package/esm/src/_packages/core.index.d.ts +2 -0
- package/esm/src/_packages/types.index.d.ts +2 -0
- package/esm/src/book-components/Chat/Chat/TeamToolCallModalContent.d.ts +0 -2
- package/esm/src/book-components/Chat/Chat/renderTimeoutToolCallDetails.d.ts +7 -1
- package/esm/src/book-components/Chat/Chat/useChatInputAreaComposer.d.ts +1 -1
- package/esm/src/book-components/Chat/Chat/useChatInputAreaDictation.d.ts +2 -2
- package/esm/src/book-components/Chat/hooks/useChatAutoScroll.d.ts +6 -3
- package/esm/src/book-components/Chat/types/ChatMessage.d.ts +34 -0
- package/esm/src/cli/cli-commands/agent/agentProjectPaths.d.ts +54 -0
- package/esm/src/cli/cli-commands/agent/agentRunCliOptions.d.ts +13 -0
- package/esm/src/cli/cli-commands/agent/init.d.ts +10 -0
- package/esm/src/cli/cli-commands/agent/initializeAgentProjectConfiguration.d.ts +21 -0
- package/esm/src/cli/cli-commands/agent/printAgentInitializationSummary.d.ts +7 -0
- package/esm/src/cli/cli-commands/agent/run.d.ts +10 -0
- package/esm/src/cli/cli-commands/agent/run.test.d.ts +1 -0
- package/esm/src/cli/cli-commands/agent/tick.d.ts +10 -0
- package/esm/src/cli/cli-commands/agent.d.ts +15 -0
- package/esm/src/cli/cli-commands/common/promptRunnerCliOptions.d.ts +86 -0
- package/esm/src/commitments/KNOWLEDGE/KNOWLEDGE.d.ts +11 -0
- package/esm/src/commitments/KNOWLEDGE/KNOWLEDGE.test.d.ts +1 -0
- package/esm/src/commitments/_common/toolRuntimeContext.d.ts +6 -0
- package/esm/src/commitments/index.d.ts +2 -1
- package/esm/src/llm-providers/openai/OpenAiAgentKitExecutionTools.d.ts +4 -2
- package/esm/src/llm-providers/openai/OpenAiAgentKitExecutionToolsOptions.d.ts +9 -0
- package/esm/src/version.d.ts +1 -1
- package/package.json +1 -1
- package/umd/index.umd.js +2880 -775
- package/umd/index.umd.js.map +1 -1
- package/umd/src/_packages/components.index.d.ts +4 -0
- package/umd/src/_packages/core.index.d.ts +2 -0
- package/umd/src/_packages/types.index.d.ts +2 -0
- package/umd/src/book-components/Chat/Chat/TeamToolCallModalContent.d.ts +0 -2
- package/umd/src/book-components/Chat/Chat/renderTimeoutToolCallDetails.d.ts +7 -1
- package/umd/src/book-components/Chat/Chat/useChatInputAreaComposer.d.ts +1 -1
- package/umd/src/book-components/Chat/Chat/useChatInputAreaDictation.d.ts +2 -2
- package/umd/src/book-components/Chat/hooks/useChatAutoScroll.d.ts +6 -3
- package/umd/src/book-components/Chat/types/ChatMessage.d.ts +34 -0
- package/umd/src/cli/cli-commands/agent/agentProjectPaths.d.ts +54 -0
- package/umd/src/cli/cli-commands/agent/agentRunCliOptions.d.ts +13 -0
- package/umd/src/cli/cli-commands/agent/init.d.ts +10 -0
- package/umd/src/cli/cli-commands/agent/initializeAgentProjectConfiguration.d.ts +21 -0
- package/umd/src/cli/cli-commands/agent/printAgentInitializationSummary.d.ts +7 -0
- package/umd/src/cli/cli-commands/agent/run.d.ts +10 -0
- package/umd/src/cli/cli-commands/agent/run.test.d.ts +1 -0
- package/umd/src/cli/cli-commands/agent/tick.d.ts +10 -0
- package/umd/src/cli/cli-commands/agent.d.ts +15 -0
- package/umd/src/cli/cli-commands/common/promptRunnerCliOptions.d.ts +86 -0
- package/umd/src/commitments/KNOWLEDGE/KNOWLEDGE.d.ts +11 -0
- package/umd/src/commitments/KNOWLEDGE/KNOWLEDGE.test.d.ts +1 -0
- package/umd/src/commitments/_common/toolRuntimeContext.d.ts +6 -0
- package/umd/src/commitments/index.d.ts +2 -1
- package/umd/src/llm-providers/openai/OpenAiAgentKitExecutionTools.d.ts +4 -2
- package/umd/src/llm-providers/openai/OpenAiAgentKitExecutionToolsOptions.d.ts +9 -0
- 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-
|
|
33
|
+
const PROMPTBOOK_ENGINE_VERSION = '0.112.0-63';
|
|
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
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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
|
-
['
|
|
23800
|
-
['
|
|
23801
|
-
['
|
|
23802
|
-
['
|
|
23803
|
-
['
|
|
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 =
|
|
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
|
-
*
|
|
25807
|
+
* Internal upload item state shared by Monaco upload helpers.
|
|
25081
25808
|
*
|
|
25082
25809
|
* @private function of BookEditorMonaco
|
|
25083
25810
|
*/
|
|
25084
|
-
function
|
|
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
|
-
|
|
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
|
-
}, [
|
|
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
|
|
25155
|
-
if (!
|
|
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
|
|
25179
|
-
|
|
25180
|
-
|
|
25181
|
-
|
|
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
|
-
|
|
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.
|
|
25323
|
-
|
|
25324
|
-
|
|
25325
|
-
|
|
25326
|
-
|
|
25327
|
-
|
|
25328
|
-
|
|
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
|
|
25338
|
-
if (
|
|
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
|
-
|
|
25351
|
-
|
|
25352
|
-
|
|
25353
|
-
|
|
25354
|
-
|
|
25355
|
-
|
|
25356
|
-
|
|
25357
|
-
|
|
25358
|
-
|
|
25359
|
-
|
|
25360
|
-
|
|
25361
|
-
|
|
25362
|
-
|
|
25363
|
-
|
|
25364
|
-
|
|
25365
|
-
|
|
25366
|
-
|
|
25367
|
-
|
|
25368
|
-
|
|
25369
|
-
registerUploadPlaceholderResources
|
|
25370
|
-
setUploadItems
|
|
25371
|
-
|
|
25372
|
-
|
|
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
|
-
|
|
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
|
-
}, [
|
|
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
|
|
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
|
-
*
|
|
29327
|
+
* Returns the required `onMessage` callback or throws when the composer is misconfigured.
|
|
28471
29328
|
*
|
|
28472
|
-
* @private function of
|
|
29329
|
+
* @private function of `useChatInputAreaComposer`
|
|
28473
29330
|
*/
|
|
28474
|
-
function
|
|
28475
|
-
|
|
28476
|
-
|
|
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
|
-
|
|
28500
|
-
|
|
28501
|
-
|
|
28502
|
-
|
|
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
|
-
|
|
28518
|
-
|
|
28519
|
-
|
|
28520
|
-
|
|
28521
|
-
|
|
28522
|
-
|
|
28523
|
-
|
|
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 (
|
|
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
|
|
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
|
|
28595
|
-
if (event.key
|
|
28596
|
-
|
|
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
|
-
|
|
28611
|
-
|
|
28612
|
-
|
|
28613
|
-
|
|
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
|
-
*
|
|
30089
|
+
* Resolves the current textarea selection with a stable fallback to the message length.
|
|
29133
30090
|
*
|
|
29134
|
-
* @private function of
|
|
30091
|
+
* @private function of `useChatInputAreaDictation`
|
|
29135
30092
|
*/
|
|
29136
|
-
function
|
|
29137
|
-
|
|
29138
|
-
const
|
|
29139
|
-
const
|
|
29140
|
-
|
|
29141
|
-
|
|
29142
|
-
|
|
29143
|
-
|
|
29144
|
-
|
|
29145
|
-
|
|
29146
|
-
|
|
29147
|
-
|
|
29148
|
-
|
|
29149
|
-
|
|
29150
|
-
|
|
29151
|
-
const
|
|
29152
|
-
|
|
29153
|
-
|
|
29154
|
-
|
|
29155
|
-
|
|
29156
|
-
|
|
29157
|
-
}
|
|
29158
|
-
|
|
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
|
-
|
|
29169
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
}, [
|
|
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
|
-
|
|
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
|
|
30365
|
+
whisperMode,
|
|
29283
30366
|
});
|
|
29284
|
-
}, [
|
|
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
|
-
}, [
|
|
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
|
-
}, [
|
|
30434
|
+
}, [
|
|
30435
|
+
applyMessageContent,
|
|
30436
|
+
dictationChunks,
|
|
30437
|
+
focusTextareaSelection,
|
|
30438
|
+
setDictationChunks,
|
|
30439
|
+
setDictationEditableChunk,
|
|
30440
|
+
setDictationLastFinalChunk,
|
|
30441
|
+
]);
|
|
29321
30442
|
const handleApplyCorrection = react.useCallback(() => {
|
|
29322
|
-
const correctedChunk =
|
|
29323
|
-
|
|
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,
|
|
30447
|
+
const nextMessageContent = replaceLastOccurrence(messageContentRef.current, dictationLastFinalChunk, correctedChunk);
|
|
29328
30448
|
applyMessageContent(nextMessageContent);
|
|
29329
30449
|
setDictationLastFinalChunk(correctedChunk);
|
|
29330
|
-
setDictationChunks((previousChunks) =>
|
|
29331
|
-
|
|
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
|
-
|
|
29355
|
-
|
|
29356
|
-
|
|
29357
|
-
|
|
29358
|
-
|
|
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
|
-
|
|
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 =
|
|
29378
|
-
|
|
29379
|
-
|
|
29380
|
-
|
|
29381
|
-
|
|
29382
|
-
|
|
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
|
|
29444
|
-
const
|
|
29445
|
-
|
|
29446
|
-
|
|
29447
|
-
|
|
29448
|
-
|
|
29449
|
-
|
|
29450
|
-
|
|
29451
|
-
|
|
29452
|
-
|
|
29453
|
-
|
|
29454
|
-
|
|
29455
|
-
|
|
29456
|
-
|
|
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
|
|
41227
|
-
const { name, instructions, knowledgeSources, tools,
|
|
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
|
-
|
|
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,
|
|
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
|
|
48826
|
-
|
|
48827
|
-
|
|
48828
|
-
|
|
48829
|
-
|
|
48830
|
-
|
|
48831
|
-
|
|
48832
|
-
|
|
48833
|
-
|
|
48834
|
-
|
|
48835
|
-
|
|
48836
|
-
|
|
48837
|
-
|
|
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 }),
|
|
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
|
|
50195
|
-
|
|
50196
|
-
|
|
50197
|
-
|
|
50198
|
-
|
|
50199
|
-
|
|
50200
|
-
: (
|
|
50201
|
-
|
|
50202
|
-
|
|
50203
|
-
|
|
50204
|
-
|
|
50205
|
-
|
|
50206
|
-
|
|
50207
|
-
|
|
50208
|
-
|
|
50209
|
-
|
|
50210
|
-
|
|
50211
|
-
|
|
50212
|
-
|
|
50213
|
-
: null
|
|
50214
|
-
|
|
50215
|
-
|
|
50216
|
-
|
|
50217
|
-
|
|
50218
|
-
|
|
50219
|
-
|
|
50220
|
-
|
|
50221
|
-
|
|
50222
|
-
|
|
50223
|
-
|
|
50224
|
-
|
|
50225
|
-
|
|
50226
|
-
|
|
50227
|
-
|
|
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
|
-
}, [
|
|
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
|
-
|
|
50915
|
-
|
|
50916
|
-
|
|
50917
|
-
|
|
50918
|
-
|
|
50919
|
-
|
|
50920
|
-
|
|
50921
|
-
|
|
50922
|
-
|
|
50923
|
-
|
|
50924
|
-
|
|
50925
|
-
|
|
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
|
-
*
|
|
52847
|
+
* Default label used when the agent name is unavailable.
|
|
51127
52848
|
*
|
|
51128
|
-
* @
|
|
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
|
-
|
|
51133
|
-
|
|
51134
|
-
|
|
51135
|
-
|
|
51136
|
-
|
|
51137
|
-
|
|
51138
|
-
|
|
51139
|
-
|
|
51140
|
-
|
|
51141
|
-
|
|
51142
|
-
|
|
51143
|
-
|
|
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
|
|
51147
|
-
content: entry.content
|
|
52895
|
+
sender: resolveTeamConversationSender(entry),
|
|
52896
|
+
content: entry.content,
|
|
51148
52897
|
isComplete: true,
|
|
51149
|
-
}
|
|
51150
|
-
|
|
51151
|
-
|
|
51152
|
-
|
|
51153
|
-
|
|
51154
|
-
|
|
51155
|
-
|
|
51156
|
-
|
|
51157
|
-
|
|
51158
|
-
|
|
51159
|
-
|
|
51160
|
-
|
|
51161
|
-
|
|
51162
|
-
|
|
51163
|
-
|
|
51164
|
-
|
|
51165
|
-
|
|
51166
|
-
|
|
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
|
-
|
|
51171
|
-
|
|
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: (
|
|
52969
|
+
label: (_b = teamResult.teammate) === null || _b === void 0 ? void 0 : _b.label,
|
|
51177
52970
|
})
|
|
51178
|
-
: { label:
|
|
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
|
|
51188
|
-
|
|
51189
|
-
|
|
51190
|
-
|
|
51191
|
-
|
|
51192
|
-
|
|
51193
|
-
|
|
51194
|
-
|
|
51195
|
-
|
|
51196
|
-
|
|
51197
|
-
|
|
51198
|
-
|
|
51199
|
-
|
|
51200
|
-
|
|
51201
|
-
|
|
51202
|
-
|
|
51203
|
-
|
|
51204
|
-
|
|
51205
|
-
|
|
51206
|
-
|
|
51207
|
-
|
|
51208
|
-
|
|
51209
|
-
|
|
51210
|
-
|
|
51211
|
-
|
|
51212
|
-
|
|
51213
|
-
|
|
51214
|
-
|
|
51215
|
-
|
|
51216
|
-
|
|
51217
|
-
|
|
51218
|
-
|
|
51219
|
-
|
|
51220
|
-
|
|
51221
|
-
|
|
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
|
-
|
|
51697
|
-
react.
|
|
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
|
-
|
|
51738
|
-
|
|
51739
|
-
|
|
51740
|
-
|
|
51741
|
-
|
|
51742
|
-
|
|
51743
|
-
|
|
51744
|
-
|
|
51745
|
-
|
|
51746
|
-
|
|
51747
|
-
|
|
51748
|
-
|
|
51749
|
-
|
|
51750
|
-
|
|
51751
|
-
|
|
51752
|
-
|
|
51753
|
-
|
|
51754
|
-
|
|
51755
|
-
|
|
51756
|
-
|
|
51757
|
-
|
|
51758
|
-
|
|
51759
|
-
|
|
51760
|
-
const
|
|
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
|
-
|
|
51831
|
-
|
|
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:
|
|
51844
|
-
enableAutoScroll,
|
|
53948
|
+
scrollToBottom: activateAutoScroll,
|
|
53949
|
+
enableAutoScroll: activateAutoScroll,
|
|
51845
53950
|
disableAutoScroll,
|
|
51846
53951
|
isMobile,
|
|
51847
53952
|
};
|