@promptbook/components 0.112.0-62 → 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/esm/index.es.js
CHANGED
|
@@ -40,7 +40,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
|
|
|
40
40
|
* @generated
|
|
41
41
|
* @see https://github.com/webgptorg/promptbook
|
|
42
42
|
*/
|
|
43
|
-
const PROMPTBOOK_ENGINE_VERSION = '0.112.0-
|
|
43
|
+
const PROMPTBOOK_ENGINE_VERSION = '0.112.0-63';
|
|
44
44
|
/**
|
|
45
45
|
* TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
|
|
46
46
|
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
@@ -10898,6 +10898,18 @@ function parseDataUrlKnowledgeSource(source) {
|
|
|
10898
10898
|
}
|
|
10899
10899
|
// Note: [💞] Ignore a discrepancy between file name and entity name
|
|
10900
10900
|
|
|
10901
|
+
/**
|
|
10902
|
+
* Name of the tool used by agents to search configured `KNOWLEDGE` sources.
|
|
10903
|
+
*
|
|
10904
|
+
* @public exported from `@promptbook/core`
|
|
10905
|
+
*/
|
|
10906
|
+
const KNOWLEDGE_SEARCH_TOOL_NAME = 'knowledge_search';
|
|
10907
|
+
/**
|
|
10908
|
+
* Title of the system-message section generated for `KNOWLEDGE` commitments.
|
|
10909
|
+
*
|
|
10910
|
+
* @private constant of `KnowledgeCommitmentDefinition`
|
|
10911
|
+
*/
|
|
10912
|
+
const KNOWLEDGE_SEARCH_SYSTEM_SECTION_TITLE = 'Knowledge Search';
|
|
10901
10913
|
/**
|
|
10902
10914
|
* KNOWLEDGE commitment definition
|
|
10903
10915
|
*
|
|
@@ -11019,9 +11031,17 @@ class KnowledgeCommitmentDefinition extends BaseCommitmentDefinition {
|
|
|
11019
11031
|
knowledgeInfoEntries.push(`Knowledge Source Inline: ${inlineSource.filename} (derived from inline content and processed for retrieval during chat)`);
|
|
11020
11032
|
}
|
|
11021
11033
|
if (knowledgeInfoEntries.length === 0) {
|
|
11022
|
-
return nextRequirements;
|
|
11034
|
+
return addKnowledgeSearchToolAndSystemSection(nextRequirements);
|
|
11023
11035
|
}
|
|
11024
|
-
return
|
|
11036
|
+
return addKnowledgeSearchToolAndSystemSection(nextRequirements);
|
|
11037
|
+
}
|
|
11038
|
+
/**
|
|
11039
|
+
* Gets human-readable titles for tool functions provided by this commitment.
|
|
11040
|
+
*/
|
|
11041
|
+
getToolTitles() {
|
|
11042
|
+
return {
|
|
11043
|
+
[KNOWLEDGE_SEARCH_TOOL_NAME]: 'Knowledge search',
|
|
11044
|
+
};
|
|
11025
11045
|
}
|
|
11026
11046
|
}
|
|
11027
11047
|
/**
|
|
@@ -11035,6 +11055,128 @@ function hasMeaningfulNonUrlText(content, urls) {
|
|
|
11035
11055
|
const significantText = contentWithoutUrls.replace(/[\s.,!?;:'"`()[\]{}<>/-]+/g, '');
|
|
11036
11056
|
return significantText.length > 0;
|
|
11037
11057
|
}
|
|
11058
|
+
/**
|
|
11059
|
+
* Adds the shared `knowledge_search` tool definition and the consolidated system-message section.
|
|
11060
|
+
*
|
|
11061
|
+
* @param requirements - Requirements after one `KNOWLEDGE` commitment was applied.
|
|
11062
|
+
* @returns Requirements with the knowledge search instructions and tool definition.
|
|
11063
|
+
*
|
|
11064
|
+
* @private internal utility of `KnowledgeCommitmentDefinition`
|
|
11065
|
+
*/
|
|
11066
|
+
function addKnowledgeSearchToolAndSystemSection(requirements) {
|
|
11067
|
+
const nextRequirements = addKnowledgeSearchTool(requirements);
|
|
11068
|
+
const section = createKnowledgeSearchSystemSection(nextRequirements);
|
|
11069
|
+
const sectionHeader = `## ${KNOWLEDGE_SEARCH_SYSTEM_SECTION_TITLE}`;
|
|
11070
|
+
if (nextRequirements.systemMessage.includes(sectionHeader)) {
|
|
11071
|
+
return {
|
|
11072
|
+
...nextRequirements,
|
|
11073
|
+
systemMessage: nextRequirements.systemMessage.replace(new RegExp(`## ${KNOWLEDGE_SEARCH_SYSTEM_SECTION_TITLE.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[\\s\\S]*?(?=\\n\\n##|$)`), section),
|
|
11074
|
+
};
|
|
11075
|
+
}
|
|
11076
|
+
return {
|
|
11077
|
+
...nextRequirements,
|
|
11078
|
+
systemMessage: nextRequirements.systemMessage.trim()
|
|
11079
|
+
? `${nextRequirements.systemMessage}\n\n${section}`
|
|
11080
|
+
: section,
|
|
11081
|
+
};
|
|
11082
|
+
}
|
|
11083
|
+
/**
|
|
11084
|
+
* Adds the `knowledge_search` model tool when it is not already present.
|
|
11085
|
+
*
|
|
11086
|
+
* @param requirements - Current model requirements.
|
|
11087
|
+
* @returns Requirements with the tool definition available to the model.
|
|
11088
|
+
*
|
|
11089
|
+
* @private internal utility of `KnowledgeCommitmentDefinition`
|
|
11090
|
+
*/
|
|
11091
|
+
function addKnowledgeSearchTool(requirements) {
|
|
11092
|
+
const existingTools = requirements.tools || [];
|
|
11093
|
+
if (existingTools.some((tool) => tool.name === KNOWLEDGE_SEARCH_TOOL_NAME)) {
|
|
11094
|
+
return requirements;
|
|
11095
|
+
}
|
|
11096
|
+
return {
|
|
11097
|
+
...requirements,
|
|
11098
|
+
tools: [
|
|
11099
|
+
...existingTools,
|
|
11100
|
+
{
|
|
11101
|
+
name: KNOWLEDGE_SEARCH_TOOL_NAME,
|
|
11102
|
+
description: spaceTrim$1(`
|
|
11103
|
+
Search the agent's configured knowledge sources and return relevant excerpts with citation ids.
|
|
11104
|
+
Use this before answering questions that may depend on the agent's KNOWLEDGE commitments.
|
|
11105
|
+
`),
|
|
11106
|
+
parameters: {
|
|
11107
|
+
type: 'object',
|
|
11108
|
+
properties: {
|
|
11109
|
+
query: {
|
|
11110
|
+
type: 'string',
|
|
11111
|
+
description: 'The natural-language search query for the knowledge base.',
|
|
11112
|
+
},
|
|
11113
|
+
limit: {
|
|
11114
|
+
type: 'integer',
|
|
11115
|
+
description: 'Maximum number of matching source excerpts to return.',
|
|
11116
|
+
},
|
|
11117
|
+
},
|
|
11118
|
+
required: ['query'],
|
|
11119
|
+
},
|
|
11120
|
+
},
|
|
11121
|
+
],
|
|
11122
|
+
};
|
|
11123
|
+
}
|
|
11124
|
+
/**
|
|
11125
|
+
* Creates the model-facing system-message section for knowledge search.
|
|
11126
|
+
*
|
|
11127
|
+
* @param requirements - Current model requirements.
|
|
11128
|
+
* @returns Markdown system-message section.
|
|
11129
|
+
*
|
|
11130
|
+
* @private internal utility of `KnowledgeCommitmentDefinition`
|
|
11131
|
+
*/
|
|
11132
|
+
function createKnowledgeSearchSystemSection(requirements) {
|
|
11133
|
+
const sourceEntries = createKnowledgeSourceSystemEntries(requirements);
|
|
11134
|
+
const sourceList = sourceEntries.length > 0 ? sourceEntries.map((entry) => `- ${entry}`).join('\n') : '- None';
|
|
11135
|
+
return spaceTrim$1(`
|
|
11136
|
+
## ${KNOWLEDGE_SEARCH_SYSTEM_SECTION_TITLE}
|
|
11137
|
+
|
|
11138
|
+
- Use \`${KNOWLEDGE_SEARCH_TOOL_NAME}\` to search the configured knowledge sources before answering questions that depend on this agent's knowledge base.
|
|
11139
|
+
- Base source-backed factual answers on the returned excerpts.
|
|
11140
|
+
- When you use a returned excerpt, include its citation marker in the answer body, for example \`[0:0]\`.
|
|
11141
|
+
- If the search returns no relevant information, say that the knowledge base did not contain the answer instead of inventing it.
|
|
11142
|
+
|
|
11143
|
+
Configured knowledge sources:
|
|
11144
|
+
${sourceList}
|
|
11145
|
+
`);
|
|
11146
|
+
}
|
|
11147
|
+
/**
|
|
11148
|
+
* Builds a stable list of configured knowledge sources for system-message diagnostics.
|
|
11149
|
+
*
|
|
11150
|
+
* @param requirements - Current model requirements.
|
|
11151
|
+
* @returns Human-readable source entries.
|
|
11152
|
+
*
|
|
11153
|
+
* @private internal utility of `KnowledgeCommitmentDefinition`
|
|
11154
|
+
*/
|
|
11155
|
+
function createKnowledgeSourceSystemEntries(requirements) {
|
|
11156
|
+
var _a;
|
|
11157
|
+
const entries = [];
|
|
11158
|
+
const seenEntries = new Set();
|
|
11159
|
+
for (const source of requirements.knowledgeSources || []) {
|
|
11160
|
+
const entry = `Source URL: ${source} (processed for retrieval during chat)`;
|
|
11161
|
+
if (seenEntries.has(entry)) {
|
|
11162
|
+
continue;
|
|
11163
|
+
}
|
|
11164
|
+
seenEntries.add(entry);
|
|
11165
|
+
entries.push(entry);
|
|
11166
|
+
}
|
|
11167
|
+
const inlineSources = (((_a = requirements._metadata) === null || _a === void 0 ? void 0 : _a.inlineKnowledgeSources) || [])
|
|
11168
|
+
.map((source) => source.filename)
|
|
11169
|
+
.filter(Boolean);
|
|
11170
|
+
for (const filename of inlineSources) {
|
|
11171
|
+
const entry = `Knowledge Source Inline: ${filename} (Inline source: processed for retrieval during chat)`;
|
|
11172
|
+
if (seenEntries.has(entry)) {
|
|
11173
|
+
continue;
|
|
11174
|
+
}
|
|
11175
|
+
seenEntries.add(entry);
|
|
11176
|
+
entries.push(entry);
|
|
11177
|
+
}
|
|
11178
|
+
return entries;
|
|
11179
|
+
}
|
|
11038
11180
|
|
|
11039
11181
|
/**
|
|
11040
11182
|
* LANGUAGE commitment definition
|
|
@@ -15637,7 +15779,7 @@ function resolveUseCalendarToolRuntime(args) {
|
|
|
15637
15779
|
const runtimeContext = (readToolRuntimeContextFromToolArgs(args) ||
|
|
15638
15780
|
{});
|
|
15639
15781
|
const configuredCalendars = normalizeConfiguredCalendars$1((_a = runtimeContext.calendars) === null || _a === void 0 ? void 0 : _a.connections);
|
|
15640
|
-
const calendarArgument = normalizeOptionalText$
|
|
15782
|
+
const calendarArgument = normalizeOptionalText$2(args.calendarUrl);
|
|
15641
15783
|
let calendarReference = null;
|
|
15642
15784
|
if (calendarArgument) {
|
|
15643
15785
|
calendarReference = parseGoogleCalendarReference(calendarArgument);
|
|
@@ -15657,7 +15799,7 @@ function resolveUseCalendarToolRuntime(args) {
|
|
|
15657
15799
|
if (!calendarReference) {
|
|
15658
15800
|
throw new Error('Calendar is required but was not resolved.');
|
|
15659
15801
|
}
|
|
15660
|
-
const accessToken = normalizeOptionalText$
|
|
15802
|
+
const accessToken = normalizeOptionalText$2((_b = runtimeContext.calendars) === null || _b === void 0 ? void 0 : _b.googleAccessToken) || '';
|
|
15661
15803
|
if (!accessToken) {
|
|
15662
15804
|
throw new CalendarWalletCredentialRequiredError({
|
|
15663
15805
|
calendarReference,
|
|
@@ -15690,7 +15832,7 @@ function normalizeConfiguredCalendars$1(rawCalendars) {
|
|
|
15690
15832
|
continue;
|
|
15691
15833
|
}
|
|
15692
15834
|
const calendar = rawCalendar;
|
|
15693
|
-
const rawUrl = normalizeOptionalText$
|
|
15835
|
+
const rawUrl = normalizeOptionalText$2(calendar.url);
|
|
15694
15836
|
if (!rawUrl) {
|
|
15695
15837
|
continue;
|
|
15696
15838
|
}
|
|
@@ -15741,7 +15883,7 @@ function createCalendarWalletCredentialRequiredResult(error) {
|
|
|
15741
15883
|
*
|
|
15742
15884
|
* @private function of resolveUseCalendarToolRuntimeOrWalletCredentialResult
|
|
15743
15885
|
*/
|
|
15744
|
-
function normalizeOptionalText$
|
|
15886
|
+
function normalizeOptionalText$2(value) {
|
|
15745
15887
|
if (typeof value !== 'string') {
|
|
15746
15888
|
return undefined;
|
|
15747
15889
|
}
|
|
@@ -15773,13 +15915,13 @@ function createUseCalendarToolFunctions() {
|
|
|
15773
15915
|
async [UseCalendarToolNames.listEvents](args) {
|
|
15774
15916
|
return withUseCalendarRuntime(args, async ({ calendarReference, accessToken }) => {
|
|
15775
15917
|
const query = {};
|
|
15776
|
-
if (normalizeOptionalText(args.timeMin)) {
|
|
15918
|
+
if (normalizeOptionalText$1(args.timeMin)) {
|
|
15777
15919
|
query.timeMin = args.timeMin.trim();
|
|
15778
15920
|
}
|
|
15779
|
-
if (normalizeOptionalText(args.timeMax)) {
|
|
15921
|
+
if (normalizeOptionalText$1(args.timeMax)) {
|
|
15780
15922
|
query.timeMax = args.timeMax.trim();
|
|
15781
15923
|
}
|
|
15782
|
-
if (normalizeOptionalText(args.query)) {
|
|
15924
|
+
if (normalizeOptionalText$1(args.query)) {
|
|
15783
15925
|
query.q = args.query.trim();
|
|
15784
15926
|
}
|
|
15785
15927
|
if (typeof args.maxResults === 'number' && Number.isFinite(args.maxResults) && args.maxResults > 0) {
|
|
@@ -15791,7 +15933,7 @@ function createUseCalendarToolFunctions() {
|
|
|
15791
15933
|
if (args.orderBy === 'startTime' || args.orderBy === 'updated') {
|
|
15792
15934
|
query.orderBy = args.orderBy;
|
|
15793
15935
|
}
|
|
15794
|
-
if (normalizeOptionalText(args.timeZone)) {
|
|
15936
|
+
if (normalizeOptionalText$1(args.timeZone)) {
|
|
15795
15937
|
query.timeZone = args.timeZone.trim();
|
|
15796
15938
|
}
|
|
15797
15939
|
const payload = await callGoogleCalendarApi(accessToken, {
|
|
@@ -15829,11 +15971,11 @@ function createUseCalendarToolFunctions() {
|
|
|
15829
15971
|
return withUseCalendarRuntime(args, async ({ calendarReference, accessToken }) => {
|
|
15830
15972
|
const requestBody = createGoogleCalendarEventPayload({
|
|
15831
15973
|
summary: normalizeRequiredText(args.summary, 'summary'),
|
|
15832
|
-
description: normalizeOptionalText(args.description),
|
|
15833
|
-
location: normalizeOptionalText(args.location),
|
|
15974
|
+
description: normalizeOptionalText$1(args.description),
|
|
15975
|
+
location: normalizeOptionalText$1(args.location),
|
|
15834
15976
|
start: normalizeRequiredText(args.start, 'start'),
|
|
15835
15977
|
end: normalizeRequiredText(args.end, 'end'),
|
|
15836
|
-
timeZone: normalizeOptionalText(args.timeZone),
|
|
15978
|
+
timeZone: normalizeOptionalText$1(args.timeZone),
|
|
15837
15979
|
attendees: normalizeAttendees(args.attendees),
|
|
15838
15980
|
reminderMinutes: normalizeReminderMinutes(args.reminderMinutes),
|
|
15839
15981
|
});
|
|
@@ -15855,12 +15997,12 @@ function createUseCalendarToolFunctions() {
|
|
|
15855
15997
|
return withUseCalendarRuntime(args, async ({ calendarReference, accessToken }) => {
|
|
15856
15998
|
const eventId = normalizeRequiredText(args.eventId, 'eventId');
|
|
15857
15999
|
const requestBody = createGoogleCalendarEventPayload({
|
|
15858
|
-
summary: normalizeOptionalText(args.summary),
|
|
15859
|
-
description: normalizeOptionalText(args.description),
|
|
15860
|
-
location: normalizeOptionalText(args.location),
|
|
15861
|
-
start: normalizeOptionalText(args.start),
|
|
15862
|
-
end: normalizeOptionalText(args.end),
|
|
15863
|
-
timeZone: normalizeOptionalText(args.timeZone),
|
|
16000
|
+
summary: normalizeOptionalText$1(args.summary),
|
|
16001
|
+
description: normalizeOptionalText$1(args.description),
|
|
16002
|
+
location: normalizeOptionalText$1(args.location),
|
|
16003
|
+
start: normalizeOptionalText$1(args.start),
|
|
16004
|
+
end: normalizeOptionalText$1(args.end),
|
|
16005
|
+
timeZone: normalizeOptionalText$1(args.timeZone),
|
|
15864
16006
|
attendees: normalizeAttendees(args.attendees),
|
|
15865
16007
|
reminderMinutes: normalizeReminderMinutes(args.reminderMinutes),
|
|
15866
16008
|
});
|
|
@@ -15907,7 +16049,7 @@ function createUseCalendarToolFunctions() {
|
|
|
15907
16049
|
path: `/calendars/${encodeGoogleCalendarId(calendarReference.calendarId)}/events/${encodeURIComponent(eventId)}`,
|
|
15908
16050
|
});
|
|
15909
16051
|
const existingAttendees = ((existingEvent === null || existingEvent === void 0 ? void 0 : existingEvent.attendees) || [])
|
|
15910
|
-
.map((attendee) => normalizeOptionalText(attendee.email))
|
|
16052
|
+
.map((attendee) => normalizeOptionalText$1(attendee.email))
|
|
15911
16053
|
.filter((email) => Boolean(email));
|
|
15912
16054
|
const mergedAttendees = [...new Set([...existingAttendees, ...guests])];
|
|
15913
16055
|
const payload = await callGoogleCalendarApi(accessToken, {
|
|
@@ -15955,7 +16097,7 @@ function encodeGoogleCalendarId(calendarId) {
|
|
|
15955
16097
|
* @private function of createUseCalendarToolFunctions
|
|
15956
16098
|
*/
|
|
15957
16099
|
function normalizeRequiredText(value, fieldName) {
|
|
15958
|
-
const normalizedValue = normalizeOptionalText(value);
|
|
16100
|
+
const normalizedValue = normalizeOptionalText$1(value);
|
|
15959
16101
|
if (!normalizedValue) {
|
|
15960
16102
|
throw new Error(`Tool "${fieldName}" requires non-empty value.`);
|
|
15961
16103
|
}
|
|
@@ -15966,7 +16108,7 @@ function normalizeRequiredText(value, fieldName) {
|
|
|
15966
16108
|
*
|
|
15967
16109
|
* @private function of createUseCalendarToolFunctions
|
|
15968
16110
|
*/
|
|
15969
|
-
function normalizeOptionalText(value) {
|
|
16111
|
+
function normalizeOptionalText$1(value) {
|
|
15970
16112
|
if (typeof value !== 'string') {
|
|
15971
16113
|
return undefined;
|
|
15972
16114
|
}
|
|
@@ -20652,6 +20794,580 @@ class UseUserLocationCommitmentDefinition extends BaseCommitmentDefinition {
|
|
|
20652
20794
|
}
|
|
20653
20795
|
// Note: [💞] Ignore a discrepancy between file name and entity name
|
|
20654
20796
|
|
|
20797
|
+
/**
|
|
20798
|
+
* Names of tools used by the WALLET commitment.
|
|
20799
|
+
*
|
|
20800
|
+
* @private constant of WalletCommitmentDefinition
|
|
20801
|
+
*/
|
|
20802
|
+
const WalletToolNames = {
|
|
20803
|
+
retrieve: 'retrieve_wallet_records',
|
|
20804
|
+
store: 'store_wallet_record',
|
|
20805
|
+
update: 'update_wallet_record',
|
|
20806
|
+
delete: 'delete_wallet_record',
|
|
20807
|
+
request: 'request_wallet_record',
|
|
20808
|
+
};
|
|
20809
|
+
|
|
20810
|
+
/**
|
|
20811
|
+
* Creates WALLET system-message instructions.
|
|
20812
|
+
*
|
|
20813
|
+
* @private function of WalletCommitmentDefinition
|
|
20814
|
+
*/
|
|
20815
|
+
function createWalletSystemMessage(extraInstructions) {
|
|
20816
|
+
return spaceTrim$1((block) => `
|
|
20817
|
+
Wallet:
|
|
20818
|
+
- Use "${WalletToolNames.retrieve}" before authenticated operations.
|
|
20819
|
+
- Use "${WalletToolNames.store}" and "${WalletToolNames.update}" to maintain credentials.
|
|
20820
|
+
- Use "${WalletToolNames.delete}" to remove invalid credentials.
|
|
20821
|
+
- Use "${WalletToolNames.request}" to request missing credentials via UI popup.
|
|
20822
|
+
- Scope records by user (\`isUserScoped\`) and/or by agent (\`isGlobal=false\`) as needed.
|
|
20823
|
+
- Never expose raw credentials in chat responses.
|
|
20824
|
+
${block(extraInstructions)}
|
|
20825
|
+
`);
|
|
20826
|
+
}
|
|
20827
|
+
|
|
20828
|
+
/**
|
|
20829
|
+
* Resolves disabled message for wallet runtime context.
|
|
20830
|
+
*
|
|
20831
|
+
* @private function of WalletCommitmentDefinition
|
|
20832
|
+
*/
|
|
20833
|
+
function resolveWalletDisabledMessage(runtimeContext) {
|
|
20834
|
+
if (runtimeContext.isPrivateMode) {
|
|
20835
|
+
return 'Wallet is disabled because private mode is active.';
|
|
20836
|
+
}
|
|
20837
|
+
if (runtimeContext.isTeamConversation) {
|
|
20838
|
+
return 'Wallet is disabled for TEAM conversations.';
|
|
20839
|
+
}
|
|
20840
|
+
if (!runtimeContext.enabled) {
|
|
20841
|
+
return 'Wallet is disabled for unauthenticated users.';
|
|
20842
|
+
}
|
|
20843
|
+
return null;
|
|
20844
|
+
}
|
|
20845
|
+
/**
|
|
20846
|
+
* Resolves runtime adapter for wallet tools or returns disabled payload when unavailable.
|
|
20847
|
+
*
|
|
20848
|
+
* @private function of WalletCommitmentDefinition
|
|
20849
|
+
*/
|
|
20850
|
+
function getWalletToolRuntimeAdapterOrDisabledResult(action, runtimeContext) {
|
|
20851
|
+
const disabledMessage = resolveWalletDisabledMessage(runtimeContext);
|
|
20852
|
+
if (disabledMessage) {
|
|
20853
|
+
return {
|
|
20854
|
+
adapter: null,
|
|
20855
|
+
disabledResult: {
|
|
20856
|
+
action,
|
|
20857
|
+
status: 'disabled',
|
|
20858
|
+
records: action === 'retrieve' ? [] : undefined,
|
|
20859
|
+
message: disabledMessage,
|
|
20860
|
+
},
|
|
20861
|
+
};
|
|
20862
|
+
}
|
|
20863
|
+
{
|
|
20864
|
+
return {
|
|
20865
|
+
adapter: null,
|
|
20866
|
+
disabledResult: {
|
|
20867
|
+
action,
|
|
20868
|
+
status: 'disabled',
|
|
20869
|
+
records: action === 'retrieve' ? [] : undefined,
|
|
20870
|
+
message: 'Wallet runtime is not available in this environment.',
|
|
20871
|
+
},
|
|
20872
|
+
};
|
|
20873
|
+
}
|
|
20874
|
+
}
|
|
20875
|
+
|
|
20876
|
+
/**
|
|
20877
|
+
* Parses store/update wallet payload.
|
|
20878
|
+
*
|
|
20879
|
+
* @private function of WalletCommitmentDefinition
|
|
20880
|
+
*/
|
|
20881
|
+
function parseWalletPayload(args) {
|
|
20882
|
+
const recordType = parseWalletRecordType(args.recordType);
|
|
20883
|
+
return {
|
|
20884
|
+
recordType,
|
|
20885
|
+
service: parseWalletService(args.service),
|
|
20886
|
+
key: parseWalletKey(args.key),
|
|
20887
|
+
isUserScoped: args.isUserScoped === true,
|
|
20888
|
+
isGlobal: args.isGlobal === true,
|
|
20889
|
+
...parseWalletSecrets({
|
|
20890
|
+
recordType,
|
|
20891
|
+
username: args.username,
|
|
20892
|
+
password: args.password,
|
|
20893
|
+
secret: args.secret,
|
|
20894
|
+
cookies: args.cookies,
|
|
20895
|
+
}),
|
|
20896
|
+
};
|
|
20897
|
+
}
|
|
20898
|
+
/**
|
|
20899
|
+
* Parses text argument and returns trimmed text when available.
|
|
20900
|
+
*
|
|
20901
|
+
* @private function of WalletCommitmentDefinition
|
|
20902
|
+
*/
|
|
20903
|
+
function normalizeOptionalText(value) {
|
|
20904
|
+
if (typeof value !== 'string') {
|
|
20905
|
+
return undefined;
|
|
20906
|
+
}
|
|
20907
|
+
const trimmed = value.trim();
|
|
20908
|
+
return trimmed || undefined;
|
|
20909
|
+
}
|
|
20910
|
+
/**
|
|
20911
|
+
* Parses wallet service argument.
|
|
20912
|
+
*
|
|
20913
|
+
* @private function of WalletCommitmentDefinition
|
|
20914
|
+
*/
|
|
20915
|
+
function parseWalletService(value) {
|
|
20916
|
+
return (normalizeOptionalText(value) || 'generic').toLowerCase();
|
|
20917
|
+
}
|
|
20918
|
+
/**
|
|
20919
|
+
* Parses wallet key argument.
|
|
20920
|
+
*
|
|
20921
|
+
* @private function of WalletCommitmentDefinition
|
|
20922
|
+
*/
|
|
20923
|
+
function parseWalletKey(value) {
|
|
20924
|
+
return normalizeOptionalText(value) || 'default';
|
|
20925
|
+
}
|
|
20926
|
+
/**
|
|
20927
|
+
* Parses one wallet record id argument.
|
|
20928
|
+
*
|
|
20929
|
+
* @private function of WalletCommitmentDefinition
|
|
20930
|
+
*/
|
|
20931
|
+
function parseWalletId(value) {
|
|
20932
|
+
const walletId = normalizeOptionalText(value);
|
|
20933
|
+
if (!walletId) {
|
|
20934
|
+
throw new Error('Wallet id is required.');
|
|
20935
|
+
}
|
|
20936
|
+
return walletId;
|
|
20937
|
+
}
|
|
20938
|
+
/**
|
|
20939
|
+
* Parses wallet record type.
|
|
20940
|
+
*
|
|
20941
|
+
* @private function of WalletCommitmentDefinition
|
|
20942
|
+
*/
|
|
20943
|
+
function parseWalletRecordType(value, fallback) {
|
|
20944
|
+
var _a;
|
|
20945
|
+
const normalizedType = (_a = normalizeOptionalText(value)) === null || _a === void 0 ? void 0 : _a.toUpperCase();
|
|
20946
|
+
if (normalizedType === 'USERNAME_PASSWORD') {
|
|
20947
|
+
return 'USERNAME_PASSWORD';
|
|
20948
|
+
}
|
|
20949
|
+
if (normalizedType === 'SESSION_COOKIE') {
|
|
20950
|
+
return 'SESSION_COOKIE';
|
|
20951
|
+
}
|
|
20952
|
+
if (normalizedType === 'ACCESS_TOKEN') {
|
|
20953
|
+
return 'ACCESS_TOKEN';
|
|
20954
|
+
}
|
|
20955
|
+
if (fallback) {
|
|
20956
|
+
return fallback;
|
|
20957
|
+
}
|
|
20958
|
+
throw new Error('Unsupported wallet recordType. Expected one of: USERNAME_PASSWORD, SESSION_COOKIE, ACCESS_TOKEN.');
|
|
20959
|
+
}
|
|
20960
|
+
/**
|
|
20961
|
+
* Parses wallet secret fields according to record type.
|
|
20962
|
+
*
|
|
20963
|
+
* @private function of WalletCommitmentDefinition
|
|
20964
|
+
*/
|
|
20965
|
+
function parseWalletSecrets(args) {
|
|
20966
|
+
const username = normalizeOptionalText(args.username);
|
|
20967
|
+
const password = normalizeOptionalText(args.password);
|
|
20968
|
+
const secret = normalizeOptionalText(args.secret);
|
|
20969
|
+
const cookies = normalizeOptionalText(args.cookies);
|
|
20970
|
+
if (args.recordType === 'USERNAME_PASSWORD') {
|
|
20971
|
+
if (!username || !password) {
|
|
20972
|
+
throw new Error('Both username and password are required for USERNAME_PASSWORD.');
|
|
20973
|
+
}
|
|
20974
|
+
return { username, password };
|
|
20975
|
+
}
|
|
20976
|
+
if (args.recordType === 'SESSION_COOKIE') {
|
|
20977
|
+
if (!cookies) {
|
|
20978
|
+
throw new Error('Cookies are required for SESSION_COOKIE.');
|
|
20979
|
+
}
|
|
20980
|
+
return { cookies };
|
|
20981
|
+
}
|
|
20982
|
+
if (!secret) {
|
|
20983
|
+
throw new Error('Secret is required for ACCESS_TOKEN.');
|
|
20984
|
+
}
|
|
20985
|
+
return { secret };
|
|
20986
|
+
}
|
|
20987
|
+
/**
|
|
20988
|
+
* Collection of WALLET tool argument parsers.
|
|
20989
|
+
*
|
|
20990
|
+
* @private function of WalletCommitmentDefinition
|
|
20991
|
+
*/
|
|
20992
|
+
const parseWalletToolArgs = {
|
|
20993
|
+
/**
|
|
20994
|
+
* Parses retrieve arguments.
|
|
20995
|
+
*/
|
|
20996
|
+
retrieve(args) {
|
|
20997
|
+
const limit = typeof args.limit === 'number' && Number.isFinite(args.limit) ? Math.floor(args.limit) : undefined;
|
|
20998
|
+
return {
|
|
20999
|
+
query: normalizeOptionalText(args.query),
|
|
21000
|
+
recordType: normalizeOptionalText(args.recordType) ? parseWalletRecordType(args.recordType) : undefined,
|
|
21001
|
+
service: normalizeOptionalText(args.service) ? parseWalletService(args.service) : undefined,
|
|
21002
|
+
key: normalizeOptionalText(args.key) ? parseWalletKey(args.key) : undefined,
|
|
21003
|
+
limit: limit && limit > 0 ? Math.min(limit, 20) : undefined,
|
|
21004
|
+
};
|
|
21005
|
+
},
|
|
21006
|
+
/**
|
|
21007
|
+
* Parses store payload.
|
|
21008
|
+
*/
|
|
21009
|
+
store(args) {
|
|
21010
|
+
return parseWalletPayload(args);
|
|
21011
|
+
},
|
|
21012
|
+
/**
|
|
21013
|
+
* Parses update payload.
|
|
21014
|
+
*/
|
|
21015
|
+
update(args) {
|
|
21016
|
+
const walletId = parseWalletId(args.walletId);
|
|
21017
|
+
const record = parseWalletPayload(args);
|
|
21018
|
+
return {
|
|
21019
|
+
...record,
|
|
21020
|
+
walletId,
|
|
21021
|
+
};
|
|
21022
|
+
},
|
|
21023
|
+
/**
|
|
21024
|
+
* Parses delete payload.
|
|
21025
|
+
*/
|
|
21026
|
+
delete(args) {
|
|
21027
|
+
return { walletId: parseWalletId(args.walletId) };
|
|
21028
|
+
},
|
|
21029
|
+
/**
|
|
21030
|
+
* Parses request payload for user wallet input prompt.
|
|
21031
|
+
*/
|
|
21032
|
+
request(args) {
|
|
21033
|
+
return {
|
|
21034
|
+
recordType: parseWalletRecordType(args.recordType, 'ACCESS_TOKEN'),
|
|
21035
|
+
service: parseWalletService(args.service),
|
|
21036
|
+
key: parseWalletKey(args.key),
|
|
21037
|
+
message: normalizeOptionalText(args.message),
|
|
21038
|
+
isUserScoped: args.isUserScoped === true,
|
|
21039
|
+
isGlobal: args.isGlobal === true,
|
|
21040
|
+
};
|
|
21041
|
+
},
|
|
21042
|
+
};
|
|
21043
|
+
|
|
21044
|
+
/**
|
|
21045
|
+
* Resolves runtime context from hidden tool arguments.
|
|
21046
|
+
*
|
|
21047
|
+
* @private function of WalletCommitmentDefinition
|
|
21048
|
+
*/
|
|
21049
|
+
function resolveWalletRuntimeContext(args) {
|
|
21050
|
+
const runtimeContext = readToolRuntimeContextFromToolArgs(args);
|
|
21051
|
+
const memoryContext = runtimeContext === null || runtimeContext === void 0 ? void 0 : runtimeContext.memory;
|
|
21052
|
+
return {
|
|
21053
|
+
enabled: (memoryContext === null || memoryContext === void 0 ? void 0 : memoryContext.enabled) === true,
|
|
21054
|
+
userId: memoryContext === null || memoryContext === void 0 ? void 0 : memoryContext.userId,
|
|
21055
|
+
username: memoryContext === null || memoryContext === void 0 ? void 0 : memoryContext.username,
|
|
21056
|
+
agentId: memoryContext === null || memoryContext === void 0 ? void 0 : memoryContext.agentId,
|
|
21057
|
+
agentName: memoryContext === null || memoryContext === void 0 ? void 0 : memoryContext.agentName,
|
|
21058
|
+
isTeamConversation: (memoryContext === null || memoryContext === void 0 ? void 0 : memoryContext.isTeamConversation) === true,
|
|
21059
|
+
isPrivateMode: (memoryContext === null || memoryContext === void 0 ? void 0 : memoryContext.isPrivateMode) === true,
|
|
21060
|
+
};
|
|
21061
|
+
}
|
|
21062
|
+
|
|
21063
|
+
/**
|
|
21064
|
+
* Creates runtime wallet tool function implementations.
|
|
21065
|
+
*
|
|
21066
|
+
* @private function of WalletCommitmentDefinition
|
|
21067
|
+
*/
|
|
21068
|
+
function createWalletToolFunctions() {
|
|
21069
|
+
return {
|
|
21070
|
+
async [WalletToolNames.retrieve](args) {
|
|
21071
|
+
const runtimeContext = resolveWalletRuntimeContext(args);
|
|
21072
|
+
const { adapter, disabledResult } = getWalletToolRuntimeAdapterOrDisabledResult('retrieve', runtimeContext);
|
|
21073
|
+
if (!adapter || disabledResult) {
|
|
21074
|
+
return JSON.stringify(disabledResult);
|
|
21075
|
+
}
|
|
21076
|
+
try {
|
|
21077
|
+
const parsedArgs = parseWalletToolArgs.retrieve(args);
|
|
21078
|
+
const records = await adapter.retrieveWalletRecords(parsedArgs, runtimeContext);
|
|
21079
|
+
return JSON.stringify({
|
|
21080
|
+
action: 'retrieve',
|
|
21081
|
+
status: 'ok',
|
|
21082
|
+
query: parsedArgs.query,
|
|
21083
|
+
records,
|
|
21084
|
+
});
|
|
21085
|
+
}
|
|
21086
|
+
catch (error) {
|
|
21087
|
+
return JSON.stringify({
|
|
21088
|
+
action: 'retrieve',
|
|
21089
|
+
status: 'error',
|
|
21090
|
+
records: [],
|
|
21091
|
+
message: error instanceof Error ? error.message : String(error),
|
|
21092
|
+
});
|
|
21093
|
+
}
|
|
21094
|
+
},
|
|
21095
|
+
async [WalletToolNames.store](args) {
|
|
21096
|
+
const runtimeContext = resolveWalletRuntimeContext(args);
|
|
21097
|
+
const { adapter, disabledResult } = getWalletToolRuntimeAdapterOrDisabledResult('store', runtimeContext);
|
|
21098
|
+
if (!adapter || disabledResult) {
|
|
21099
|
+
return JSON.stringify(disabledResult);
|
|
21100
|
+
}
|
|
21101
|
+
try {
|
|
21102
|
+
const parsedArgs = parseWalletToolArgs.store(args);
|
|
21103
|
+
const record = await adapter.storeWalletRecord(parsedArgs, runtimeContext);
|
|
21104
|
+
return JSON.stringify({
|
|
21105
|
+
action: 'store',
|
|
21106
|
+
status: 'stored',
|
|
21107
|
+
record,
|
|
21108
|
+
});
|
|
21109
|
+
}
|
|
21110
|
+
catch (error) {
|
|
21111
|
+
return JSON.stringify({
|
|
21112
|
+
action: 'store',
|
|
21113
|
+
status: 'error',
|
|
21114
|
+
message: error instanceof Error ? error.message : String(error),
|
|
21115
|
+
});
|
|
21116
|
+
}
|
|
21117
|
+
},
|
|
21118
|
+
async [WalletToolNames.update](args) {
|
|
21119
|
+
const runtimeContext = resolveWalletRuntimeContext(args);
|
|
21120
|
+
const { adapter, disabledResult } = getWalletToolRuntimeAdapterOrDisabledResult('update', runtimeContext);
|
|
21121
|
+
if (!adapter || disabledResult) {
|
|
21122
|
+
return JSON.stringify(disabledResult);
|
|
21123
|
+
}
|
|
21124
|
+
try {
|
|
21125
|
+
const parsedArgs = parseWalletToolArgs.update(args);
|
|
21126
|
+
const record = await adapter.updateWalletRecord(parsedArgs, runtimeContext);
|
|
21127
|
+
return JSON.stringify({
|
|
21128
|
+
action: 'update',
|
|
21129
|
+
status: 'updated',
|
|
21130
|
+
record,
|
|
21131
|
+
});
|
|
21132
|
+
}
|
|
21133
|
+
catch (error) {
|
|
21134
|
+
return JSON.stringify({
|
|
21135
|
+
action: 'update',
|
|
21136
|
+
status: 'error',
|
|
21137
|
+
message: error instanceof Error ? error.message : String(error),
|
|
21138
|
+
});
|
|
21139
|
+
}
|
|
21140
|
+
},
|
|
21141
|
+
async [WalletToolNames.delete](args) {
|
|
21142
|
+
const runtimeContext = resolveWalletRuntimeContext(args);
|
|
21143
|
+
const { adapter, disabledResult } = getWalletToolRuntimeAdapterOrDisabledResult('delete', runtimeContext);
|
|
21144
|
+
if (!adapter || disabledResult) {
|
|
21145
|
+
return JSON.stringify(disabledResult);
|
|
21146
|
+
}
|
|
21147
|
+
try {
|
|
21148
|
+
const parsedArgs = parseWalletToolArgs.delete(args);
|
|
21149
|
+
const deleted = await adapter.deleteWalletRecord(parsedArgs, runtimeContext);
|
|
21150
|
+
return JSON.stringify({
|
|
21151
|
+
action: 'delete',
|
|
21152
|
+
status: 'deleted',
|
|
21153
|
+
walletId: deleted.id,
|
|
21154
|
+
});
|
|
21155
|
+
}
|
|
21156
|
+
catch (error) {
|
|
21157
|
+
return JSON.stringify({
|
|
21158
|
+
action: 'delete',
|
|
21159
|
+
status: 'error',
|
|
21160
|
+
message: error instanceof Error ? error.message : String(error),
|
|
21161
|
+
});
|
|
21162
|
+
}
|
|
21163
|
+
},
|
|
21164
|
+
async [WalletToolNames.request](args) {
|
|
21165
|
+
const runtimeContext = resolveWalletRuntimeContext(args);
|
|
21166
|
+
const disabledMessage = resolveWalletDisabledMessage(runtimeContext);
|
|
21167
|
+
if (disabledMessage) {
|
|
21168
|
+
return JSON.stringify({
|
|
21169
|
+
action: 'request',
|
|
21170
|
+
status: 'disabled',
|
|
21171
|
+
message: disabledMessage,
|
|
21172
|
+
});
|
|
21173
|
+
}
|
|
21174
|
+
const request = parseWalletToolArgs.request(args);
|
|
21175
|
+
return JSON.stringify({
|
|
21176
|
+
action: 'request',
|
|
21177
|
+
status: 'requested',
|
|
21178
|
+
request,
|
|
21179
|
+
message: request.message ||
|
|
21180
|
+
`Request user to provide ${request.recordType} credentials for service "${request.service}".`,
|
|
21181
|
+
});
|
|
21182
|
+
},
|
|
21183
|
+
};
|
|
21184
|
+
}
|
|
21185
|
+
|
|
21186
|
+
/**
|
|
21187
|
+
* Creates tool definitions required by WALLET commitment.
|
|
21188
|
+
*
|
|
21189
|
+
* @private function of WalletCommitmentDefinition
|
|
21190
|
+
*/
|
|
21191
|
+
function createWalletTools(existingTools) {
|
|
21192
|
+
const tools = [...(existingTools || [])];
|
|
21193
|
+
addWalletToolIfMissing(tools, {
|
|
21194
|
+
name: WalletToolNames.retrieve,
|
|
21195
|
+
description: 'Retrieve wallet records relevant to the current task.',
|
|
21196
|
+
parameters: {
|
|
21197
|
+
type: 'object',
|
|
21198
|
+
properties: {
|
|
21199
|
+
query: { type: 'string', description: 'Optional text query used to filter wallet records.' },
|
|
21200
|
+
recordType: {
|
|
21201
|
+
type: 'string',
|
|
21202
|
+
description: 'Optional record type filter (USERNAME_PASSWORD, SESSION_COOKIE, ACCESS_TOKEN).',
|
|
21203
|
+
},
|
|
21204
|
+
service: { type: 'string', description: 'Optional service filter, for example github.' },
|
|
21205
|
+
key: { type: 'string', description: 'Optional wallet key filter.' },
|
|
21206
|
+
limit: { type: 'integer', description: 'Optional maximum number of records (default 5, max 20).' },
|
|
21207
|
+
},
|
|
21208
|
+
required: [],
|
|
21209
|
+
},
|
|
21210
|
+
});
|
|
21211
|
+
addWalletToolIfMissing(tools, {
|
|
21212
|
+
name: WalletToolNames.store,
|
|
21213
|
+
description: 'Store one wallet record.',
|
|
21214
|
+
parameters: {
|
|
21215
|
+
type: 'object',
|
|
21216
|
+
properties: {
|
|
21217
|
+
recordType: {
|
|
21218
|
+
type: 'string',
|
|
21219
|
+
description: 'Record type: USERNAME_PASSWORD, SESSION_COOKIE, ACCESS_TOKEN.',
|
|
21220
|
+
},
|
|
21221
|
+
service: { type: 'string', description: 'Service identifier, for example github.' },
|
|
21222
|
+
key: { type: 'string', description: 'Logical credential key.' },
|
|
21223
|
+
username: { type: 'string', description: 'Username for USERNAME_PASSWORD.' },
|
|
21224
|
+
password: { type: 'string', description: 'Password for USERNAME_PASSWORD.' },
|
|
21225
|
+
secret: { type: 'string', description: 'Token/API key for ACCESS_TOKEN.' },
|
|
21226
|
+
cookies: { type: 'string', description: 'Cookie header/json for SESSION_COOKIE.' },
|
|
21227
|
+
isUserScoped: { type: 'boolean', description: 'Set true to scope this record to current user.' },
|
|
21228
|
+
isGlobal: { type: 'boolean', description: 'Set true to make this record global.' },
|
|
21229
|
+
},
|
|
21230
|
+
required: ['recordType', 'service'],
|
|
21231
|
+
},
|
|
21232
|
+
});
|
|
21233
|
+
addWalletToolIfMissing(tools, {
|
|
21234
|
+
name: WalletToolNames.update,
|
|
21235
|
+
description: 'Update one existing wallet record.',
|
|
21236
|
+
parameters: {
|
|
21237
|
+
type: 'object',
|
|
21238
|
+
properties: {
|
|
21239
|
+
walletId: { type: 'string', description: 'Wallet record id to update.' },
|
|
21240
|
+
recordType: {
|
|
21241
|
+
type: 'string',
|
|
21242
|
+
description: 'Record type: USERNAME_PASSWORD, SESSION_COOKIE, ACCESS_TOKEN.',
|
|
21243
|
+
},
|
|
21244
|
+
service: { type: 'string', description: 'Service identifier, for example github.' },
|
|
21245
|
+
key: { type: 'string', description: 'Logical credential key.' },
|
|
21246
|
+
username: { type: 'string', description: 'Username for USERNAME_PASSWORD.' },
|
|
21247
|
+
password: { type: 'string', description: 'Password for USERNAME_PASSWORD.' },
|
|
21248
|
+
secret: { type: 'string', description: 'Token/API key for ACCESS_TOKEN.' },
|
|
21249
|
+
cookies: { type: 'string', description: 'Cookie header/json for SESSION_COOKIE.' },
|
|
21250
|
+
isUserScoped: { type: 'boolean', description: 'Set true to scope this record to current user.' },
|
|
21251
|
+
isGlobal: { type: 'boolean', description: 'Set true to make this record global.' },
|
|
21252
|
+
},
|
|
21253
|
+
required: ['walletId', 'recordType', 'service'],
|
|
21254
|
+
},
|
|
21255
|
+
});
|
|
21256
|
+
addWalletToolIfMissing(tools, {
|
|
21257
|
+
name: WalletToolNames.delete,
|
|
21258
|
+
description: 'Delete one wallet record.',
|
|
21259
|
+
parameters: {
|
|
21260
|
+
type: 'object',
|
|
21261
|
+
properties: {
|
|
21262
|
+
walletId: { type: 'string', description: 'Wallet record id to delete.' },
|
|
21263
|
+
},
|
|
21264
|
+
required: ['walletId'],
|
|
21265
|
+
},
|
|
21266
|
+
});
|
|
21267
|
+
addWalletToolIfMissing(tools, {
|
|
21268
|
+
name: WalletToolNames.request,
|
|
21269
|
+
description: 'Request missing credential from user via popup.',
|
|
21270
|
+
parameters: {
|
|
21271
|
+
type: 'object',
|
|
21272
|
+
properties: {
|
|
21273
|
+
recordType: {
|
|
21274
|
+
type: 'string',
|
|
21275
|
+
description: 'Requested record type: USERNAME_PASSWORD, SESSION_COOKIE, ACCESS_TOKEN.',
|
|
21276
|
+
},
|
|
21277
|
+
service: { type: 'string', description: 'Service identifier.' },
|
|
21278
|
+
key: { type: 'string', description: 'Logical credential key.' },
|
|
21279
|
+
message: { type: 'string', description: 'Optional UI message for user.' },
|
|
21280
|
+
isUserScoped: {
|
|
21281
|
+
type: 'boolean',
|
|
21282
|
+
description: 'Set true when record should be scoped to current user.',
|
|
21283
|
+
},
|
|
21284
|
+
isGlobal: { type: 'boolean', description: 'Set true when record should be global.' },
|
|
21285
|
+
},
|
|
21286
|
+
required: [],
|
|
21287
|
+
},
|
|
21288
|
+
});
|
|
21289
|
+
return tools;
|
|
21290
|
+
}
|
|
21291
|
+
/**
|
|
21292
|
+
* Registers one wallet tool when missing in current tool list.
|
|
21293
|
+
*
|
|
21294
|
+
* @private function of WalletCommitmentDefinition
|
|
21295
|
+
*/
|
|
21296
|
+
function addWalletToolIfMissing(tools, tool) {
|
|
21297
|
+
if (!tools.some((existingTool) => existingTool.name === tool.name)) {
|
|
21298
|
+
tools.push(tool);
|
|
21299
|
+
}
|
|
21300
|
+
}
|
|
21301
|
+
|
|
21302
|
+
/**
|
|
21303
|
+
* Gets markdown documentation for WALLET commitment.
|
|
21304
|
+
*
|
|
21305
|
+
* @private function of WalletCommitmentDefinition
|
|
21306
|
+
*/
|
|
21307
|
+
function getWalletCommitmentDocumentation(type) {
|
|
21308
|
+
return spaceTrim$1(`
|
|
21309
|
+
# ${type}
|
|
21310
|
+
|
|
21311
|
+
Enables private credential storage for tokens, usernames/passwords, and session cookies.
|
|
21312
|
+
`);
|
|
21313
|
+
}
|
|
21314
|
+
|
|
21315
|
+
/**
|
|
21316
|
+
* Gets human-readable titles for WALLET tool functions.
|
|
21317
|
+
*
|
|
21318
|
+
* @private function of WalletCommitmentDefinition
|
|
21319
|
+
*/
|
|
21320
|
+
function getWalletToolTitles() {
|
|
21321
|
+
return {
|
|
21322
|
+
[WalletToolNames.retrieve]: 'Wallet',
|
|
21323
|
+
[WalletToolNames.store]: 'Store wallet record',
|
|
21324
|
+
[WalletToolNames.update]: 'Update wallet record',
|
|
21325
|
+
[WalletToolNames.delete]: 'Delete wallet record',
|
|
21326
|
+
[WalletToolNames.request]: 'Request wallet record',
|
|
21327
|
+
};
|
|
21328
|
+
}
|
|
21329
|
+
|
|
21330
|
+
/**
|
|
21331
|
+
* WALLET commitment definition.
|
|
21332
|
+
*
|
|
21333
|
+
* @private [🪔] Maybe export the commitments through some package
|
|
21334
|
+
*/
|
|
21335
|
+
class WalletCommitmentDefinition extends BaseCommitmentDefinition {
|
|
21336
|
+
constructor(type = 'WALLET') {
|
|
21337
|
+
super(type);
|
|
21338
|
+
}
|
|
21339
|
+
get requiresContent() {
|
|
21340
|
+
return false;
|
|
21341
|
+
}
|
|
21342
|
+
get description() {
|
|
21343
|
+
return 'Enable persistent private credential storage (tokens, logins, cookies) scoped per agent or globally.';
|
|
21344
|
+
}
|
|
21345
|
+
get icon() {
|
|
21346
|
+
return '👛';
|
|
21347
|
+
}
|
|
21348
|
+
get documentation() {
|
|
21349
|
+
return getWalletCommitmentDocumentation(this.type);
|
|
21350
|
+
}
|
|
21351
|
+
applyToAgentModelRequirements(requirements, content) {
|
|
21352
|
+
const extraInstructions = formatOptionalInstructionBlock('Wallet instructions', content);
|
|
21353
|
+
return this.appendToSystemMessage({
|
|
21354
|
+
...requirements,
|
|
21355
|
+
tools: createWalletTools(requirements.tools),
|
|
21356
|
+
_metadata: {
|
|
21357
|
+
...requirements._metadata,
|
|
21358
|
+
useWallet: content || true,
|
|
21359
|
+
},
|
|
21360
|
+
}, createWalletSystemMessage(extraInstructions));
|
|
21361
|
+
}
|
|
21362
|
+
getToolTitles() {
|
|
21363
|
+
return getWalletToolTitles();
|
|
21364
|
+
}
|
|
21365
|
+
getToolFunctions() {
|
|
21366
|
+
return createWalletToolFunctions();
|
|
21367
|
+
}
|
|
21368
|
+
}
|
|
21369
|
+
// Note: [💞] Ignore a discrepancy between file name and entity name
|
|
21370
|
+
|
|
20655
21371
|
/**
|
|
20656
21372
|
* `WRITING RULES` commitment definition.
|
|
20657
21373
|
*
|
|
@@ -20926,6 +21642,8 @@ const COMMITMENT_REGISTRY = [
|
|
|
20926
21642
|
new MessageSuffixCommitmentDefinition(),
|
|
20927
21643
|
new MessageCommitmentDefinition('MESSAGE'),
|
|
20928
21644
|
new MessageCommitmentDefinition('MESSAGES'),
|
|
21645
|
+
new WalletCommitmentDefinition('WALLET'),
|
|
21646
|
+
new WalletCommitmentDefinition('WALLETS'),
|
|
20929
21647
|
new ScenarioCommitmentDefinition('SCENARIO'),
|
|
20930
21648
|
new ScenarioCommitmentDefinition('SCENARIOS'),
|
|
20931
21649
|
new DeleteCommitmentDefinition('DELETE'),
|
|
@@ -21595,7 +22313,6 @@ function createInheritanceCapability(content) {
|
|
|
21595
22313
|
if (isVoidPseudoAgentReference(reference)) {
|
|
21596
22314
|
label = VOID_PSEUDO_AGENT_REFERENCE; // <- {Void} label
|
|
21597
22315
|
iconName = 'ShieldAlert';
|
|
21598
|
-
return null; // <- Note: Do not show `{Void}` in capabilities, it's only used for internal logic
|
|
21599
22316
|
}
|
|
21600
22317
|
return {
|
|
21601
22318
|
type: 'inheritance',
|
|
@@ -23806,11 +24523,11 @@ function useBookEditorMonacoInteractions({ editor, handleFiles, }) {
|
|
|
23806
24523
|
*/
|
|
23807
24524
|
const IMPORTANT_COMMITMENT_TYPE_SORT_ORDER = new Map([
|
|
23808
24525
|
['GOAL', 0],
|
|
23809
|
-
['
|
|
23810
|
-
['
|
|
23811
|
-
['
|
|
23812
|
-
['
|
|
23813
|
-
['
|
|
24526
|
+
['RULE', 1],
|
|
24527
|
+
['KNOWLEDGE', 2],
|
|
24528
|
+
['TEAM', 3],
|
|
24529
|
+
['GOALS', 4],
|
|
24530
|
+
['RULES', 5],
|
|
23814
24531
|
]);
|
|
23815
24532
|
/**
|
|
23816
24533
|
* Sort rank used when unfinished, low-level, and deprecated commitments should be grouped last.
|
|
@@ -24325,6 +25042,16 @@ function ensureBookEditorMonacoLanguage(monaco, theme = 'LIGHT') {
|
|
|
24325
25042
|
const commitmentDefinitions = getAllCommitmentDefinitions();
|
|
24326
25043
|
const commitmentTypes = [...new Set(commitmentDefinitions.map(({ type }) => type))];
|
|
24327
25044
|
const commitmentDefinitionByType = new Map(commitmentDefinitions.map((definition) => [definition.type, definition]));
|
|
25045
|
+
const completionCommitmentTypes = [...commitmentTypes].sort((leftType, rightType) => {
|
|
25046
|
+
const leftDefinition = commitmentDefinitionByType.get(leftType);
|
|
25047
|
+
const rightDefinition = commitmentDefinitionByType.get(rightType);
|
|
25048
|
+
const leftRank = (leftDefinition === null || leftDefinition === void 0 ? void 0 : leftDefinition.isUnfinished) ? 1 : 0;
|
|
25049
|
+
const rightRank = (rightDefinition === null || rightDefinition === void 0 ? void 0 : rightDefinition.isUnfinished) ? 1 : 0;
|
|
25050
|
+
if (leftRank !== rightRank) {
|
|
25051
|
+
return leftRank - rightRank;
|
|
25052
|
+
}
|
|
25053
|
+
return commitmentTypes.indexOf(leftType) - commitmentTypes.indexOf(rightType);
|
|
25054
|
+
});
|
|
24328
25055
|
const noteLikeCommitmentTypeSet = new Set([...TODO_COMMITMENT_TYPES, ...NOTE_COMMITMENT_TYPES]);
|
|
24329
25056
|
const noteLikeCommitmentStates = createNoteLikeCommitmentStates(commitmentTypes);
|
|
24330
25057
|
const executableCommitmentTypes = commitmentTypes.filter((type) => !noteLikeCommitmentTypeSet.has(type.toUpperCase()));
|
|
@@ -24381,7 +25108,7 @@ function ensureBookEditorMonacoLanguage(monaco, theme = 'LIGHT') {
|
|
|
24381
25108
|
startColumn: word.startColumn,
|
|
24382
25109
|
endColumn: word.endColumn,
|
|
24383
25110
|
};
|
|
24384
|
-
const suggestions =
|
|
25111
|
+
const suggestions = completionCommitmentTypes.map((type, index) => {
|
|
24385
25112
|
var _a;
|
|
24386
25113
|
const definition = commitmentDefinitionByType.get(type);
|
|
24387
25114
|
const notice = definition ? getCommitmentNoticeMetadata(definition) : null;
|
|
@@ -25087,22 +25814,13 @@ function createUploadStats(uploadItems) {
|
|
|
25087
25814
|
};
|
|
25088
25815
|
}
|
|
25089
25816
|
/**
|
|
25090
|
-
*
|
|
25817
|
+
* Internal upload item state shared by Monaco upload helpers.
|
|
25091
25818
|
*
|
|
25092
25819
|
* @private function of BookEditorMonaco
|
|
25093
25820
|
*/
|
|
25094
|
-
function
|
|
25821
|
+
function useBookEditorMonacoUploadItemsState() {
|
|
25095
25822
|
const [uploadItems, setUploadItemsState] = useState([]);
|
|
25096
25823
|
const uploadItemsRef = useRef([]);
|
|
25097
|
-
const uploadFilesRef = useRef(new Map());
|
|
25098
|
-
const uploadDecorationIdsRef = useRef(new Map());
|
|
25099
|
-
const uploadControllersRef = useRef(new Map());
|
|
25100
|
-
const uploadQueueTimerRef = useRef(null);
|
|
25101
|
-
const editorUpdateTimerRef = useRef(null);
|
|
25102
|
-
const progressUpdateTimerRef = useRef(null);
|
|
25103
|
-
const pendingReplacementsRef = useRef([]);
|
|
25104
|
-
const pendingProgressUpdatesRef = useRef(new Map());
|
|
25105
|
-
const processUploadQueueRef = useRef(() => undefined);
|
|
25106
25824
|
const setUploadItems = useCallback((updater) => {
|
|
25107
25825
|
const next = updater(uploadItemsRef.current);
|
|
25108
25826
|
uploadItemsRef.current = next;
|
|
@@ -25111,6 +25829,31 @@ function useBookEditorMonacoUploads({ editor, monaco, onFileUpload }) {
|
|
|
25111
25829
|
const updateUploadItem = useCallback((uploadId, createNextUploadItem) => {
|
|
25112
25830
|
setUploadItems((currentUploadItems) => replaceUploadItemById(currentUploadItems, uploadId, createNextUploadItem));
|
|
25113
25831
|
}, [setUploadItems]);
|
|
25832
|
+
const uploadStats = useMemo(() => createUploadStats(uploadItems), [uploadItems]);
|
|
25833
|
+
const activeUploadItems = useMemo(() => uploadItems.filter((uploadItem) => uploadItem.status !== 'completed'), [uploadItems]);
|
|
25834
|
+
return {
|
|
25835
|
+
uploadItems,
|
|
25836
|
+
uploadItemsRef,
|
|
25837
|
+
setUploadItems,
|
|
25838
|
+
updateUploadItem,
|
|
25839
|
+
uploadStats,
|
|
25840
|
+
activeUploadItems,
|
|
25841
|
+
};
|
|
25842
|
+
}
|
|
25843
|
+
/**
|
|
25844
|
+
* Debounces upload progress updates before they hit React state.
|
|
25845
|
+
*
|
|
25846
|
+
* @private function of BookEditorMonaco
|
|
25847
|
+
*/
|
|
25848
|
+
function useBookEditorMonacoUploadProgressQueue({ setUploadItems }) {
|
|
25849
|
+
const progressUpdateTimerRef = useRef(null);
|
|
25850
|
+
const pendingProgressUpdatesRef = useRef(new Map());
|
|
25851
|
+
const flushProgressUpdates = useCallback(() => {
|
|
25852
|
+
progressUpdateTimerRef.current = null;
|
|
25853
|
+
const progressUpdates = pendingProgressUpdatesRef.current;
|
|
25854
|
+
pendingProgressUpdatesRef.current = new Map();
|
|
25855
|
+
setUploadItems((currentUploadItems) => applyProgressUpdates(currentUploadItems, progressUpdates));
|
|
25856
|
+
}, [setUploadItems]);
|
|
25114
25857
|
const queueProgressUpdate = useCallback((uploadId, progress, loadedBytes, totalBytes) => {
|
|
25115
25858
|
pendingProgressUpdatesRef.current.set(uploadId, {
|
|
25116
25859
|
progress,
|
|
@@ -25121,12 +25864,27 @@ function useBookEditorMonacoUploads({ editor, monaco, onFileUpload }) {
|
|
|
25121
25864
|
return;
|
|
25122
25865
|
}
|
|
25123
25866
|
progressUpdateTimerRef.current = window.setTimeout(() => {
|
|
25124
|
-
|
|
25125
|
-
const progressUpdates = pendingProgressUpdatesRef.current;
|
|
25126
|
-
pendingProgressUpdatesRef.current = new Map();
|
|
25127
|
-
setUploadItems((currentUploadItems) => applyProgressUpdates(currentUploadItems, progressUpdates));
|
|
25867
|
+
flushProgressUpdates();
|
|
25128
25868
|
}, BookEditorMonacoConstants.UPLOAD_PROGRESS_DEBOUNCE_MS);
|
|
25129
|
-
}, [
|
|
25869
|
+
}, [flushProgressUpdates]);
|
|
25870
|
+
const clearProgressQueue = useCallback(() => {
|
|
25871
|
+
clearScheduledTimer(progressUpdateTimerRef.current);
|
|
25872
|
+
progressUpdateTimerRef.current = null;
|
|
25873
|
+
pendingProgressUpdatesRef.current = new Map();
|
|
25874
|
+
}, []);
|
|
25875
|
+
return {
|
|
25876
|
+
queueProgressUpdate,
|
|
25877
|
+
clearProgressQueue,
|
|
25878
|
+
};
|
|
25879
|
+
}
|
|
25880
|
+
/**
|
|
25881
|
+
* Manages Monaco placeholder insertion and later replacement with uploaded URLs.
|
|
25882
|
+
*
|
|
25883
|
+
* @private function of BookEditorMonaco
|
|
25884
|
+
*/
|
|
25885
|
+
function useBookEditorMonacoUploadEditorSync({ editor, uploadFilesRef, uploadDecorationIdsRef, }) {
|
|
25886
|
+
const editorUpdateTimerRef = useRef(null);
|
|
25887
|
+
const pendingReplacementsRef = useRef([]);
|
|
25130
25888
|
const flushEditorReplacements = useCallback(() => {
|
|
25131
25889
|
if (!editor) {
|
|
25132
25890
|
return;
|
|
@@ -25160,23 +25918,20 @@ function useBookEditorMonacoUploads({ editor, monaco, onFileUpload }) {
|
|
|
25160
25918
|
editor.deltaDecorations(decorationsToRemove, []);
|
|
25161
25919
|
}
|
|
25162
25920
|
}, [editor]);
|
|
25921
|
+
const registerUploadPlaceholderResources = useCallback((placeholders, decorationIds) => {
|
|
25922
|
+
placeholders.forEach((placeholder, index) => {
|
|
25923
|
+
uploadFilesRef.current.set(placeholder.id, placeholder.file);
|
|
25924
|
+
const decorationId = decorationIds[index];
|
|
25925
|
+
if (decorationId) {
|
|
25926
|
+
uploadDecorationIdsRef.current.set(placeholder.id, decorationId);
|
|
25927
|
+
}
|
|
25928
|
+
});
|
|
25929
|
+
}, []);
|
|
25163
25930
|
const queueEditorReplacement = useCallback((uploadId, replacementText) => {
|
|
25164
|
-
const
|
|
25165
|
-
if (!
|
|
25931
|
+
const hasQueuedReplacement = queueEditorReplacementItem(uploadId, replacementText, uploadDecorationIdsRef, pendingReplacementsRef);
|
|
25932
|
+
if (!hasQueuedReplacement) {
|
|
25166
25933
|
return;
|
|
25167
25934
|
}
|
|
25168
|
-
const pendingIndex = pendingReplacementsRef.current.findIndex((item) => item.uploadId === uploadId);
|
|
25169
|
-
const nextReplacement = {
|
|
25170
|
-
uploadId,
|
|
25171
|
-
decorationId,
|
|
25172
|
-
replacementText,
|
|
25173
|
-
};
|
|
25174
|
-
if (pendingIndex >= 0) {
|
|
25175
|
-
pendingReplacementsRef.current[pendingIndex] = nextReplacement;
|
|
25176
|
-
}
|
|
25177
|
-
else {
|
|
25178
|
-
pendingReplacementsRef.current.push(nextReplacement);
|
|
25179
|
-
}
|
|
25180
25935
|
if (editorUpdateTimerRef.current !== null) {
|
|
25181
25936
|
return;
|
|
25182
25937
|
}
|
|
@@ -25184,16 +25939,81 @@ function useBookEditorMonacoUploads({ editor, monaco, onFileUpload }) {
|
|
|
25184
25939
|
editorUpdateTimerRef.current = null;
|
|
25185
25940
|
flushEditorReplacements();
|
|
25186
25941
|
}, BookEditorMonacoConstants.UPLOAD_EDIT_DEBOUNCE_MS);
|
|
25187
|
-
}, [flushEditorReplacements]);
|
|
25188
|
-
const
|
|
25189
|
-
|
|
25190
|
-
|
|
25191
|
-
|
|
25192
|
-
if (decorationId) {
|
|
25193
|
-
uploadDecorationIdsRef.current.set(placeholder.id, decorationId);
|
|
25194
|
-
}
|
|
25195
|
-
});
|
|
25942
|
+
}, [flushEditorReplacements, uploadDecorationIdsRef]);
|
|
25943
|
+
const clearEditorSync = useCallback(() => {
|
|
25944
|
+
clearScheduledTimer(editorUpdateTimerRef.current);
|
|
25945
|
+
editorUpdateTimerRef.current = null;
|
|
25946
|
+
pendingReplacementsRef.current = [];
|
|
25196
25947
|
}, []);
|
|
25948
|
+
return {
|
|
25949
|
+
registerUploadPlaceholderResources,
|
|
25950
|
+
queueEditorReplacement,
|
|
25951
|
+
clearEditorSync,
|
|
25952
|
+
};
|
|
25953
|
+
}
|
|
25954
|
+
/**
|
|
25955
|
+
* Enqueues replacement text for one upload placeholder.
|
|
25956
|
+
*
|
|
25957
|
+
* @private function of BookEditorMonaco
|
|
25958
|
+
*/
|
|
25959
|
+
function queueEditorReplacementItem(uploadId, replacementText, uploadDecorationIdsRef, pendingReplacementsRef) {
|
|
25960
|
+
const decorationId = uploadDecorationIdsRef.current.get(uploadId);
|
|
25961
|
+
if (!decorationId) {
|
|
25962
|
+
return false;
|
|
25963
|
+
}
|
|
25964
|
+
const pendingIndex = pendingReplacementsRef.current.findIndex((item) => item.uploadId === uploadId);
|
|
25965
|
+
const nextReplacement = {
|
|
25966
|
+
uploadId,
|
|
25967
|
+
decorationId,
|
|
25968
|
+
replacementText,
|
|
25969
|
+
};
|
|
25970
|
+
if (pendingIndex >= 0) {
|
|
25971
|
+
pendingReplacementsRef.current[pendingIndex] = nextReplacement;
|
|
25972
|
+
}
|
|
25973
|
+
else {
|
|
25974
|
+
pendingReplacementsRef.current.push(nextReplacement);
|
|
25975
|
+
}
|
|
25976
|
+
return true;
|
|
25977
|
+
}
|
|
25978
|
+
/**
|
|
25979
|
+
* Inserts Monaco placeholders and queues matching upload items.
|
|
25980
|
+
*
|
|
25981
|
+
* @private function of BookEditorMonaco
|
|
25982
|
+
*/
|
|
25983
|
+
function enqueueFilesForUpload({ editor, monaco, registerUploadPlaceholderResources, setUploadItems, }) {
|
|
25984
|
+
return (files) => {
|
|
25985
|
+
if (!editor || !monaco) {
|
|
25986
|
+
return false;
|
|
25987
|
+
}
|
|
25988
|
+
const model = editor.getModel();
|
|
25989
|
+
if (!model) {
|
|
25990
|
+
return false;
|
|
25991
|
+
}
|
|
25992
|
+
const placeholders = createUploadPlaceholderEntries(files);
|
|
25993
|
+
const insertPlan = createUploadPlaceholderInsertPlan(model, monaco, placeholders);
|
|
25994
|
+
editor.executeEdits('upload-placeholders', [
|
|
25995
|
+
{
|
|
25996
|
+
range: new monaco.Range(insertPlan.insertLine, insertPlan.insertColumn, insertPlan.insertLine, insertPlan.insertColumn),
|
|
25997
|
+
text: insertPlan.textToInsert,
|
|
25998
|
+
forceMoveMarkers: true,
|
|
25999
|
+
},
|
|
26000
|
+
]);
|
|
26001
|
+
const placeholderDecorations = createUploadPlaceholderDecorations(model, monaco, placeholders, insertPlan.insertStartOffset, insertPlan.prefixLength);
|
|
26002
|
+
const decorationIds = editor.deltaDecorations([], placeholderDecorations);
|
|
26003
|
+
registerUploadPlaceholderResources(placeholders, decorationIds);
|
|
26004
|
+
setUploadItems((currentUploadItems) => [...currentUploadItems, ...createQueuedUploadItems(placeholders)]);
|
|
26005
|
+
return true;
|
|
26006
|
+
};
|
|
26007
|
+
}
|
|
26008
|
+
/**
|
|
26009
|
+
* Manages upload concurrency, retries, pausing and completion side effects.
|
|
26010
|
+
*
|
|
26011
|
+
* @private function of BookEditorMonaco
|
|
26012
|
+
*/
|
|
26013
|
+
function useBookEditorMonacoUploadQueue({ onFileUpload, uploadItemsRef, uploadFilesRef, queueProgressUpdate, queueEditorReplacement, setUploadItems, updateUploadItem, }) {
|
|
26014
|
+
const uploadControllersRef = useRef(new Map());
|
|
26015
|
+
const uploadQueueTimerRef = useRef(null);
|
|
26016
|
+
const processUploadQueueRef = useRef(() => undefined);
|
|
25197
26017
|
const queueUploadProcessing = useCallback(() => {
|
|
25198
26018
|
if (uploadQueueTimerRef.current !== null) {
|
|
25199
26019
|
return;
|
|
@@ -25306,6 +26126,8 @@ function useBookEditorMonacoUploads({ editor, monaco, onFileUpload }) {
|
|
|
25306
26126
|
onFileUpload,
|
|
25307
26127
|
queueProgressUpdate,
|
|
25308
26128
|
queueUploadProcessing,
|
|
26129
|
+
uploadFilesRef,
|
|
26130
|
+
uploadItemsRef,
|
|
25309
26131
|
]);
|
|
25310
26132
|
const processUploadQueue = useCallback(() => {
|
|
25311
26133
|
if (!onFileUpload) {
|
|
@@ -25318,34 +26140,45 @@ function useBookEditorMonacoUploads({ editor, monaco, onFileUpload }) {
|
|
|
25318
26140
|
queuedUploadIds.forEach((queuedUploadId) => {
|
|
25319
26141
|
void startUpload(queuedUploadId);
|
|
25320
26142
|
});
|
|
25321
|
-
}, [onFileUpload, startUpload]);
|
|
26143
|
+
}, [onFileUpload, startUpload, uploadItemsRef]);
|
|
25322
26144
|
processUploadQueueRef.current = processUploadQueue;
|
|
25323
26145
|
const pauseUpload = useCallback((uploadId) => {
|
|
26146
|
+
var _a;
|
|
25324
26147
|
pauseQueuedUpload(uploadId);
|
|
25325
|
-
|
|
25326
|
-
controller === null || controller === void 0 ? void 0 : controller.abort();
|
|
26148
|
+
(_a = uploadControllersRef.current.get(uploadId)) === null || _a === void 0 ? void 0 : _a.abort();
|
|
25327
26149
|
}, [pauseQueuedUpload]);
|
|
25328
26150
|
const resumeUpload = useCallback((uploadId) => {
|
|
25329
26151
|
resetUploadForRetry(uploadId);
|
|
25330
26152
|
queueUploadProcessing();
|
|
25331
26153
|
}, [queueUploadProcessing, resetUploadForRetry]);
|
|
25332
|
-
|
|
25333
|
-
|
|
25334
|
-
|
|
25335
|
-
|
|
25336
|
-
|
|
25337
|
-
|
|
25338
|
-
|
|
25339
|
-
}
|
|
25340
|
-
uploadControllersRef.current.clear();
|
|
25341
|
-
};
|
|
26154
|
+
const clearUploadQueue = useCallback(() => {
|
|
26155
|
+
clearScheduledTimer(uploadQueueTimerRef.current);
|
|
26156
|
+
uploadQueueTimerRef.current = null;
|
|
26157
|
+
for (const controller of uploadControllersRef.current.values()) {
|
|
26158
|
+
controller.abort();
|
|
26159
|
+
}
|
|
26160
|
+
uploadControllersRef.current.clear();
|
|
25342
26161
|
}, []);
|
|
26162
|
+
useEffect(() => clearUploadQueue, [clearUploadQueue]);
|
|
26163
|
+
return {
|
|
26164
|
+
pauseUpload,
|
|
26165
|
+
resumeUpload,
|
|
26166
|
+
queueUploadProcessing,
|
|
26167
|
+
clearUploadQueue,
|
|
26168
|
+
};
|
|
26169
|
+
}
|
|
26170
|
+
/**
|
|
26171
|
+
* Clears upload UI state shortly after all uploads finish.
|
|
26172
|
+
*
|
|
26173
|
+
* @private function of BookEditorMonaco
|
|
26174
|
+
*/
|
|
26175
|
+
function useCompletedUploadsAutoClear({ uploadItems, uploadFilesRef, uploadDecorationIdsRef, setUploadItems, }) {
|
|
25343
26176
|
useEffect(() => {
|
|
25344
26177
|
if (uploadItems.length === 0) {
|
|
25345
26178
|
return;
|
|
25346
26179
|
}
|
|
25347
|
-
const
|
|
25348
|
-
if (
|
|
26180
|
+
const hasActiveUploads = uploadItems.some((item) => item.status !== 'completed');
|
|
26181
|
+
if (hasActiveUploads) {
|
|
25349
26182
|
return;
|
|
25350
26183
|
}
|
|
25351
26184
|
const timer = window.setTimeout(() => {
|
|
@@ -25356,45 +26189,61 @@ function useBookEditorMonacoUploads({ editor, monaco, onFileUpload }) {
|
|
|
25356
26189
|
return () => {
|
|
25357
26190
|
clearTimeout(timer);
|
|
25358
26191
|
};
|
|
25359
|
-
}, [setUploadItems, uploadItems]);
|
|
25360
|
-
|
|
25361
|
-
|
|
25362
|
-
|
|
25363
|
-
|
|
25364
|
-
|
|
25365
|
-
|
|
25366
|
-
|
|
25367
|
-
|
|
25368
|
-
|
|
25369
|
-
|
|
25370
|
-
|
|
25371
|
-
|
|
25372
|
-
|
|
25373
|
-
|
|
25374
|
-
|
|
25375
|
-
|
|
25376
|
-
|
|
25377
|
-
|
|
25378
|
-
|
|
25379
|
-
registerUploadPlaceholderResources
|
|
25380
|
-
setUploadItems
|
|
25381
|
-
|
|
25382
|
-
|
|
26192
|
+
}, [setUploadItems, uploadDecorationIdsRef, uploadFilesRef, uploadItems]);
|
|
26193
|
+
}
|
|
26194
|
+
/**
|
|
26195
|
+
* Handles file uploads and placeholder rendering inside `BookEditorMonaco`.
|
|
26196
|
+
*
|
|
26197
|
+
* @private function of BookEditorMonaco
|
|
26198
|
+
*/
|
|
26199
|
+
function useBookEditorMonacoUploads({ editor, monaco, onFileUpload }) {
|
|
26200
|
+
const uploadFilesRef = useRef(new Map());
|
|
26201
|
+
const uploadDecorationIdsRef = useRef(new Map());
|
|
26202
|
+
const { uploadItems, uploadItemsRef, setUploadItems, updateUploadItem, uploadStats, activeUploadItems } = useBookEditorMonacoUploadItemsState();
|
|
26203
|
+
const { queueProgressUpdate, clearProgressQueue } = useBookEditorMonacoUploadProgressQueue({ setUploadItems });
|
|
26204
|
+
const { registerUploadPlaceholderResources, queueEditorReplacement, clearEditorSync } = useBookEditorMonacoUploadEditorSync({
|
|
26205
|
+
editor,
|
|
26206
|
+
uploadFilesRef,
|
|
26207
|
+
uploadDecorationIdsRef,
|
|
26208
|
+
});
|
|
26209
|
+
const enqueueFiles = useMemo(() => enqueueFilesForUpload({
|
|
26210
|
+
editor,
|
|
26211
|
+
monaco,
|
|
26212
|
+
registerUploadPlaceholderResources,
|
|
26213
|
+
setUploadItems,
|
|
26214
|
+
}), [editor, monaco, registerUploadPlaceholderResources, setUploadItems]);
|
|
26215
|
+
const { pauseUpload, resumeUpload, queueUploadProcessing, clearUploadQueue } = useBookEditorMonacoUploadQueue({
|
|
26216
|
+
onFileUpload,
|
|
26217
|
+
uploadItemsRef,
|
|
26218
|
+
uploadFilesRef,
|
|
26219
|
+
queueProgressUpdate,
|
|
26220
|
+
queueEditorReplacement,
|
|
26221
|
+
setUploadItems,
|
|
26222
|
+
updateUploadItem,
|
|
26223
|
+
});
|
|
26224
|
+
useCompletedUploadsAutoClear({
|
|
26225
|
+
uploadItems,
|
|
26226
|
+
uploadFilesRef,
|
|
26227
|
+
uploadDecorationIdsRef,
|
|
26228
|
+
setUploadItems,
|
|
26229
|
+
});
|
|
26230
|
+
useEffect(() => {
|
|
26231
|
+
return () => {
|
|
26232
|
+
clearEditorSync();
|
|
26233
|
+
clearProgressQueue();
|
|
26234
|
+
clearUploadQueue();
|
|
26235
|
+
};
|
|
26236
|
+
}, [clearEditorSync, clearProgressQueue, clearUploadQueue]);
|
|
25383
26237
|
const handleFiles = useCallback(async (files) => {
|
|
25384
|
-
if (!onFileUpload) {
|
|
26238
|
+
if (!onFileUpload || files.length === 0) {
|
|
25385
26239
|
return;
|
|
25386
26240
|
}
|
|
25387
|
-
|
|
25388
|
-
return;
|
|
25389
|
-
}
|
|
25390
|
-
const hasQueuedUploads = enqueueFilesForUpload(files);
|
|
26241
|
+
const hasQueuedUploads = enqueueFiles(files);
|
|
25391
26242
|
if (!hasQueuedUploads) {
|
|
25392
26243
|
return;
|
|
25393
26244
|
}
|
|
25394
26245
|
queueUploadProcessing();
|
|
25395
|
-
}, [
|
|
25396
|
-
const uploadStats = useMemo(() => createUploadStats(uploadItems), [uploadItems]);
|
|
25397
|
-
const activeUploadItems = useMemo(() => uploadItems.filter((uploadItem) => uploadItem.status !== 'completed'), [uploadItems]);
|
|
26246
|
+
}, [enqueueFiles, onFileUpload, queueUploadProcessing]);
|
|
25398
26247
|
return {
|
|
25399
26248
|
activeUploadItems,
|
|
25400
26249
|
uploadItems,
|
|
@@ -28370,7 +29219,7 @@ function insertTextAtSelection(params) {
|
|
|
28370
29219
|
*
|
|
28371
29220
|
* @private function of `useChatInputAreaComposer`
|
|
28372
29221
|
*/
|
|
28373
|
-
function resolveTextareaSelection(textareaElement, messageContent, selectionStart, selectionEnd) {
|
|
29222
|
+
function resolveTextareaSelection$1(textareaElement, messageContent, selectionStart, selectionEnd) {
|
|
28374
29223
|
var _a, _b;
|
|
28375
29224
|
const resolvedSelectionStart = (_a = selectionStart !== null && selectionStart !== void 0 ? selectionStart : textareaElement.selectionStart) !== null && _a !== void 0 ? _a : messageContent.length;
|
|
28376
29225
|
const resolvedSelectionEnd = (_b = selectionEnd !== null && selectionEnd !== void 0 ? selectionEnd : textareaElement.selectionEnd) !== null && _b !== void 0 ? _b : resolvedSelectionStart;
|
|
@@ -28428,13 +29277,21 @@ function hasPendingEnterIntentStayedCurrent(params) {
|
|
|
28428
29277
|
areAttachmentSnapshotsEqual(snapshot.attachmentIds, uploadedFiles.map((uploadedFile) => uploadedFile.id)) &&
|
|
28429
29278
|
getReplyingToMessageId(replyingToMessage) === snapshot.replyingToMessageId);
|
|
28430
29279
|
}
|
|
29280
|
+
/**
|
|
29281
|
+
* Returns true when the composer contains text or attachments that can be sent.
|
|
29282
|
+
*
|
|
29283
|
+
* @private function of `useChatInputAreaComposer`
|
|
29284
|
+
*/
|
|
29285
|
+
function hasSendableComposerContent(messageContent, attachmentCount) {
|
|
29286
|
+
return spaceTrim$1(messageContent) !== '' || attachmentCount > 0;
|
|
29287
|
+
}
|
|
28431
29288
|
/**
|
|
28432
29289
|
* Returns true when the deferred Enter snapshot still contains something sendable.
|
|
28433
29290
|
*
|
|
28434
29291
|
* @private function of `useChatInputAreaComposer`
|
|
28435
29292
|
*/
|
|
28436
29293
|
function hasPendingEnterIntentContent(snapshot) {
|
|
28437
|
-
return
|
|
29294
|
+
return hasSendableComposerContent(snapshot.value, snapshot.attachmentIds.length);
|
|
28438
29295
|
}
|
|
28439
29296
|
/**
|
|
28440
29297
|
* Builds the attachment payload expected by `onMessage`.
|
|
@@ -28477,16 +29334,36 @@ async function resolvePendingEnterIntent(params) {
|
|
|
28477
29334
|
handleInsertNewline(snapshot.selectionStart, snapshot.selectionEnd);
|
|
28478
29335
|
}
|
|
28479
29336
|
/**
|
|
28480
|
-
*
|
|
29337
|
+
* Returns the required `onMessage` callback or throws when the composer is misconfigured.
|
|
28481
29338
|
*
|
|
28482
|
-
* @private function of
|
|
29339
|
+
* @private function of `useChatInputAreaComposer`
|
|
28483
29340
|
*/
|
|
28484
|
-
function
|
|
28485
|
-
|
|
28486
|
-
|
|
29341
|
+
function getRequiredOnMessage(onMessage) {
|
|
29342
|
+
if (!onMessage) {
|
|
29343
|
+
throw new Error(`Can not find onMessage callback`);
|
|
29344
|
+
}
|
|
29345
|
+
return onMessage;
|
|
29346
|
+
}
|
|
29347
|
+
/**
|
|
29348
|
+
* Returns the active composer textarea or throws when the DOM node is missing.
|
|
29349
|
+
*
|
|
29350
|
+
* @private function of `useChatInputAreaComposer`
|
|
29351
|
+
*/
|
|
29352
|
+
function getRequiredTextareaElement(textareaRef) {
|
|
29353
|
+
const textareaElement = textareaRef.current;
|
|
29354
|
+
if (!textareaElement) {
|
|
29355
|
+
throw new Error(`Can not find textarea`);
|
|
29356
|
+
}
|
|
29357
|
+
return textareaElement;
|
|
29358
|
+
}
|
|
29359
|
+
/**
|
|
29360
|
+
* Manages controlled message text state and change propagation for the composer.
|
|
29361
|
+
*
|
|
29362
|
+
* @private function of `useChatInputAreaComposer`
|
|
29363
|
+
*/
|
|
29364
|
+
function useChatInputAreaMessageContentState({ defaultMessage, onChange }) {
|
|
28487
29365
|
const [messageContent, setMessageContent] = useState(defaultMessage || '');
|
|
28488
29366
|
const messageContentRef = useRef(messageContent);
|
|
28489
|
-
const isResolvingEnterBehaviorRef = useRef(false);
|
|
28490
29367
|
const applyMessageContent = useCallback((nextContent) => {
|
|
28491
29368
|
messageContentRef.current = nextContent;
|
|
28492
29369
|
setMessageContent(nextContent);
|
|
@@ -28497,6 +29374,22 @@ function useChatInputAreaComposer(props) {
|
|
|
28497
29374
|
messageContentRef.current = nextDefaultMessage;
|
|
28498
29375
|
setMessageContent(nextDefaultMessage);
|
|
28499
29376
|
}, [defaultMessage]);
|
|
29377
|
+
const handleTextInputChange = useCallback((event) => {
|
|
29378
|
+
applyMessageContent(event.target.value);
|
|
29379
|
+
}, [applyMessageContent]);
|
|
29380
|
+
return {
|
|
29381
|
+
messageContent,
|
|
29382
|
+
messageContentRef,
|
|
29383
|
+
applyMessageContent,
|
|
29384
|
+
handleTextInputChange,
|
|
29385
|
+
};
|
|
29386
|
+
}
|
|
29387
|
+
/**
|
|
29388
|
+
* Applies initial-focus behavior for the composer textarea.
|
|
29389
|
+
*
|
|
29390
|
+
* @private function of `useChatInputAreaComposer`
|
|
29391
|
+
*/
|
|
29392
|
+
function useChatInputAreaComposerFocus({ textareaRef, isFocusedOnLoad, isMobile, }) {
|
|
28500
29393
|
useEffect(( /* Focus textarea on page load */) => {
|
|
28501
29394
|
if (!textareaRef.current) {
|
|
28502
29395
|
return;
|
|
@@ -28505,16 +29398,20 @@ function useChatInputAreaComposer(props) {
|
|
|
28505
29398
|
if (shouldFocus) {
|
|
28506
29399
|
textareaRef.current.focus();
|
|
28507
29400
|
}
|
|
28508
|
-
}, [isFocusedOnLoad, isMobile]);
|
|
28509
|
-
|
|
28510
|
-
|
|
28511
|
-
|
|
28512
|
-
|
|
29401
|
+
}, [isFocusedOnLoad, isMobile, textareaRef]);
|
|
29402
|
+
}
|
|
29403
|
+
/**
|
|
29404
|
+
* Creates the newline insertion action used by the composer Enter handling.
|
|
29405
|
+
*
|
|
29406
|
+
* @private function of `useChatInputAreaComposer`
|
|
29407
|
+
*/
|
|
29408
|
+
function useChatInputAreaNewlineHandler({ textareaRef, messageContentRef, applyMessageContent, }) {
|
|
29409
|
+
return useCallback((selectionStart, selectionEnd) => {
|
|
28513
29410
|
const textareaElement = textareaRef.current;
|
|
28514
29411
|
if (!textareaElement) {
|
|
28515
29412
|
return;
|
|
28516
29413
|
}
|
|
28517
|
-
const resolvedSelection = resolveTextareaSelection(textareaElement, messageContentRef.current, selectionStart, selectionEnd);
|
|
29414
|
+
const resolvedSelection = resolveTextareaSelection$1(textareaElement, messageContentRef.current, selectionStart, selectionEnd);
|
|
28518
29415
|
const insertion = insertTextAtSelection({
|
|
28519
29416
|
currentValue: messageContentRef.current,
|
|
28520
29417
|
insertedText: '\n',
|
|
@@ -28523,20 +29420,22 @@ function useChatInputAreaComposer(props) {
|
|
|
28523
29420
|
});
|
|
28524
29421
|
applyMessageContent(insertion.nextValue);
|
|
28525
29422
|
focusTextareaCaret(textareaElement, insertion.caret);
|
|
28526
|
-
}, [applyMessageContent]);
|
|
28527
|
-
|
|
28528
|
-
|
|
28529
|
-
|
|
28530
|
-
|
|
28531
|
-
|
|
28532
|
-
|
|
28533
|
-
|
|
28534
|
-
|
|
29423
|
+
}, [applyMessageContent, messageContentRef, textareaRef]);
|
|
29424
|
+
}
|
|
29425
|
+
/**
|
|
29426
|
+
* Creates the send action while keeping focus restoration and attachments in sync.
|
|
29427
|
+
*
|
|
29428
|
+
* @private function of `useChatInputAreaComposer`
|
|
29429
|
+
*/
|
|
29430
|
+
function useChatInputAreaSendAction({ onMessage, textareaRef, uploadedFiles, clearUploadedFiles, messageContentRef, applyMessageContent, replyingToMessage, onCancelReply, soundSystem, }) {
|
|
29431
|
+
return useCallback(async () => {
|
|
29432
|
+
const onMessageCallback = getRequiredOnMessage(onMessage);
|
|
29433
|
+
const textareaElement = getRequiredTextareaElement(textareaRef);
|
|
28535
29434
|
const wasTextareaFocused = document.activeElement === textareaElement;
|
|
28536
29435
|
try {
|
|
28537
29436
|
const attachments = createMessageAttachments(uploadedFiles);
|
|
28538
29437
|
const contentToSend = messageContentRef.current;
|
|
28539
|
-
if (
|
|
29438
|
+
if (!hasSendableComposerContent(contentToSend, attachments.length)) {
|
|
28540
29439
|
throw new Error(`You need to write some text or upload a file`);
|
|
28541
29440
|
}
|
|
28542
29441
|
if (soundSystem) {
|
|
@@ -28547,7 +29446,7 @@ function useChatInputAreaComposer(props) {
|
|
|
28547
29446
|
if (wasTextareaFocused) {
|
|
28548
29447
|
textareaElement.focus();
|
|
28549
29448
|
}
|
|
28550
|
-
await
|
|
29449
|
+
await onMessageCallback(contentToSend, attachments, replyingToMessage || null);
|
|
28551
29450
|
onCancelReply === null || onCancelReply === void 0 ? void 0 : onCancelReply();
|
|
28552
29451
|
}
|
|
28553
29452
|
catch (error) {
|
|
@@ -28560,12 +29459,22 @@ function useChatInputAreaComposer(props) {
|
|
|
28560
29459
|
}, [
|
|
28561
29460
|
applyMessageContent,
|
|
28562
29461
|
clearUploadedFiles,
|
|
29462
|
+
messageContentRef,
|
|
28563
29463
|
onCancelReply,
|
|
28564
29464
|
onMessage,
|
|
28565
29465
|
replyingToMessage,
|
|
28566
29466
|
soundSystem,
|
|
29467
|
+
textareaRef,
|
|
28567
29468
|
uploadedFiles,
|
|
28568
29469
|
]);
|
|
29470
|
+
}
|
|
29471
|
+
/**
|
|
29472
|
+
* Creates the keyboard handler that coordinates reply cancel, send, newline, and deferred Enter behavior.
|
|
29473
|
+
*
|
|
29474
|
+
* @private function of `useChatInputAreaComposer`
|
|
29475
|
+
*/
|
|
29476
|
+
function useChatInputAreaEnterKeyHandler({ textareaRef, enterBehavior, resolveEnterBehavior, messageContentRef, uploadedFilesRef, replyingToMessage, onCancelReply, handleInsertNewline, handleSend, }) {
|
|
29477
|
+
const isResolvingEnterBehaviorRef = useRef(false);
|
|
28569
29478
|
const handleImmediateEnterAction = useCallback((isCtrlPressed) => {
|
|
28570
29479
|
const resolvedAction = resolveChatEnterAction(enterBehavior || 'SEND', isCtrlPressed);
|
|
28571
29480
|
if (resolvedAction === 'SEND') {
|
|
@@ -28600,30 +29509,78 @@ function useChatInputAreaComposer(props) {
|
|
|
28600
29509
|
}).finally(() => {
|
|
28601
29510
|
isResolvingEnterBehaviorRef.current = false;
|
|
28602
29511
|
});
|
|
28603
|
-
}, [handleInsertNewline, handleSend, replyingToMessage, uploadedFilesRef]);
|
|
28604
|
-
const
|
|
28605
|
-
if (event.key
|
|
28606
|
-
|
|
28607
|
-
onCancelReply();
|
|
28608
|
-
return;
|
|
29512
|
+
}, [handleInsertNewline, handleSend, messageContentRef, replyingToMessage, textareaRef, uploadedFilesRef]);
|
|
29513
|
+
const handleReplyCancelShortcut = useCallback((event) => {
|
|
29514
|
+
if (event.key !== 'Escape' || !replyingToMessage || !onCancelReply) {
|
|
29515
|
+
return false;
|
|
28609
29516
|
}
|
|
29517
|
+
event.preventDefault();
|
|
29518
|
+
onCancelReply();
|
|
29519
|
+
return true;
|
|
29520
|
+
}, [onCancelReply, replyingToMessage]);
|
|
29521
|
+
const handleComposerEnterKeyDown = useCallback((event) => {
|
|
28610
29522
|
if (!isComposerEnterAction(event)) {
|
|
28611
|
-
return;
|
|
29523
|
+
return false;
|
|
28612
29524
|
}
|
|
28613
29525
|
event.preventDefault();
|
|
28614
29526
|
if (shouldResolveDeferredEnterBehavior(enterBehavior, event.ctrlKey, resolveEnterBehavior)) {
|
|
28615
29527
|
handleDeferredEnterAction(resolveEnterBehavior);
|
|
28616
|
-
return;
|
|
29528
|
+
return true;
|
|
28617
29529
|
}
|
|
28618
29530
|
handleImmediateEnterAction(event.ctrlKey);
|
|
28619
|
-
|
|
28620
|
-
|
|
28621
|
-
|
|
28622
|
-
|
|
28623
|
-
|
|
29531
|
+
return true;
|
|
29532
|
+
}, [enterBehavior, handleDeferredEnterAction, handleImmediateEnterAction, resolveEnterBehavior]);
|
|
29533
|
+
return useCallback((event) => {
|
|
29534
|
+
if (handleReplyCancelShortcut(event)) {
|
|
29535
|
+
return;
|
|
29536
|
+
}
|
|
29537
|
+
handleComposerEnterKeyDown(event);
|
|
29538
|
+
}, [handleComposerEnterKeyDown, handleReplyCancelShortcut]);
|
|
29539
|
+
}
|
|
29540
|
+
/**
|
|
29541
|
+
* Manages textarea state, send/newline behavior, and deferred Enter resolution for `<ChatInputArea/>`.
|
|
29542
|
+
*
|
|
29543
|
+
* @private function of `<ChatInputArea/>`
|
|
29544
|
+
*/
|
|
29545
|
+
function useChatInputAreaComposer(props) {
|
|
29546
|
+
const { onMessage, onChange, defaultMessage, enterBehavior, resolveEnterBehavior, isFocusedOnLoad, isMobile, uploadedFiles, uploadedFilesRef, clearUploadedFiles, replyingToMessage, onCancelReply, soundSystem, } = props;
|
|
29547
|
+
const textareaRef = useRef(null);
|
|
29548
|
+
const { messageContent, messageContentRef, applyMessageContent, handleTextInputChange } = useChatInputAreaMessageContentState({
|
|
29549
|
+
defaultMessage,
|
|
29550
|
+
onChange,
|
|
29551
|
+
});
|
|
29552
|
+
useChatInputAreaComposerFocus({
|
|
29553
|
+
textareaRef,
|
|
29554
|
+
isFocusedOnLoad,
|
|
29555
|
+
isMobile,
|
|
29556
|
+
});
|
|
29557
|
+
const handleInsertNewline = useChatInputAreaNewlineHandler({
|
|
29558
|
+
textareaRef,
|
|
29559
|
+
messageContentRef,
|
|
29560
|
+
applyMessageContent,
|
|
29561
|
+
});
|
|
29562
|
+
const handleSend = useChatInputAreaSendAction({
|
|
29563
|
+
onMessage,
|
|
29564
|
+
textareaRef,
|
|
29565
|
+
uploadedFiles,
|
|
29566
|
+
clearUploadedFiles,
|
|
29567
|
+
messageContentRef,
|
|
29568
|
+
applyMessageContent,
|
|
28624
29569
|
replyingToMessage,
|
|
29570
|
+
onCancelReply,
|
|
29571
|
+
soundSystem,
|
|
29572
|
+
});
|
|
29573
|
+
const handleComposerKeyDown = useChatInputAreaEnterKeyHandler({
|
|
29574
|
+
textareaRef,
|
|
29575
|
+
enterBehavior,
|
|
28625
29576
|
resolveEnterBehavior,
|
|
28626
|
-
|
|
29577
|
+
messageContentRef,
|
|
29578
|
+
uploadedFilesRef,
|
|
29579
|
+
replyingToMessage,
|
|
29580
|
+
onCancelReply,
|
|
29581
|
+
handleInsertNewline,
|
|
29582
|
+
handleSend,
|
|
29583
|
+
});
|
|
28627
29584
|
return {
|
|
28628
29585
|
textareaRef,
|
|
28629
29586
|
messageContent,
|
|
@@ -29139,33 +30096,122 @@ function createDictationChunkId() {
|
|
|
29139
30096
|
return `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
29140
30097
|
}
|
|
29141
30098
|
/**
|
|
29142
|
-
*
|
|
30099
|
+
* Resolves the current textarea selection with a stable fallback to the message length.
|
|
29143
30100
|
*
|
|
29144
|
-
* @private function of
|
|
30101
|
+
* @private function of `useChatInputAreaDictation`
|
|
29145
30102
|
*/
|
|
29146
|
-
function
|
|
29147
|
-
|
|
29148
|
-
const
|
|
29149
|
-
const
|
|
29150
|
-
|
|
29151
|
-
|
|
29152
|
-
|
|
29153
|
-
|
|
29154
|
-
|
|
29155
|
-
|
|
29156
|
-
|
|
29157
|
-
|
|
29158
|
-
|
|
29159
|
-
|
|
29160
|
-
|
|
29161
|
-
const
|
|
29162
|
-
|
|
29163
|
-
|
|
29164
|
-
|
|
29165
|
-
|
|
29166
|
-
|
|
29167
|
-
}
|
|
29168
|
-
|
|
30103
|
+
function resolveTextareaSelection(textareaElement, fallbackLength) {
|
|
30104
|
+
var _a, _b;
|
|
30105
|
+
const selectionStart = (_a = textareaElement.selectionStart) !== null && _a !== void 0 ? _a : fallbackLength;
|
|
30106
|
+
const selectionEnd = (_b = textareaElement.selectionEnd) !== null && _b !== void 0 ? _b : selectionStart;
|
|
30107
|
+
return {
|
|
30108
|
+
selectionStart,
|
|
30109
|
+
selectionEnd,
|
|
30110
|
+
};
|
|
30111
|
+
}
|
|
30112
|
+
/**
|
|
30113
|
+
* Creates the persisted metadata entry for one finalized dictated chunk.
|
|
30114
|
+
*
|
|
30115
|
+
* @private function of `useChatInputAreaDictation`
|
|
30116
|
+
*/
|
|
30117
|
+
function createFinalizedDictationChunk(params) {
|
|
30118
|
+
const { beforeValue, finalText, start } = params;
|
|
30119
|
+
return {
|
|
30120
|
+
id: createDictationChunkId(),
|
|
30121
|
+
beforeValue,
|
|
30122
|
+
finalText,
|
|
30123
|
+
start,
|
|
30124
|
+
};
|
|
30125
|
+
}
|
|
30126
|
+
/**
|
|
30127
|
+
* Clears the pending stop fallback timeout when one exists.
|
|
30128
|
+
*
|
|
30129
|
+
* @private function of `useChatInputAreaDictation`
|
|
30130
|
+
*/
|
|
30131
|
+
function clearPendingStopFallbackTimeout(pendingStopFallbackRef) {
|
|
30132
|
+
if (!pendingStopFallbackRef.current) {
|
|
30133
|
+
return;
|
|
30134
|
+
}
|
|
30135
|
+
clearTimeout(pendingStopFallbackRef.current);
|
|
30136
|
+
pendingStopFallbackRef.current = null;
|
|
30137
|
+
}
|
|
30138
|
+
/**
|
|
30139
|
+
* Captures whether the next finalized chunk should replace the current selection.
|
|
30140
|
+
*
|
|
30141
|
+
* @private function of `useChatInputAreaDictation`
|
|
30142
|
+
*/
|
|
30143
|
+
function captureSelectionReplacementIntent(textareaRef, replaceSelectionOnNextFinalRef) {
|
|
30144
|
+
const textarea = textareaRef.current;
|
|
30145
|
+
if (!textarea) {
|
|
30146
|
+
return;
|
|
30147
|
+
}
|
|
30148
|
+
const { selectionStart, selectionEnd } = resolveTextareaSelection(textarea, 0);
|
|
30149
|
+
replaceSelectionOnNextFinalRef.current = selectionStart !== selectionEnd;
|
|
30150
|
+
}
|
|
30151
|
+
/**
|
|
30152
|
+
* Produces the corrected chunk value only when a real correction should be applied.
|
|
30153
|
+
*
|
|
30154
|
+
* @private function of `useChatInputAreaDictation`
|
|
30155
|
+
*/
|
|
30156
|
+
function resolveCorrectedDictationChunk(dictationEditableChunk, dictationLastFinalChunk) {
|
|
30157
|
+
const correctedChunk = normalizeDictationWhitespace(dictationEditableChunk);
|
|
30158
|
+
if (!correctedChunk || !dictationLastFinalChunk || correctedChunk === dictationLastFinalChunk) {
|
|
30159
|
+
return null;
|
|
30160
|
+
}
|
|
30161
|
+
return correctedChunk;
|
|
30162
|
+
}
|
|
30163
|
+
/**
|
|
30164
|
+
* Replaces the last tracked chunk text inside the tracked chunk list.
|
|
30165
|
+
*
|
|
30166
|
+
* @private function of `useChatInputAreaDictation`
|
|
30167
|
+
*/
|
|
30168
|
+
function replaceLastDictationChunk(previousChunks, correctedChunk) {
|
|
30169
|
+
if (previousChunks.length === 0) {
|
|
30170
|
+
return previousChunks;
|
|
30171
|
+
}
|
|
30172
|
+
const nextChunks = [...previousChunks];
|
|
30173
|
+
const lastChunk = nextChunks[nextChunks.length - 1];
|
|
30174
|
+
if (!lastChunk) {
|
|
30175
|
+
return previousChunks;
|
|
30176
|
+
}
|
|
30177
|
+
nextChunks[nextChunks.length - 1] = {
|
|
30178
|
+
...lastChunk,
|
|
30179
|
+
finalText: correctedChunk,
|
|
30180
|
+
};
|
|
30181
|
+
return nextChunks;
|
|
30182
|
+
}
|
|
30183
|
+
/**
|
|
30184
|
+
* Opens browser microphone settings in a new tab when a settings URL is available.
|
|
30185
|
+
*
|
|
30186
|
+
* @private function of `useChatInputAreaDictation`
|
|
30187
|
+
*/
|
|
30188
|
+
function openBrowserSettings(microphoneSettingsUrl) {
|
|
30189
|
+
if (!microphoneSettingsUrl) {
|
|
30190
|
+
return;
|
|
30191
|
+
}
|
|
30192
|
+
window.open(microphoneSettingsUrl, '_blank', 'noopener,noreferrer');
|
|
30193
|
+
}
|
|
30194
|
+
/**
|
|
30195
|
+
* Resolves whether the dictation panel should be visible for the current state snapshot.
|
|
30196
|
+
*
|
|
30197
|
+
* @private function of `useChatInputAreaDictation`
|
|
30198
|
+
*/
|
|
30199
|
+
function resolveShouldShowDictationPanel(params) {
|
|
30200
|
+
const { speechRecognition, isDictationPanelExpanded, dictationUiState, dictationInterimText, dictationError, dictationLastFinalChunk, } = params;
|
|
30201
|
+
return Boolean(speechRecognition &&
|
|
30202
|
+
(isDictationPanelExpanded ||
|
|
30203
|
+
dictationUiState !== 'idle' ||
|
|
30204
|
+
Boolean(dictationInterimText) ||
|
|
30205
|
+
Boolean(dictationError) ||
|
|
30206
|
+
Boolean(dictationLastFinalChunk)));
|
|
30207
|
+
}
|
|
30208
|
+
/**
|
|
30209
|
+
* Focuses the textarea and restores a given selection range after programmatic edits.
|
|
30210
|
+
*
|
|
30211
|
+
* @private function of `useChatInputAreaDictation`
|
|
30212
|
+
*/
|
|
30213
|
+
function useTextareaSelectionFocus(textareaRef) {
|
|
30214
|
+
return useCallback((selectionStart, selectionEnd) => {
|
|
29169
30215
|
requestAnimationFrame(() => {
|
|
29170
30216
|
const textarea = textareaRef.current;
|
|
29171
30217
|
if (!textarea) {
|
|
@@ -29175,8 +30221,15 @@ function useChatInputAreaDictation({ speechRecognition, speechRecognitionLanguag
|
|
|
29175
30221
|
textarea.setSelectionRange(selectionStart, selectionEnd);
|
|
29176
30222
|
});
|
|
29177
30223
|
}, [textareaRef]);
|
|
29178
|
-
|
|
29179
|
-
|
|
30224
|
+
}
|
|
30225
|
+
/**
|
|
30226
|
+
* Builds the final-result handler that refines text, inserts it, and exposes it for correction.
|
|
30227
|
+
*
|
|
30228
|
+
* @private function of `useChatInputAreaDictation`
|
|
30229
|
+
*/
|
|
30230
|
+
function useChatInputAreaDictationFinalResultHandler({ textareaRef, messageContentRef, applyMessageContent, dictationSettings, dictationDictionary, replaceSelectionOnNextFinalRef, focusTextareaSelection, state, }) {
|
|
30231
|
+
const { setDictationUiState, setDictationInterimText, setDictationError, setDictationLastFinalChunk, setDictationEditableChunk, setDictationChunks, setIsDictationPanelExpanded, } = state;
|
|
30232
|
+
return useCallback((rawText) => {
|
|
29180
30233
|
const textarea = textareaRef.current;
|
|
29181
30234
|
if (!textarea) {
|
|
29182
30235
|
return;
|
|
@@ -29186,8 +30239,7 @@ function useChatInputAreaDictation({ speechRecognition, speechRecognitionLanguag
|
|
|
29186
30239
|
if (!refinedText) {
|
|
29187
30240
|
return;
|
|
29188
30241
|
}
|
|
29189
|
-
const selectionStart
|
|
29190
|
-
const selectionEnd = (_b = textarea.selectionEnd) !== null && _b !== void 0 ? _b : selectionStart;
|
|
30242
|
+
const { selectionStart, selectionEnd } = resolveTextareaSelection(textarea, previousMessageContent.length);
|
|
29191
30243
|
const insertion = insertDictationChunk({
|
|
29192
30244
|
currentValue: previousMessageContent,
|
|
29193
30245
|
dictatedText: refinedText,
|
|
@@ -29202,12 +30254,11 @@ function useChatInputAreaDictation({ speechRecognition, speechRecognitionLanguag
|
|
|
29202
30254
|
applyMessageContent(insertion.nextValue);
|
|
29203
30255
|
setDictationChunks((previousChunks) => [
|
|
29204
30256
|
...previousChunks,
|
|
29205
|
-
{
|
|
29206
|
-
id: createDictationChunkId(),
|
|
30257
|
+
createFinalizedDictationChunk({
|
|
29207
30258
|
beforeValue: previousMessageContent,
|
|
29208
30259
|
finalText: refinedText,
|
|
29209
30260
|
start: insertion.start,
|
|
29210
|
-
},
|
|
30261
|
+
}),
|
|
29211
30262
|
]);
|
|
29212
30263
|
setDictationLastFinalChunk(refinedText);
|
|
29213
30264
|
setDictationEditableChunk(refinedText);
|
|
@@ -29219,9 +30270,25 @@ function useChatInputAreaDictation({ speechRecognition, speechRecognitionLanguag
|
|
|
29219
30270
|
dictationSettings,
|
|
29220
30271
|
focusTextareaSelection,
|
|
29221
30272
|
messageContentRef,
|
|
30273
|
+
replaceSelectionOnNextFinalRef,
|
|
30274
|
+
setDictationChunks,
|
|
30275
|
+
setDictationEditableChunk,
|
|
30276
|
+
setDictationError,
|
|
30277
|
+
setDictationInterimText,
|
|
30278
|
+
setDictationLastFinalChunk,
|
|
30279
|
+
setDictationUiState,
|
|
30280
|
+
setIsDictationPanelExpanded,
|
|
29222
30281
|
textareaRef,
|
|
29223
30282
|
]);
|
|
29224
|
-
|
|
30283
|
+
}
|
|
30284
|
+
/**
|
|
30285
|
+
* Builds the event handler that translates speech-recognition events into dictation UI updates.
|
|
30286
|
+
*
|
|
30287
|
+
* @private function of `useChatInputAreaDictation`
|
|
30288
|
+
*/
|
|
30289
|
+
function useChatInputAreaDictationSpeechRecognitionEventHandler({ clearPendingStopFallback, handleDictationFinalResult, state, }) {
|
|
30290
|
+
const { setDictationUiState, setDictationInterimText, setDictationError, setIsDictationPanelExpanded } = state;
|
|
30291
|
+
return useCallback((event) => {
|
|
29225
30292
|
switch (event.type) {
|
|
29226
30293
|
case 'START':
|
|
29227
30294
|
clearPendingStopFallback();
|
|
@@ -29255,7 +30322,21 @@ function useChatInputAreaDictation({ speechRecognition, speechRecognitionLanguag
|
|
|
29255
30322
|
setDictationInterimText('');
|
|
29256
30323
|
return;
|
|
29257
30324
|
}
|
|
29258
|
-
}, [
|
|
30325
|
+
}, [
|
|
30326
|
+
clearPendingStopFallback,
|
|
30327
|
+
handleDictationFinalResult,
|
|
30328
|
+
setDictationError,
|
|
30329
|
+
setDictationInterimText,
|
|
30330
|
+
setDictationUiState,
|
|
30331
|
+
setIsDictationPanelExpanded,
|
|
30332
|
+
]);
|
|
30333
|
+
}
|
|
30334
|
+
/**
|
|
30335
|
+
* Wires the speech-recognition subscription and shutdown cleanup for dictation.
|
|
30336
|
+
*
|
|
30337
|
+
* @private function of `useChatInputAreaDictation`
|
|
30338
|
+
*/
|
|
30339
|
+
function useChatInputAreaDictationSpeechRecognitionLifecycle({ speechRecognition, clearPendingStopFallback, handleSpeechRecognitionEvent, }) {
|
|
29259
30340
|
useEffect(() => {
|
|
29260
30341
|
if (!speechRecognition) {
|
|
29261
30342
|
return;
|
|
@@ -29272,26 +30353,37 @@ function useChatInputAreaDictation({ speechRecognition, speechRecognitionLanguag
|
|
|
29272
30353
|
speechRecognition === null || speechRecognition === void 0 ? void 0 : speechRecognition.$stop();
|
|
29273
30354
|
};
|
|
29274
30355
|
}, [clearPendingStopFallback, speechRecognition]);
|
|
30356
|
+
}
|
|
30357
|
+
/**
|
|
30358
|
+
* Builds start/stop/toggle handlers for the speech-recognition session.
|
|
30359
|
+
*
|
|
30360
|
+
* @private function of `useChatInputAreaDictation`
|
|
30361
|
+
*/
|
|
30362
|
+
function useChatInputAreaDictationVoiceInputControls({ speechRecognition, textareaRef, resolvedSpeechRecognitionLanguage, whisperMode, pendingStopFallbackRef, replaceSelectionOnNextFinalRef, clearPendingStopFallback, state, }) {
|
|
30363
|
+
const { dictationUiState, setDictationUiState, setDictationInterimText, setDictationError } = state;
|
|
29275
30364
|
const startVoiceInput = useCallback(() => {
|
|
29276
|
-
var _a, _b;
|
|
29277
30365
|
if (!speechRecognition) {
|
|
29278
30366
|
return;
|
|
29279
30367
|
}
|
|
29280
|
-
|
|
29281
|
-
if (textarea) {
|
|
29282
|
-
const selectionStart = (_a = textarea.selectionStart) !== null && _a !== void 0 ? _a : 0;
|
|
29283
|
-
const selectionEnd = (_b = textarea.selectionEnd) !== null && _b !== void 0 ? _b : selectionStart;
|
|
29284
|
-
replaceSelectionOnNextFinalRef.current = selectionStart !== selectionEnd;
|
|
29285
|
-
}
|
|
30368
|
+
captureSelectionReplacementIntent(textareaRef, replaceSelectionOnNextFinalRef);
|
|
29286
30369
|
setDictationError(null);
|
|
29287
30370
|
setDictationInterimText('');
|
|
29288
30371
|
setDictationUiState('listening');
|
|
29289
30372
|
speechRecognition.$start({
|
|
29290
30373
|
language: resolvedSpeechRecognitionLanguage,
|
|
29291
30374
|
interimResults: true,
|
|
29292
|
-
whisperMode
|
|
30375
|
+
whisperMode,
|
|
29293
30376
|
});
|
|
29294
|
-
}, [
|
|
30377
|
+
}, [
|
|
30378
|
+
replaceSelectionOnNextFinalRef,
|
|
30379
|
+
resolvedSpeechRecognitionLanguage,
|
|
30380
|
+
setDictationError,
|
|
30381
|
+
setDictationInterimText,
|
|
30382
|
+
setDictationUiState,
|
|
30383
|
+
speechRecognition,
|
|
30384
|
+
textareaRef,
|
|
30385
|
+
whisperMode,
|
|
30386
|
+
]);
|
|
29295
30387
|
const stopVoiceInput = useCallback(() => {
|
|
29296
30388
|
if (!speechRecognition) {
|
|
29297
30389
|
return;
|
|
@@ -29303,7 +30395,13 @@ function useChatInputAreaDictation({ speechRecognition, speechRecognitionLanguag
|
|
|
29303
30395
|
setDictationUiState('idle');
|
|
29304
30396
|
setDictationInterimText('');
|
|
29305
30397
|
}, STOP_LISTENING_FALLBACK_TIMEOUT_MS);
|
|
29306
|
-
}, [
|
|
30398
|
+
}, [
|
|
30399
|
+
clearPendingStopFallback,
|
|
30400
|
+
pendingStopFallbackRef,
|
|
30401
|
+
setDictationInterimText,
|
|
30402
|
+
setDictationUiState,
|
|
30403
|
+
speechRecognition,
|
|
30404
|
+
]);
|
|
29307
30405
|
const handleToggleVoiceInput = useCallback(() => {
|
|
29308
30406
|
if (!speechRecognition) {
|
|
29309
30407
|
return;
|
|
@@ -29314,6 +30412,22 @@ function useChatInputAreaDictation({ speechRecognition, speechRecognitionLanguag
|
|
|
29314
30412
|
}
|
|
29315
30413
|
startVoiceInput();
|
|
29316
30414
|
}, [dictationUiState, speechRecognition, startVoiceInput, stopVoiceInput]);
|
|
30415
|
+
const handleRetryPermissionRequest = useCallback(() => {
|
|
30416
|
+
setDictationError(null);
|
|
30417
|
+
setDictationUiState('idle');
|
|
30418
|
+
handleToggleVoiceInput();
|
|
30419
|
+
}, [handleToggleVoiceInput, setDictationError, setDictationUiState]);
|
|
30420
|
+
return {
|
|
30421
|
+
handleToggleVoiceInput,
|
|
30422
|
+
handleRetryPermissionRequest,
|
|
30423
|
+
};
|
|
30424
|
+
}
|
|
30425
|
+
/**
|
|
30426
|
+
* Builds the correction and backtrack handlers for the latest finalized chunk.
|
|
30427
|
+
*
|
|
30428
|
+
* @private function of `useChatInputAreaDictation`
|
|
30429
|
+
*/
|
|
30430
|
+
function useChatInputAreaDictationCorrectionHandlers({ dictationChunks, dictationLastFinalChunk, dictationEditableChunk, dictationDictionary, messageContentRef, applyMessageContent, focusTextareaSelection, setDictationChunks, setDictationLastFinalChunk, setDictationEditableChunk, setDictationDictionary, }) {
|
|
29317
30431
|
const handleBacktrackLastChunk = useCallback(() => {
|
|
29318
30432
|
var _a;
|
|
29319
30433
|
const previousChunks = [...dictationChunks];
|
|
@@ -29327,50 +30441,123 @@ function useChatInputAreaDictation({ speechRecognition, speechRecognitionLanguag
|
|
|
29327
30441
|
setDictationLastFinalChunk(previousFinalChunk);
|
|
29328
30442
|
setDictationEditableChunk(previousFinalChunk);
|
|
29329
30443
|
focusTextareaSelection(lastChunk.start, lastChunk.start);
|
|
29330
|
-
}, [
|
|
30444
|
+
}, [
|
|
30445
|
+
applyMessageContent,
|
|
30446
|
+
dictationChunks,
|
|
30447
|
+
focusTextareaSelection,
|
|
30448
|
+
setDictationChunks,
|
|
30449
|
+
setDictationEditableChunk,
|
|
30450
|
+
setDictationLastFinalChunk,
|
|
30451
|
+
]);
|
|
29331
30452
|
const handleApplyCorrection = useCallback(() => {
|
|
29332
|
-
const correctedChunk =
|
|
29333
|
-
|
|
29334
|
-
if (!correctedChunk || !previousChunk || correctedChunk === previousChunk) {
|
|
30453
|
+
const correctedChunk = resolveCorrectedDictationChunk(dictationEditableChunk, dictationLastFinalChunk);
|
|
30454
|
+
if (!correctedChunk) {
|
|
29335
30455
|
return;
|
|
29336
30456
|
}
|
|
29337
|
-
const nextMessageContent = replaceLastOccurrence(messageContentRef.current,
|
|
30457
|
+
const nextMessageContent = replaceLastOccurrence(messageContentRef.current, dictationLastFinalChunk, correctedChunk);
|
|
29338
30458
|
applyMessageContent(nextMessageContent);
|
|
29339
30459
|
setDictationLastFinalChunk(correctedChunk);
|
|
29340
|
-
setDictationChunks((previousChunks) =>
|
|
29341
|
-
|
|
29342
|
-
return previousChunks;
|
|
29343
|
-
}
|
|
29344
|
-
const nextChunks = [...previousChunks];
|
|
29345
|
-
const lastChunk = nextChunks[nextChunks.length - 1];
|
|
29346
|
-
if (!lastChunk) {
|
|
29347
|
-
return previousChunks;
|
|
29348
|
-
}
|
|
29349
|
-
nextChunks[nextChunks.length - 1] = {
|
|
29350
|
-
...lastChunk,
|
|
29351
|
-
finalText: correctedChunk,
|
|
29352
|
-
};
|
|
29353
|
-
return nextChunks;
|
|
29354
|
-
});
|
|
29355
|
-
setDictationDictionary(learnDictationDictionary(previousChunk, correctedChunk, dictationDictionary));
|
|
30460
|
+
setDictationChunks((previousChunks) => replaceLastDictationChunk(previousChunks, correctedChunk));
|
|
30461
|
+
setDictationDictionary(learnDictationDictionary(dictationLastFinalChunk, correctedChunk, dictationDictionary));
|
|
29356
30462
|
}, [
|
|
29357
30463
|
applyMessageContent,
|
|
29358
30464
|
dictationDictionary,
|
|
29359
30465
|
dictationEditableChunk,
|
|
29360
30466
|
dictationLastFinalChunk,
|
|
29361
30467
|
messageContentRef,
|
|
30468
|
+
setDictationChunks,
|
|
29362
30469
|
setDictationDictionary,
|
|
30470
|
+
setDictationLastFinalChunk,
|
|
29363
30471
|
]);
|
|
29364
|
-
|
|
29365
|
-
|
|
29366
|
-
|
|
29367
|
-
|
|
29368
|
-
|
|
30472
|
+
return {
|
|
30473
|
+
handleBacktrackLastChunk,
|
|
30474
|
+
handleApplyCorrection,
|
|
30475
|
+
};
|
|
30476
|
+
}
|
|
30477
|
+
/**
|
|
30478
|
+
* Manages speech-recognition state, transcript refinement, and correction UI.
|
|
30479
|
+
*
|
|
30480
|
+
* @private function of `<ChatInputArea/>`
|
|
30481
|
+
*/
|
|
30482
|
+
function useChatInputAreaDictation({ speechRecognition, speechRecognitionLanguage, textareaRef, messageContentRef, applyMessageContent, }) {
|
|
30483
|
+
const pendingStopFallbackRef = useRef(null);
|
|
30484
|
+
const replaceSelectionOnNextFinalRef = useRef(false);
|
|
30485
|
+
const [dictationUiState, setDictationUiState] = useState('idle');
|
|
30486
|
+
const [dictationInterimText, setDictationInterimText] = useState('');
|
|
30487
|
+
const [dictationError, setDictationError] = useState(null);
|
|
30488
|
+
const [dictationLastFinalChunk, setDictationLastFinalChunk] = useState('');
|
|
30489
|
+
const [dictationEditableChunk, setDictationEditableChunk] = useState('');
|
|
30490
|
+
const [dictationChunks, setDictationChunks] = useState([]);
|
|
30491
|
+
const [isDictationPanelExpanded, setIsDictationPanelExpanded] = useState(false);
|
|
30492
|
+
const { dictationSettings, setDictationSettings, dictationDictionary, setDictationDictionary } = useChatInputAreaDictationPersistence();
|
|
30493
|
+
const { speechRecognitionUiDescriptor, resolvedSpeechRecognitionLanguage, isBrowserSpeechFallbackSupported, microphoneSettingsUrl, } = useChatInputAreaDictationSupport({
|
|
30494
|
+
dictationUiState,
|
|
30495
|
+
speechRecognitionLanguage,
|
|
30496
|
+
});
|
|
30497
|
+
const dictationState = {
|
|
30498
|
+
dictationUiState,
|
|
30499
|
+
setDictationUiState,
|
|
30500
|
+
setDictationInterimText,
|
|
30501
|
+
setDictationError,
|
|
30502
|
+
setDictationLastFinalChunk,
|
|
30503
|
+
setDictationEditableChunk,
|
|
30504
|
+
setDictationChunks,
|
|
30505
|
+
setIsDictationPanelExpanded,
|
|
30506
|
+
};
|
|
30507
|
+
const clearPendingStopFallback = useCallback(() => {
|
|
30508
|
+
clearPendingStopFallbackTimeout(pendingStopFallbackRef);
|
|
30509
|
+
}, []);
|
|
30510
|
+
const focusTextareaSelection = useTextareaSelectionFocus(textareaRef);
|
|
30511
|
+
const handleDictationFinalResult = useChatInputAreaDictationFinalResultHandler({
|
|
30512
|
+
textareaRef,
|
|
30513
|
+
messageContentRef,
|
|
30514
|
+
applyMessageContent,
|
|
30515
|
+
dictationSettings,
|
|
30516
|
+
dictationDictionary,
|
|
30517
|
+
replaceSelectionOnNextFinalRef,
|
|
30518
|
+
focusTextareaSelection,
|
|
30519
|
+
state: dictationState,
|
|
30520
|
+
});
|
|
30521
|
+
const handleSpeechRecognitionEvent = useChatInputAreaDictationSpeechRecognitionEventHandler({
|
|
30522
|
+
clearPendingStopFallback,
|
|
30523
|
+
handleDictationFinalResult,
|
|
30524
|
+
state: dictationState,
|
|
30525
|
+
});
|
|
30526
|
+
useChatInputAreaDictationSpeechRecognitionLifecycle({
|
|
30527
|
+
speechRecognition,
|
|
30528
|
+
clearPendingStopFallback,
|
|
30529
|
+
handleSpeechRecognitionEvent,
|
|
30530
|
+
});
|
|
30531
|
+
const { handleToggleVoiceInput, handleRetryPermissionRequest } = useChatInputAreaDictationVoiceInputControls({
|
|
30532
|
+
speechRecognition,
|
|
30533
|
+
textareaRef,
|
|
30534
|
+
resolvedSpeechRecognitionLanguage,
|
|
30535
|
+
whisperMode: dictationSettings.whisperMode,
|
|
30536
|
+
pendingStopFallbackRef,
|
|
30537
|
+
replaceSelectionOnNextFinalRef,
|
|
30538
|
+
clearPendingStopFallback,
|
|
30539
|
+
state: {
|
|
30540
|
+
dictationUiState,
|
|
30541
|
+
setDictationUiState,
|
|
30542
|
+
setDictationInterimText,
|
|
30543
|
+
setDictationError,
|
|
30544
|
+
},
|
|
30545
|
+
});
|
|
30546
|
+
const { handleBacktrackLastChunk, handleApplyCorrection } = useChatInputAreaDictationCorrectionHandlers({
|
|
30547
|
+
dictationChunks,
|
|
30548
|
+
dictationLastFinalChunk,
|
|
30549
|
+
dictationEditableChunk,
|
|
30550
|
+
dictationDictionary,
|
|
30551
|
+
messageContentRef,
|
|
30552
|
+
applyMessageContent,
|
|
30553
|
+
focusTextareaSelection,
|
|
30554
|
+
setDictationChunks,
|
|
30555
|
+
setDictationLastFinalChunk,
|
|
30556
|
+
setDictationEditableChunk,
|
|
30557
|
+
setDictationDictionary,
|
|
30558
|
+
});
|
|
29369
30559
|
const handleOpenBrowserSettings = useCallback(() => {
|
|
29370
|
-
|
|
29371
|
-
return;
|
|
29372
|
-
}
|
|
29373
|
-
window.open(microphoneSettingsUrl, '_blank', 'noopener,noreferrer');
|
|
30560
|
+
openBrowserSettings(microphoneSettingsUrl);
|
|
29374
30561
|
}, [microphoneSettingsUrl]);
|
|
29375
30562
|
const toggleDictationPanel = useCallback(() => {
|
|
29376
30563
|
setIsDictationPanelExpanded((value) => !value);
|
|
@@ -29384,12 +30571,14 @@ function useChatInputAreaDictation({ speechRecognition, speechRecognitionLanguag
|
|
|
29384
30571
|
[settingName]: checked,
|
|
29385
30572
|
}));
|
|
29386
30573
|
}, [setDictationSettings]);
|
|
29387
|
-
const shouldShowDictationPanel =
|
|
29388
|
-
|
|
29389
|
-
|
|
29390
|
-
|
|
29391
|
-
|
|
29392
|
-
|
|
30574
|
+
const shouldShowDictationPanel = resolveShouldShowDictationPanel({
|
|
30575
|
+
speechRecognition,
|
|
30576
|
+
isDictationPanelExpanded,
|
|
30577
|
+
dictationUiState,
|
|
30578
|
+
dictationInterimText,
|
|
30579
|
+
dictationError,
|
|
30580
|
+
dictationLastFinalChunk,
|
|
30581
|
+
});
|
|
29393
30582
|
return {
|
|
29394
30583
|
speechRecognitionUiDescriptor,
|
|
29395
30584
|
shouldShowDictationPanel,
|
|
@@ -29414,13 +30603,206 @@ function useChatInputAreaDictation({ speechRecognition, speechRecognitionLanguag
|
|
|
29414
30603
|
};
|
|
29415
30604
|
}
|
|
29416
30605
|
|
|
30606
|
+
/**
|
|
30607
|
+
* Creates the shared color styles used by the composer.
|
|
30608
|
+
*
|
|
30609
|
+
* @private component of `<ChatInputArea/>`
|
|
30610
|
+
*/
|
|
30611
|
+
function createChatInputAreaColorModel(participants, buttonColor) {
|
|
30612
|
+
var _a;
|
|
30613
|
+
const myColor = ((_a = participants.find((participant) => participant.isMe)) === null || _a === void 0 ? void 0 : _a.color) || USER_CHAT_COLOR;
|
|
30614
|
+
const inputBgColor = Color.from(myColor).then(lighten(0.4)).then(grayscale(0.7));
|
|
30615
|
+
const inputTextColor = inputBgColor.then(textColor);
|
|
30616
|
+
return {
|
|
30617
|
+
inputContainerStyle: {
|
|
30618
|
+
'--chat-placeholder-color': '#fff',
|
|
30619
|
+
'--input-bg-color': inputBgColor.toHex(),
|
|
30620
|
+
'--input-text-color': inputTextColor.toHex(),
|
|
30621
|
+
'--brand-color': buttonColor.toHex(),
|
|
30622
|
+
},
|
|
30623
|
+
primaryButtonStyle: {
|
|
30624
|
+
backgroundColor: buttonColor.toHex(),
|
|
30625
|
+
color: buttonColor.then(textColor).toHex(),
|
|
30626
|
+
},
|
|
30627
|
+
};
|
|
30628
|
+
}
|
|
30629
|
+
/**
|
|
30630
|
+
* Builds the reply preview view model when the composer is replying to a message.
|
|
30631
|
+
*
|
|
30632
|
+
* @private component of `<ChatInputArea/>`
|
|
30633
|
+
*/
|
|
30634
|
+
function createChatInputAreaReplyPreviewModel(params) {
|
|
30635
|
+
const { replyingToMessage, participants, chatUiTranslations, onCancelReply } = params;
|
|
30636
|
+
if (!replyingToMessage) {
|
|
30637
|
+
return null;
|
|
30638
|
+
}
|
|
30639
|
+
const previewText = resolveChatMessageReplyPreviewText(replyingToMessage, {
|
|
30640
|
+
maxLength: 180,
|
|
30641
|
+
emptyLabel: 'Original message',
|
|
30642
|
+
});
|
|
30643
|
+
const senderLabel = resolveChatMessageReplySenderLabel({
|
|
30644
|
+
sender: replyingToMessage.sender,
|
|
30645
|
+
participants,
|
|
30646
|
+
});
|
|
30647
|
+
if (!previewText || !senderLabel) {
|
|
30648
|
+
return null;
|
|
30649
|
+
}
|
|
30650
|
+
return {
|
|
30651
|
+
label: (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.replyingToLabel) || 'Replying to',
|
|
30652
|
+
senderLabel,
|
|
30653
|
+
previewText,
|
|
30654
|
+
dismissLabel: (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.cancelReplyLabel) || 'Cancel reply',
|
|
30655
|
+
onDismiss: onCancelReply || undefined,
|
|
30656
|
+
};
|
|
30657
|
+
}
|
|
30658
|
+
/**
|
|
30659
|
+
* Adds drag-and-drop handlers only when uploads are enabled.
|
|
30660
|
+
*
|
|
30661
|
+
* @private component of `<ChatInputArea/>`
|
|
30662
|
+
*/
|
|
30663
|
+
function createChatInputAreaRootProps(params) {
|
|
30664
|
+
const { onFileUpload, handleDrop, handleDragOver, handleDragLeave } = params;
|
|
30665
|
+
if (!onFileUpload) {
|
|
30666
|
+
return {};
|
|
30667
|
+
}
|
|
30668
|
+
return {
|
|
30669
|
+
onDrop: handleDrop,
|
|
30670
|
+
onDragOver: handleDragOver,
|
|
30671
|
+
onDragLeave: handleDragLeave,
|
|
30672
|
+
};
|
|
30673
|
+
}
|
|
30674
|
+
/**
|
|
30675
|
+
* Formats uploaded file sizes for the preview chips.
|
|
30676
|
+
*
|
|
30677
|
+
* @private component of `<ChatInputArea/>`
|
|
30678
|
+
*/
|
|
30679
|
+
function formatUploadedFileSizeInKilobytes(fileSizeInBytes) {
|
|
30680
|
+
return `${(fileSizeInBytes / 1024).toFixed(1)} KB`;
|
|
30681
|
+
}
|
|
30682
|
+
/**
|
|
30683
|
+
* Renders the optional reply preview section.
|
|
30684
|
+
*
|
|
30685
|
+
* @private component of `<ChatInputArea/>`
|
|
30686
|
+
*/
|
|
30687
|
+
function ChatInputAreaReplyPreviewSection({ replyPreview }) {
|
|
30688
|
+
if (!replyPreview) {
|
|
30689
|
+
return null;
|
|
30690
|
+
}
|
|
30691
|
+
return (jsx(ChatReplyPreview, { label: replyPreview.label, senderLabel: replyPreview.senderLabel, previewText: replyPreview.previewText, className: styles$5.replyComposerPreview, dismissLabel: replyPreview.dismissLabel, onDismiss: replyPreview.onDismiss }));
|
|
30692
|
+
}
|
|
30693
|
+
/**
|
|
30694
|
+
* Renders uploaded file previews above the composer.
|
|
30695
|
+
*
|
|
30696
|
+
* @private component of `<ChatInputArea/>`
|
|
30697
|
+
*/
|
|
30698
|
+
function ChatInputAreaUploadedFilesSection(props) {
|
|
30699
|
+
const { uploadedFiles, removeUploadedFile } = props;
|
|
30700
|
+
if (uploadedFiles.length === 0) {
|
|
30701
|
+
return null;
|
|
30702
|
+
}
|
|
30703
|
+
return (jsx("div", { className: styles$5.filePreviewContainer, children: uploadedFiles.map((uploadedFile) => (jsxs("div", { className: styles$5.filePreview, children: [jsx("div", { className: styles$5.fileIcon, children: "\uD83D\uDCCE" }), jsxs("div", { className: styles$5.fileInfo, children: [jsx("div", { className: styles$5.fileName, children: uploadedFile.file.name }), jsx("div", { className: styles$5.fileSize, children: formatUploadedFileSizeInKilobytes(uploadedFile.file.size) })] }), jsx("button", { className: styles$5.removeFileButton, onClick: () => removeUploadedFile(uploadedFile.id), title: "Remove file", children: jsx(CloseIcon, {}) })] }, uploadedFile.id))) }));
|
|
30704
|
+
}
|
|
30705
|
+
/**
|
|
30706
|
+
* Renders the hidden file input and attachment trigger when uploads are enabled.
|
|
30707
|
+
*
|
|
30708
|
+
* @private component of `<ChatInputArea/>`
|
|
30709
|
+
*/
|
|
30710
|
+
function ChatInputAreaAttachmentButton(props) {
|
|
30711
|
+
const { onFileUpload, fileInputRef, handleFileInputChange, onButtonClick, openFilePicker, isUploading, primaryButtonStyle, } = props;
|
|
30712
|
+
if (!onFileUpload) {
|
|
30713
|
+
return null;
|
|
30714
|
+
}
|
|
30715
|
+
return (jsxs(Fragment, { children: [jsx("input", { ref: fileInputRef, type: "file", multiple: true, style: { display: 'none' }, onChange: handleFileInputChange }), jsx("button", { type: "button", style: primaryButtonStyle, className: classNames(styles$5.attachmentButton, chatCssClassNames.inputAttachmentButton), onClick: onButtonClick(openFilePicker), disabled: isUploading, title: "Attach file", children: jsx(AttachmentIcon, { size: 20 }) })] }));
|
|
30716
|
+
}
|
|
30717
|
+
/**
|
|
30718
|
+
* Creates the voice button colors from the current dictation state.
|
|
30719
|
+
*
|
|
30720
|
+
* @private component of `<ChatInputArea/>`
|
|
30721
|
+
*/
|
|
30722
|
+
function createVoiceButtonStyle(params) {
|
|
30723
|
+
const { buttonColor, speechRecognitionUiDescriptor } = params;
|
|
30724
|
+
if (speechRecognitionUiDescriptor.isButtonActive) {
|
|
30725
|
+
return {
|
|
30726
|
+
backgroundColor: Color.from('#ff4444').toHex(),
|
|
30727
|
+
color: Color.from('#ffffff').toHex(),
|
|
30728
|
+
};
|
|
30729
|
+
}
|
|
30730
|
+
return {
|
|
30731
|
+
backgroundColor: buttonColor.toHex(),
|
|
30732
|
+
color: buttonColor.then(textColor).toHex(),
|
|
30733
|
+
};
|
|
30734
|
+
}
|
|
30735
|
+
/**
|
|
30736
|
+
* Renders the voice input trigger when speech recognition is available.
|
|
30737
|
+
*
|
|
30738
|
+
* @private component of `<ChatInputArea/>`
|
|
30739
|
+
*/
|
|
30740
|
+
function ChatInputAreaVoiceButton(props) {
|
|
30741
|
+
const { speechRecognition, isVoiceCalling, buttonColor, speechRecognitionUiDescriptor, onButtonClick, handleToggleVoiceInput, } = props;
|
|
30742
|
+
if (!speechRecognition) {
|
|
30743
|
+
return null;
|
|
30744
|
+
}
|
|
30745
|
+
return (jsx("button", { "data-button-type": "voice", disabled: speechRecognitionUiDescriptor.isButtonDisabled, style: createVoiceButtonStyle({
|
|
30746
|
+
buttonColor,
|
|
30747
|
+
speechRecognitionUiDescriptor,
|
|
30748
|
+
}), className: classNames(styles$5.voiceButton, chatCssClassNames.inputVoiceButton, (isVoiceCalling || speechRecognitionUiDescriptor.isButtonActive) && styles$5.voiceButtonActive), onClick: onButtonClick((event) => {
|
|
30749
|
+
event.preventDefault();
|
|
30750
|
+
handleToggleVoiceInput();
|
|
30751
|
+
}), title: speechRecognitionUiDescriptor.buttonTitle, "aria-label": speechRecognitionUiDescriptor.buttonTitle, children: jsx(MicIcon, { size: 25 }) }));
|
|
30752
|
+
}
|
|
30753
|
+
/**
|
|
30754
|
+
* Renders the composer send button.
|
|
30755
|
+
*
|
|
30756
|
+
* @private component of `<ChatInputArea/>`
|
|
30757
|
+
*/
|
|
30758
|
+
function ChatInputAreaSendButton(props) {
|
|
30759
|
+
const { primaryButtonStyle, onButtonClick, handleSend } = props;
|
|
30760
|
+
return (jsx("button", { "data-button-type": "call-to-action", className: chatCssClassNames.inputSendButton, style: primaryButtonStyle, onClick: onButtonClick((event) => {
|
|
30761
|
+
event.preventDefault();
|
|
30762
|
+
/* not await */ handleSend();
|
|
30763
|
+
}), children: jsx(SendIcon, { size: 25 }) }));
|
|
30764
|
+
}
|
|
30765
|
+
/**
|
|
30766
|
+
* Renders the optional dictation panel below the composer.
|
|
30767
|
+
*
|
|
30768
|
+
* @private component of `<ChatInputArea/>`
|
|
30769
|
+
*/
|
|
30770
|
+
function ChatInputAreaDictationPanelSection(props) {
|
|
30771
|
+
const { speechRecognition } = props;
|
|
30772
|
+
if (!speechRecognition) {
|
|
30773
|
+
return null;
|
|
30774
|
+
}
|
|
30775
|
+
return (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 }));
|
|
30776
|
+
}
|
|
30777
|
+
/**
|
|
30778
|
+
* Renders the temporary upload progress indicator.
|
|
30779
|
+
*
|
|
30780
|
+
* @private component of `<ChatInputArea/>`
|
|
30781
|
+
*/
|
|
30782
|
+
function ChatInputAreaUploadProgress({ isUploading }) {
|
|
30783
|
+
if (!isUploading) {
|
|
30784
|
+
return null;
|
|
30785
|
+
}
|
|
30786
|
+
return (jsxs("div", { className: styles$5.uploadProgress, children: [jsx("div", { className: styles$5.uploadProgressBar, children: jsx("div", { className: styles$5.uploadProgressFill }) }), jsx("span", { children: "Uploading files..." })] }));
|
|
30787
|
+
}
|
|
30788
|
+
/**
|
|
30789
|
+
* Renders the drag-and-drop overlay when uploads are enabled and active.
|
|
30790
|
+
*
|
|
30791
|
+
* @private component of `<ChatInputArea/>`
|
|
30792
|
+
*/
|
|
30793
|
+
function ChatInputAreaDragOverlay(props) {
|
|
30794
|
+
const { isDragOver, onFileUpload } = props;
|
|
30795
|
+
if (!isDragOver || !onFileUpload) {
|
|
30796
|
+
return null;
|
|
30797
|
+
}
|
|
30798
|
+
return (jsx("div", { className: styles$5.dragOverlay, children: jsxs("div", { className: styles$5.dragOverlayContent, children: [jsx(AttachmentIcon, { size: 48 }), jsx("span", { children: "Drop files here to upload" })] }) }));
|
|
30799
|
+
}
|
|
29417
30800
|
/**
|
|
29418
30801
|
* Renders the chat input area with text, file upload, and voice controls.
|
|
29419
30802
|
*
|
|
29420
30803
|
* @private component of `<Chat/>`
|
|
29421
30804
|
*/
|
|
29422
30805
|
function ChatInputArea(props) {
|
|
29423
|
-
var _a;
|
|
29424
30806
|
const { onMessage, onChange, onFileUpload, speechRecognition, speechRecognitionLanguage, defaultMessage, replyingToMessage, onCancelReply, enterBehavior, resolveEnterBehavior, placeholderMessageContent, isFocusedOnLoad, isMobile, isVoiceCalling, participants, buttonColor, soundSystem, onButtonClick, chatInputClassName, chatUiTranslations, } = props;
|
|
29425
30807
|
const { fileInputRef, uploadedFiles, uploadedFilesRef, isDragOver, isUploading, handleDrop, handleDragOver, handleDragLeave, handlePaste, handleFileInputChange, removeUploadedFile, clearUploadedFiles, openFilePicker, } = useChatInputAreaAttachments({
|
|
29426
30808
|
onFileUpload,
|
|
@@ -29450,52 +30832,20 @@ function ChatInputArea(props) {
|
|
|
29450
30832
|
if (!onMessage) {
|
|
29451
30833
|
return null;
|
|
29452
30834
|
}
|
|
29453
|
-
const
|
|
29454
|
-
const
|
|
29455
|
-
|
|
29456
|
-
|
|
29457
|
-
|
|
29458
|
-
|
|
29459
|
-
|
|
29460
|
-
|
|
29461
|
-
|
|
29462
|
-
|
|
29463
|
-
|
|
29464
|
-
|
|
29465
|
-
|
|
29466
|
-
|
|
29467
|
-
return (jsxs("div", { className: classNames(styles$5.chatInput, chatInputClassName, isDragOver && styles$5.dragOver), ...(onFileUpload
|
|
29468
|
-
? {
|
|
29469
|
-
onDrop: handleDrop,
|
|
29470
|
-
onDragOver: handleDragOver,
|
|
29471
|
-
onDragLeave: handleDragLeave,
|
|
29472
|
-
}
|
|
29473
|
-
: {}), children: [replyingToMessage && replyPreviewText && replySenderLabel && (jsx(ChatReplyPreview, { label: replyPreviewLabel, senderLabel: replySenderLabel, previewText: replyPreviewText, className: styles$5.replyComposerPreview, dismissLabel: cancelReplyLabel, onDismiss: onCancelReply || undefined })), uploadedFiles.length > 0 && (jsx("div", { className: styles$5.filePreviewContainer, children: uploadedFiles.map((uploadedFile) => (jsxs("div", { className: styles$5.filePreview, children: [jsx("div", { className: styles$5.fileIcon, children: "\uD83D\uDCCE" }), jsxs("div", { className: styles$5.fileInfo, children: [jsx("div", { className: styles$5.fileName, children: uploadedFile.file.name }), jsxs("div", { className: styles$5.fileSize, children: [(uploadedFile.file.size / 1024).toFixed(1), " KB"] })] }), jsx("button", { className: styles$5.removeFileButton, onClick: () => removeUploadedFile(uploadedFile.id), title: "Remove file", children: jsx(CloseIcon, {}) })] }, uploadedFile.id))) })), jsxs("div", { className: classNames(styles$5.inputContainer, chatCssClassNames.inputContainer), style: {
|
|
29474
|
-
'--chat-placeholder-color': '#fff',
|
|
29475
|
-
'--input-bg-color': inputBgColor.toHex(),
|
|
29476
|
-
'--input-text-color': inputTextColor.toHex(),
|
|
29477
|
-
'--brand-color': buttonColor.toHex(),
|
|
29478
|
-
}, children: [jsx("textarea", { ref: textareaRef, className: chatCssClassNames.inputTextarea, onPaste: handlePaste, value: messageContent, placeholder: placeholderMessageContent || 'Write a message...', onChange: handleTextInputChange, onKeyDown: handleComposerKeyDown }), onFileUpload && (jsxs(Fragment, { children: [jsx("input", { ref: fileInputRef, type: "file", multiple: true, style: { display: 'none' }, onChange: handleFileInputChange }), jsx("button", { type: "button", style: {
|
|
29479
|
-
backgroundColor: buttonColor.toHex(),
|
|
29480
|
-
color: buttonColor.then(textColor).toHex(),
|
|
29481
|
-
}, className: classNames(styles$5.attachmentButton, chatCssClassNames.inputAttachmentButton), onClick: onButtonClick(openFilePicker), disabled: isUploading, title: "Attach file", children: jsx(AttachmentIcon, { size: 20 }) })] })), speechRecognition && (jsx("button", { "data-button-type": "voice", disabled: speechRecognitionUiDescriptor.isButtonDisabled, style: {
|
|
29482
|
-
backgroundColor: speechRecognitionUiDescriptor.isButtonActive
|
|
29483
|
-
? Color.from('#ff4444').toHex()
|
|
29484
|
-
: buttonColor.toHex(),
|
|
29485
|
-
color: speechRecognitionUiDescriptor.isButtonActive
|
|
29486
|
-
? Color.from('#ffffff').toHex()
|
|
29487
|
-
: buttonColor.then(textColor).toHex(),
|
|
29488
|
-
}, className: classNames(styles$5.voiceButton, chatCssClassNames.inputVoiceButton, (isVoiceCalling || speechRecognitionUiDescriptor.isButtonActive) &&
|
|
29489
|
-
styles$5.voiceButtonActive), onClick: onButtonClick((event) => {
|
|
29490
|
-
event.preventDefault();
|
|
29491
|
-
handleToggleVoiceInput();
|
|
29492
|
-
}), title: speechRecognitionUiDescriptor.buttonTitle, "aria-label": speechRecognitionUiDescriptor.buttonTitle, children: jsx(MicIcon, { size: 25 }) })), jsx("button", { "data-button-type": "call-to-action", className: chatCssClassNames.inputSendButton, style: {
|
|
29493
|
-
backgroundColor: buttonColor.toHex(),
|
|
29494
|
-
color: buttonColor.then(textColor).toHex(),
|
|
29495
|
-
}, onClick: onButtonClick((event) => {
|
|
29496
|
-
event.preventDefault();
|
|
29497
|
-
/* not await */ handleSend();
|
|
29498
|
-
}), children: jsx(SendIcon, { size: 25 }) })] }), speechRecognition && (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 && (jsxs("div", { className: styles$5.uploadProgress, children: [jsx("div", { className: styles$5.uploadProgressBar, children: jsx("div", { className: styles$5.uploadProgressFill }) }), jsx("span", { children: "Uploading files..." })] })), isDragOver && onFileUpload && (jsx("div", { className: styles$5.dragOverlay, children: jsxs("div", { className: styles$5.dragOverlayContent, children: [jsx(AttachmentIcon, { size: 48 }), jsx("span", { children: "Drop files here to upload" })] }) }))] }));
|
|
30835
|
+
const { inputContainerStyle, primaryButtonStyle } = createChatInputAreaColorModel(participants, buttonColor);
|
|
30836
|
+
const replyPreview = createChatInputAreaReplyPreviewModel({
|
|
30837
|
+
replyingToMessage,
|
|
30838
|
+
participants,
|
|
30839
|
+
chatUiTranslations,
|
|
30840
|
+
onCancelReply,
|
|
30841
|
+
});
|
|
30842
|
+
const rootProps = createChatInputAreaRootProps({
|
|
30843
|
+
onFileUpload,
|
|
30844
|
+
handleDrop,
|
|
30845
|
+
handleDragOver,
|
|
30846
|
+
handleDragLeave,
|
|
30847
|
+
});
|
|
30848
|
+
return (jsxs("div", { className: classNames(styles$5.chatInput, chatInputClassName, isDragOver && styles$5.dragOver), ...rootProps, children: [jsx(ChatInputAreaReplyPreviewSection, { replyPreview: replyPreview }), jsx(ChatInputAreaUploadedFilesSection, { uploadedFiles: uploadedFiles, removeUploadedFile: removeUploadedFile }), jsxs("div", { className: classNames(styles$5.inputContainer, chatCssClassNames.inputContainer), style: inputContainerStyle, children: [jsx("textarea", { ref: textareaRef, className: chatCssClassNames.inputTextarea, onPaste: handlePaste, value: messageContent, placeholder: placeholderMessageContent || 'Write a message...', onChange: handleTextInputChange, onKeyDown: handleComposerKeyDown }), jsx(ChatInputAreaAttachmentButton, { onFileUpload: onFileUpload, fileInputRef: fileInputRef, handleFileInputChange: handleFileInputChange, onButtonClick: onButtonClick, openFilePicker: openFilePicker, isUploading: isUploading, primaryButtonStyle: primaryButtonStyle }), jsx(ChatInputAreaVoiceButton, { speechRecognition: speechRecognition, isVoiceCalling: isVoiceCalling, buttonColor: buttonColor, speechRecognitionUiDescriptor: speechRecognitionUiDescriptor, onButtonClick: onButtonClick, handleToggleVoiceInput: handleToggleVoiceInput }), jsx(ChatInputAreaSendButton, { primaryButtonStyle: primaryButtonStyle, onButtonClick: onButtonClick, handleSend: handleSend })] }), 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 }), jsx(ChatInputAreaUploadProgress, { isUploading: isUploading }), jsx(ChatInputAreaDragOverlay, { isDragOver: isDragOver, onFileUpload: onFileUpload })] }));
|
|
29499
30849
|
}
|
|
29500
30850
|
|
|
29501
30851
|
// Note: [💞] Ignore a discrepancy between file name and entity name
|
|
@@ -36458,6 +37808,12 @@ const OVERWRITTEN_COMMITMENT_GROUP_BY_TYPE = new Map([
|
|
|
36458
37808
|
['GOAL', 'GOAL'],
|
|
36459
37809
|
['GOALS', 'GOAL'],
|
|
36460
37810
|
]);
|
|
37811
|
+
/**
|
|
37812
|
+
* Legacy commitments that should be parsed for compatibility but ignored by the model-requirements pipeline.
|
|
37813
|
+
*
|
|
37814
|
+
* @private internal constant of `filterCommitmentsForAgentModelRequirements`
|
|
37815
|
+
*/
|
|
37816
|
+
const IGNORED_COMMITMENT_TYPES = new Set(['WALLET', 'WALLETS']);
|
|
36461
37817
|
/**
|
|
36462
37818
|
* Applies the commitment filtering rules used before commitment definitions are executed.
|
|
36463
37819
|
*
|
|
@@ -36506,6 +37862,9 @@ function filterOverwrittenCommitments(commitments) {
|
|
|
36506
37862
|
function filterDeletedCommitments(commitments) {
|
|
36507
37863
|
const filteredCommitments = [];
|
|
36508
37864
|
for (const commitment of commitments) {
|
|
37865
|
+
if (isIgnoredCommitmentType(commitment.type)) {
|
|
37866
|
+
continue;
|
|
37867
|
+
}
|
|
36509
37868
|
if (!isDeleteCommitmentType(commitment.type)) {
|
|
36510
37869
|
filteredCommitments.push(commitment);
|
|
36511
37870
|
continue;
|
|
@@ -36536,6 +37895,17 @@ function filterDeletedCommitments(commitments) {
|
|
|
36536
37895
|
function isDeleteCommitmentType(commitmentType) {
|
|
36537
37896
|
return DELETE_COMMITMENT_TYPES.has(commitmentType);
|
|
36538
37897
|
}
|
|
37898
|
+
/**
|
|
37899
|
+
* Checks whether a parsed commitment is intentionally ignored by the current model compiler.
|
|
37900
|
+
*
|
|
37901
|
+
* @param commitmentType - Commitment type to check.
|
|
37902
|
+
* @returns `true` when the commitment should not affect model requirements.
|
|
37903
|
+
*
|
|
37904
|
+
* @private internal utility of `filterDeletedCommitments`
|
|
37905
|
+
*/
|
|
37906
|
+
function isIgnoredCommitmentType(commitmentType) {
|
|
37907
|
+
return IGNORED_COMMITMENT_TYPES.has(commitmentType);
|
|
37908
|
+
}
|
|
36539
37909
|
/**
|
|
36540
37910
|
* Extracts normalized parameter names used for DELETE-like invalidation matching.
|
|
36541
37911
|
*
|
|
@@ -41233,8 +42603,8 @@ class OpenAiAgentKitExecutionTools extends OpenAiVectorStoreHandler {
|
|
|
41233
42603
|
* Prepares an AgentKit agent with optional knowledge sources and tool definitions.
|
|
41234
42604
|
*/
|
|
41235
42605
|
async prepareAgentKitAgent(options) {
|
|
41236
|
-
var _a, _b
|
|
41237
|
-
const { name, instructions, knowledgeSources, tools,
|
|
42606
|
+
var _a, _b;
|
|
42607
|
+
const { name, instructions, knowledgeSources, tools, vectorStoreId: cachedVectorStoreId, storeAsPrepared, } = options;
|
|
41238
42608
|
await this.ensureAgentKitDefaults();
|
|
41239
42609
|
if (this.options.isVerbose) {
|
|
41240
42610
|
console.info('[🤰]', 'Preparing OpenAI AgentKit agent', {
|
|
@@ -41242,11 +42612,10 @@ class OpenAiAgentKitExecutionTools extends OpenAiVectorStoreHandler {
|
|
|
41242
42612
|
instructionsLength: instructions.length,
|
|
41243
42613
|
knowledgeSourcesCount: (_a = knowledgeSources === null || knowledgeSources === void 0 ? void 0 : knowledgeSources.length) !== null && _a !== void 0 ? _a : 0,
|
|
41244
42614
|
toolsCount: (_b = tools === null || tools === void 0 ? void 0 : tools.length) !== null && _b !== void 0 ? _b : 0,
|
|
41245
|
-
nativeAgentKitToolsCount: (_c = nativeAgentKitTools === null || nativeAgentKitTools === void 0 ? void 0 : nativeAgentKitTools.length) !== null && _c !== void 0 ? _c : 0,
|
|
41246
42615
|
});
|
|
41247
42616
|
}
|
|
41248
42617
|
let vectorStoreId = cachedVectorStoreId;
|
|
41249
|
-
if (!vectorStoreId && knowledgeSources && knowledgeSources.length > 0) {
|
|
42618
|
+
if (this.isNativeKnowledgeSearchEnabled && !vectorStoreId && knowledgeSources && knowledgeSources.length > 0) {
|
|
41250
42619
|
const vectorStoreResult = await this.createVectorStoreWithKnowledgeSources({
|
|
41251
42620
|
client: await this.getClient(),
|
|
41252
42621
|
name,
|
|
@@ -41255,13 +42624,19 @@ class OpenAiAgentKitExecutionTools extends OpenAiVectorStoreHandler {
|
|
|
41255
42624
|
});
|
|
41256
42625
|
vectorStoreId = vectorStoreResult.vectorStoreId;
|
|
41257
42626
|
}
|
|
41258
|
-
else if (vectorStoreId && this.options.isVerbose) {
|
|
42627
|
+
else if (this.isNativeKnowledgeSearchEnabled && vectorStoreId && this.options.isVerbose) {
|
|
41259
42628
|
console.info('[🤰]', 'Using cached vector store for AgentKit agent', {
|
|
41260
42629
|
name,
|
|
41261
42630
|
vectorStoreId,
|
|
41262
42631
|
});
|
|
41263
42632
|
}
|
|
41264
|
-
|
|
42633
|
+
if (!this.isNativeKnowledgeSearchEnabled) {
|
|
42634
|
+
vectorStoreId = undefined;
|
|
42635
|
+
}
|
|
42636
|
+
const agentKitTools = this.buildAgentKitTools({
|
|
42637
|
+
tools,
|
|
42638
|
+
vectorStoreId,
|
|
42639
|
+
});
|
|
41265
42640
|
const openAiAgentKitAgent = new Agent$1({
|
|
41266
42641
|
name,
|
|
41267
42642
|
model: this.agentKitModelName,
|
|
@@ -41280,7 +42655,7 @@ class OpenAiAgentKitExecutionTools extends OpenAiVectorStoreHandler {
|
|
|
41280
42655
|
name,
|
|
41281
42656
|
model: this.agentKitModelName,
|
|
41282
42657
|
toolCount: agentKitTools.length,
|
|
41283
|
-
hasVectorStore: Boolean(vectorStoreId),
|
|
42658
|
+
hasVectorStore: this.isNativeKnowledgeSearchEnabled && Boolean(vectorStoreId),
|
|
41284
42659
|
});
|
|
41285
42660
|
}
|
|
41286
42661
|
return preparedAgent;
|
|
@@ -41300,14 +42675,11 @@ class OpenAiAgentKitExecutionTools extends OpenAiVectorStoreHandler {
|
|
|
41300
42675
|
* Builds the tool list for AgentKit, including hosted file search when applicable.
|
|
41301
42676
|
*/
|
|
41302
42677
|
buildAgentKitTools(options) {
|
|
41303
|
-
const { tools,
|
|
42678
|
+
const { tools, vectorStoreId } = options;
|
|
41304
42679
|
const agentKitTools = [];
|
|
41305
42680
|
if (vectorStoreId) {
|
|
41306
42681
|
agentKitTools.push(fileSearchTool(vectorStoreId));
|
|
41307
42682
|
}
|
|
41308
|
-
if (nativeAgentKitTools && nativeAgentKitTools.length > 0) {
|
|
41309
|
-
agentKitTools.push(...nativeAgentKitTools);
|
|
41310
|
-
}
|
|
41311
42683
|
if (tools && tools.length > 0) {
|
|
41312
42684
|
let scriptTools = null;
|
|
41313
42685
|
for (const toolDefinition of tools) {
|
|
@@ -41782,6 +43154,12 @@ class OpenAiAgentKitExecutionTools extends OpenAiVectorStoreHandler {
|
|
|
41782
43154
|
get agentKitOptions() {
|
|
41783
43155
|
return this.options;
|
|
41784
43156
|
}
|
|
43157
|
+
/**
|
|
43158
|
+
* Returns true when hosted OpenAI vector-store search should back `knowledgeSources`.
|
|
43159
|
+
*/
|
|
43160
|
+
get isNativeKnowledgeSearchEnabled() {
|
|
43161
|
+
return this.agentKitOptions.isNativeKnowledgeSearchEnabled !== false;
|
|
43162
|
+
}
|
|
41785
43163
|
/**
|
|
41786
43164
|
* Discriminant for type guards.
|
|
41787
43165
|
*/
|
|
@@ -47509,6 +48887,7 @@ const TOOL_TITLES = {
|
|
|
47509
48887
|
request_wallet_record: { title: 'Requesting wallet', emoji: '👛' },
|
|
47510
48888
|
web_search: { title: 'Searching the web', emoji: '🔎' },
|
|
47511
48889
|
deep_search: { title: 'Deep research', emoji: '🔬' },
|
|
48890
|
+
knowledge_search: { title: 'Searching knowledge', emoji: '📚' },
|
|
47512
48891
|
useSearchEngine: { title: 'Searching the web', emoji: '🔎' },
|
|
47513
48892
|
search: { title: 'Searching the web', emoji: '🔎' },
|
|
47514
48893
|
useBrowser: { title: 'Browsing the web', emoji: '🌐' },
|
|
@@ -48384,6 +49763,13 @@ const ChatMessageItem = memo(
|
|
|
48384
49763
|
content: sanitizedContentWithoutButtons,
|
|
48385
49764
|
citations: message.citations,
|
|
48386
49765
|
}), [message.citations, sanitizedContentWithoutButtons]);
|
|
49766
|
+
const structuredSourceCitations = useMemo(() => {
|
|
49767
|
+
const footnoteSourceKeys = new Set(citationFootnoteRenderModel.footnotes.map((footnote) => footnote.citation.source.trim().toLowerCase()));
|
|
49768
|
+
return (message.sources || []).filter((source) => {
|
|
49769
|
+
const sourceKey = source.source.trim().toLowerCase();
|
|
49770
|
+
return sourceKey && !footnoteSourceKeys.has(sourceKey);
|
|
49771
|
+
});
|
|
49772
|
+
}, [citationFootnoteRenderModel.footnotes, message.sources]);
|
|
48387
49773
|
const contentSegments = useMemo(() => splitMessageContentIntoSegments(citationFootnoteRenderModel.content), [citationFootnoteRenderModel.content]);
|
|
48388
49774
|
const hasMapSegment = useMemo(() => contentSegments.some((segment) => segment.type === 'map'), [contentSegments]);
|
|
48389
49775
|
const [localHoveredRating, setLocalHoveredRating] = useState(0);
|
|
@@ -48655,7 +50041,7 @@ const ChatMessageItem = memo(
|
|
|
48655
50041
|
'--message-bg-color': isAgentArticleMode ? articleModeBackgroundColor : color.toHex(),
|
|
48656
50042
|
'--message-text-color': isAgentArticleMode ? articleModeTextColor : colorOfText.toHex(),
|
|
48657
50043
|
'--chat-message-swipe-offset': swipeTranslation,
|
|
48658
|
-
}, onPointerDown: handleReplyPointerDown, onPointerMove: handleReplyPointerMove, onPointerUp: handleReplyPointerEnd, onPointerCancel: resetReplySwipe, children: [isReplyActionEnabled && (jsx("div", { className: classNames(styles$5.replySwipeIndicator, isMe && styles$5.replySwipeIndicatorRight, isReplySwipeArmed && styles$5.replySwipeIndicatorActive), "aria-hidden": "true", children: jsx(Reply, { className: styles$5.replySwipeIndicatorIcon }) })), !shouldRenderArticleActionsBar && renderMessageReadAndCopyControls(), message.isVoiceCall && (jsx("div", { className: styles$5.voiceCallIndicator, children: jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor", children: 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 && (jsx(ChatReplyPreview, { label: replyPreviewLabel, senderLabel: replySenderLabel, previewText: replyPreviewText, className: styles$5.replyBubblePreview })), jsx("div", { ref: contentWithoutButtonsRef, children: jsx(ChatMessageRichContent, { content: message.content, contentSegments: contentSegments, streamingFeaturePlaceholderKind: streamingFeaturePlaceholderKind, onCreateAgent: onCreateAgent, mode: mode }) }), message.attachments && message.attachments.length > 0 && (jsx("div", { className: styles$5.attachments, children: message.attachments.map((attachment, index) => (jsxs("a", { href: attachment.url, target: "_blank", rel: "noopener noreferrer", className: styles$5.attachment, title: attachment.name, children: [jsx("span", { className: styles$5.attachmentIcon, children: "\uD83D\uDCCE" }), jsx("span", { className: styles$5.attachmentName, children: attachment.name })] }, index))) })), jsx(ChatMessageToolCallChips, { chips: toolCallChips, onToolCallClick: onToolCallClick }), citationFootnoteRenderModel.footnotes.length > 0 && (jsx("div", { className: styles$5.citationFootnotes, children: citationFootnoteRenderModel.footnotes.map((footnote) => (jsxs("div", { className: styles$5.citationFootnoteItem, children: [jsx("span", { className: styles$5.citationFootnoteNumber, children: footnote.number }), jsx(SourceChip, { citation: footnote.citation, onClick: onCitationClick, isCitationIdVisible: false })] }, `citation-footnote-${footnote.number}-${footnote.citation.source}`))) })), transitiveCitations.length > 0 && (jsx("div", { className: styles$5.sourceCitations, children: transitiveCitations.map((citation, index) => (jsx(SourceChip, { citation: citation, suffix: `by ${citation.origin.label}`, onClick: onCitationClick }, `team-source-${citation.source}-${index}`))) })), shouldShowButtons && (jsx("div", { className: styles$5.messageButtons, children: renderableButtons.map(({ button, buttonIndex }) => (jsx("button", { type: "button", className: classNames(styles$5.messageButton, button.type === 'action' && styles$5.actionMessageButton), onClick: (event) => {
|
|
50044
|
+
}, onPointerDown: handleReplyPointerDown, onPointerMove: handleReplyPointerMove, onPointerUp: handleReplyPointerEnd, onPointerCancel: resetReplySwipe, children: [isReplyActionEnabled && (jsx("div", { className: classNames(styles$5.replySwipeIndicator, isMe && styles$5.replySwipeIndicatorRight, isReplySwipeArmed && styles$5.replySwipeIndicatorActive), "aria-hidden": "true", children: jsx(Reply, { className: styles$5.replySwipeIndicatorIcon }) })), !shouldRenderArticleActionsBar && renderMessageReadAndCopyControls(), message.isVoiceCall && (jsx("div", { className: styles$5.voiceCallIndicator, children: jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor", children: 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 && (jsx(ChatReplyPreview, { label: replyPreviewLabel, senderLabel: replySenderLabel, previewText: replyPreviewText, className: styles$5.replyBubblePreview })), jsx("div", { ref: contentWithoutButtonsRef, children: jsx(ChatMessageRichContent, { content: message.content, contentSegments: contentSegments, streamingFeaturePlaceholderKind: streamingFeaturePlaceholderKind, onCreateAgent: onCreateAgent, mode: mode }) }), message.attachments && message.attachments.length > 0 && (jsx("div", { className: styles$5.attachments, children: message.attachments.map((attachment, index) => (jsxs("a", { href: attachment.url, target: "_blank", rel: "noopener noreferrer", className: styles$5.attachment, title: attachment.name, children: [jsx("span", { className: styles$5.attachmentIcon, children: "\uD83D\uDCCE" }), jsx("span", { className: styles$5.attachmentName, children: attachment.name })] }, index))) })), jsx(ChatMessageToolCallChips, { chips: toolCallChips, onToolCallClick: onToolCallClick }), citationFootnoteRenderModel.footnotes.length > 0 && (jsx("div", { className: styles$5.citationFootnotes, children: citationFootnoteRenderModel.footnotes.map((footnote) => (jsxs("div", { className: styles$5.citationFootnoteItem, children: [jsx("span", { className: styles$5.citationFootnoteNumber, children: footnote.number }), jsx(SourceChip, { citation: footnote.citation, onClick: onCitationClick, isCitationIdVisible: false })] }, `citation-footnote-${footnote.number}-${footnote.citation.source}`))) })), structuredSourceCitations.length > 0 && (jsx("div", { className: styles$5.sourceCitations, children: structuredSourceCitations.map((citation, index) => (jsx(SourceChip, { citation: citation, onClick: onCitationClick, isCitationIdVisible: false }, `message-source-${citation.source}-${citation.id}-${index}`))) })), transitiveCitations.length > 0 && (jsx("div", { className: styles$5.sourceCitations, children: transitiveCitations.map((citation, index) => (jsx(SourceChip, { citation: citation, suffix: `by ${citation.origin.label}`, onClick: onCitationClick }, `team-source-${citation.source}-${index}`))) })), shouldShowButtons && (jsx("div", { className: styles$5.messageButtons, children: renderableButtons.map(({ button, buttonIndex }) => (jsx("button", { type: "button", className: classNames(styles$5.messageButton, button.type === 'action' && styles$5.actionMessageButton), onClick: (event) => {
|
|
48659
50045
|
event.stopPropagation();
|
|
48660
50046
|
if (button.type === 'message') {
|
|
48661
50047
|
const quickMessageHandler = onQuickMessageButton || onMessage;
|
|
@@ -48707,6 +50093,9 @@ const ChatMessageItem = memo(
|
|
|
48707
50093
|
if (prev.message.citations !== next.message.citations) {
|
|
48708
50094
|
return false;
|
|
48709
50095
|
}
|
|
50096
|
+
if (prev.message.sources !== next.message.sources) {
|
|
50097
|
+
return false;
|
|
50098
|
+
}
|
|
48710
50099
|
if (JSON.stringify(prev.message.attachments) !== JSON.stringify(next.message.attachments)) {
|
|
48711
50100
|
return false;
|
|
48712
50101
|
}
|
|
@@ -48821,68 +50210,201 @@ function resolveRenderedMessageKey(message) {
|
|
|
48821
50210
|
return message.clientMessageId || message.id || message.content;
|
|
48822
50211
|
}
|
|
48823
50212
|
|
|
50213
|
+
/**
|
|
50214
|
+
* Supported star values rendered by the rating UI.
|
|
50215
|
+
*
|
|
50216
|
+
* @private component of `<ChatRatingModal/>`
|
|
50217
|
+
*/
|
|
50218
|
+
const STAR_RATINGS = [1, 2, 3, 4, 5];
|
|
50219
|
+
/**
|
|
50220
|
+
* Resolves the key used to persist message rating state.
|
|
50221
|
+
*
|
|
50222
|
+
* @private function of `<ChatRatingModal/>`
|
|
50223
|
+
*/
|
|
50224
|
+
function resolveChatRatingMessageKey(message) {
|
|
50225
|
+
return message.id || message.content /* <-[??] */;
|
|
50226
|
+
}
|
|
50227
|
+
/**
|
|
50228
|
+
* Returns whether the modal should render the report-issue flow.
|
|
50229
|
+
*
|
|
50230
|
+
* @private function of `<ChatRatingModal/>`
|
|
50231
|
+
*/
|
|
50232
|
+
function isReportIssueFeedbackMode(feedbackMode) {
|
|
50233
|
+
return feedbackMode === 'report_issue';
|
|
50234
|
+
}
|
|
50235
|
+
/**
|
|
50236
|
+
* Finds the previous user question from postprocessed messages when it directly precedes the selected response.
|
|
50237
|
+
*
|
|
50238
|
+
* @private function of `<ChatRatingModal/>`
|
|
50239
|
+
*/
|
|
50240
|
+
function resolveUserQuestionFromPostprocessedMessages(postprocessedMessages, selectedMessage) {
|
|
50241
|
+
const selectedMessageIndex = postprocessedMessages.findIndex((message) => message.id === selectedMessage.id);
|
|
50242
|
+
if (selectedMessageIndex <= 0) {
|
|
50243
|
+
return null;
|
|
50244
|
+
}
|
|
50245
|
+
const previousMessage = postprocessedMessages[selectedMessageIndex - 1];
|
|
50246
|
+
if ((previousMessage === null || previousMessage === void 0 ? void 0 : previousMessage.sender) !== 'USER') {
|
|
50247
|
+
return null;
|
|
50248
|
+
}
|
|
50249
|
+
return previousMessage.content;
|
|
50250
|
+
}
|
|
50251
|
+
/**
|
|
50252
|
+
* Finds the nearest earlier user question in the original message list.
|
|
50253
|
+
*
|
|
50254
|
+
* @private function of `<ChatRatingModal/>`
|
|
50255
|
+
*/
|
|
50256
|
+
function resolveUserQuestionFromMessages(messages, selectedMessage) {
|
|
50257
|
+
const selectedMessageIndex = messages.findIndex((message) => message.id === selectedMessage.id);
|
|
50258
|
+
for (let index = selectedMessageIndex - 1; index >= 0; index--) {
|
|
50259
|
+
const currentMessage = messages[index];
|
|
50260
|
+
if ((currentMessage === null || currentMessage === void 0 ? void 0 : currentMessage.sender) === 'USER') {
|
|
50261
|
+
return currentMessage.content;
|
|
50262
|
+
}
|
|
50263
|
+
}
|
|
50264
|
+
return null;
|
|
50265
|
+
}
|
|
50266
|
+
/**
|
|
50267
|
+
* Resolves the user question shown above the feedback fields.
|
|
50268
|
+
*
|
|
50269
|
+
* @private function of `<ChatRatingModal/>`
|
|
50270
|
+
*/
|
|
50271
|
+
function resolveChatRatingUserQuestion(params) {
|
|
50272
|
+
const { postprocessedMessages, messages, selectedMessage } = params;
|
|
50273
|
+
return (resolveUserQuestionFromPostprocessedMessages(postprocessedMessages, selectedMessage) ||
|
|
50274
|
+
resolveUserQuestionFromMessages(messages, selectedMessage) ||
|
|
50275
|
+
'');
|
|
50276
|
+
}
|
|
50277
|
+
/**
|
|
50278
|
+
* Builds the localized text rendered by the modal.
|
|
50279
|
+
*
|
|
50280
|
+
* @private function of `<ChatRatingModal/>`
|
|
50281
|
+
*/
|
|
50282
|
+
function createChatRatingModalCopy(params) {
|
|
50283
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
50284
|
+
const { feedbackMode, feedbackTranslations } = params;
|
|
50285
|
+
const isReportIssueMode = isReportIssueFeedbackMode(feedbackMode);
|
|
50286
|
+
return {
|
|
50287
|
+
title: isReportIssueMode
|
|
50288
|
+
? (_a = feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.reportIssueModalTitle) !== null && _a !== void 0 ? _a : 'Report issue'
|
|
50289
|
+
: (_b = feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.rateResponseModalTitle) !== null && _b !== void 0 ? _b : 'Rate this response',
|
|
50290
|
+
userQuestionLabel: (_c = feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.userQuestionLabel) !== null && _c !== void 0 ? _c : 'Your question:',
|
|
50291
|
+
expectedAnswerLabel: isReportIssueMode
|
|
50292
|
+
? (_d = feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.reportIssueExpectedAnswerLabel) !== null && _d !== void 0 ? _d : 'What should the answer include?'
|
|
50293
|
+
: (_e = feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.expectedAnswerLabel) !== null && _e !== void 0 ? _e : 'Expected answer:',
|
|
50294
|
+
expectedAnswerPlaceholder: (_f = feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.expectedAnswerPlaceholder) !== null && _f !== void 0 ? _f : 'Expected answer (optional)',
|
|
50295
|
+
noteLabel: isReportIssueMode
|
|
50296
|
+
? (_g = feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.reportIssueDetailsLabel) !== null && _g !== void 0 ? _g : 'Issue details:'
|
|
50297
|
+
: (_h = feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.noteLabel) !== null && _h !== void 0 ? _h : 'Note:',
|
|
50298
|
+
notePlaceholder: isReportIssueMode
|
|
50299
|
+
? (_j = feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.reportIssueDetailsPlaceholder) !== null && _j !== void 0 ? _j : 'Describe what went wrong (optional)'
|
|
50300
|
+
: (_k = feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.notePlaceholder) !== null && _k !== void 0 ? _k : 'Add a note (optional)',
|
|
50301
|
+
cancelLabel: (feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.cancelLabel) || 'Cancel',
|
|
50302
|
+
submitLabel: isReportIssueMode
|
|
50303
|
+
? (_l = feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.reportIssueSubmitLabel) !== null && _l !== void 0 ? _l : 'Report issue'
|
|
50304
|
+
: (_m = feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.submitLabel) !== null && _m !== void 0 ? _m : 'Submit',
|
|
50305
|
+
};
|
|
50306
|
+
}
|
|
50307
|
+
/**
|
|
50308
|
+
* Resolves the active rating, preferring hover preview over persisted value.
|
|
50309
|
+
*
|
|
50310
|
+
* @private function of `<ChatRatingModal/>`
|
|
50311
|
+
*/
|
|
50312
|
+
function resolveSelectedChatRating(params) {
|
|
50313
|
+
const { hoveredRating, messageRatings, selectedMessage } = params;
|
|
50314
|
+
return hoveredRating || messageRatings.get(resolveChatRatingMessageKey(selectedMessage)) || 0;
|
|
50315
|
+
}
|
|
50316
|
+
/**
|
|
50317
|
+
* Resolves the color for one star in the rating picker.
|
|
50318
|
+
*
|
|
50319
|
+
* @private function of `<ChatRatingModal/>`
|
|
50320
|
+
*/
|
|
50321
|
+
function resolveChatRatingStarColor(star, selectedRating, mode) {
|
|
50322
|
+
if (star <= selectedRating) {
|
|
50323
|
+
return '#FFD700';
|
|
50324
|
+
}
|
|
50325
|
+
return mode === 'LIGHT' ? '#ccc' : '#555';
|
|
50326
|
+
}
|
|
50327
|
+
/**
|
|
50328
|
+
* Returns the placeholder used in the expected-answer field.
|
|
50329
|
+
*
|
|
50330
|
+
* @private function of `<ChatRatingModal/>`
|
|
50331
|
+
*/
|
|
50332
|
+
function resolveExpectedAnswerPlaceholder(selectedMessage, modalCopy) {
|
|
50333
|
+
return selectedMessage.content || modalCopy.expectedAnswerPlaceholder;
|
|
50334
|
+
}
|
|
50335
|
+
/**
|
|
50336
|
+
* Closes the modal when the mobile backdrop itself is clicked.
|
|
50337
|
+
*
|
|
50338
|
+
* @private function of `<ChatRatingModal/>`
|
|
50339
|
+
*/
|
|
50340
|
+
function createChatRatingBackdropClickHandler(params) {
|
|
50341
|
+
const { isMobile, onClose } = params;
|
|
50342
|
+
return (event) => {
|
|
50343
|
+
if (event.target === event.currentTarget && isMobile) {
|
|
50344
|
+
onClose();
|
|
50345
|
+
}
|
|
50346
|
+
};
|
|
50347
|
+
}
|
|
50348
|
+
/**
|
|
50349
|
+
* Renders the optional star selector for the rating flow.
|
|
50350
|
+
*
|
|
50351
|
+
* @private component of `<ChatRatingModal/>`
|
|
50352
|
+
*/
|
|
50353
|
+
function ChatRatingModalStarsSection(props) {
|
|
50354
|
+
const { feedbackMode, hoveredRating, messageRatings, mode, selectedMessage, setHoveredRating, setMessageRatings } = props;
|
|
50355
|
+
if (isReportIssueFeedbackMode(feedbackMode)) {
|
|
50356
|
+
return null;
|
|
50357
|
+
}
|
|
50358
|
+
const selectedRating = resolveSelectedChatRating({
|
|
50359
|
+
hoveredRating,
|
|
50360
|
+
messageRatings,
|
|
50361
|
+
selectedMessage,
|
|
50362
|
+
});
|
|
50363
|
+
return (jsx("div", { className: styles$5.stars, children: STAR_RATINGS.map((star) => (jsx("span", { onClick: () => setMessageRatings((previousRatings) => {
|
|
50364
|
+
const nextRatings = new Map(previousRatings);
|
|
50365
|
+
nextRatings.set(resolveChatRatingMessageKey(selectedMessage), star);
|
|
50366
|
+
return nextRatings;
|
|
50367
|
+
}), onMouseEnter: () => setHoveredRating(star), onMouseLeave: () => setHoveredRating(0), className: classNames(styles$5.ratingModalStar), style: {
|
|
50368
|
+
color: resolveChatRatingStarColor(star, selectedRating, mode),
|
|
50369
|
+
}, children: "\u2B50" }, star))) }));
|
|
50370
|
+
}
|
|
50371
|
+
/**
|
|
50372
|
+
* Renders the footer action buttons for the rating modal.
|
|
50373
|
+
*
|
|
50374
|
+
* @private component of `<ChatRatingModal/>`
|
|
50375
|
+
*/
|
|
50376
|
+
function ChatRatingModalActions(props) {
|
|
50377
|
+
const { cancelLabel, onClose, submitLabel, submitRating } = props;
|
|
50378
|
+
return (jsxs("div", { className: styles$5.ratingActions, children: [jsx("button", { onClick: onClose, children: cancelLabel }), jsx("button", { onClick: submitRating, children: submitLabel })] }));
|
|
50379
|
+
}
|
|
48824
50380
|
/**
|
|
48825
50381
|
* Modal that captures per-message rating feedback.
|
|
48826
50382
|
*
|
|
48827
50383
|
* @private component of `<Chat/>`
|
|
48828
50384
|
*/
|
|
48829
50385
|
function ChatRatingModal(props) {
|
|
48830
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
48831
50386
|
const { isOpen, selectedMessage, postprocessedMessages, messages, hoveredRating, messageRatings, textRating, feedbackMode, feedbackTranslations, mode, isMobile, onClose, setHoveredRating, setMessageRatings, setSelectedMessage, setTextRating, submitRating, } = props;
|
|
48832
50387
|
if (!isOpen || !selectedMessage) {
|
|
48833
50388
|
return null;
|
|
48834
50389
|
}
|
|
48835
|
-
const
|
|
48836
|
-
|
|
48837
|
-
|
|
48838
|
-
|
|
48839
|
-
|
|
48840
|
-
|
|
48841
|
-
|
|
48842
|
-
|
|
48843
|
-
|
|
48844
|
-
|
|
48845
|
-
|
|
48846
|
-
|
|
48847
|
-
|
|
48848
|
-
|
|
48849
|
-
}
|
|
48850
|
-
return '';
|
|
48851
|
-
})();
|
|
48852
|
-
return (jsx("div", { className: styles$5.ratingModal, "data-chat-modal": "rating", "data-chat-theme": mode.toLowerCase(), onClick: (event) => {
|
|
48853
|
-
if (event.target === event.currentTarget && isMobile) {
|
|
48854
|
-
onClose();
|
|
48855
|
-
}
|
|
48856
|
-
}, children: jsxs("div", { className: styles$5.ratingModalContent, children: [jsx("h3", { children: isReportIssueMode
|
|
48857
|
-
? (_a = feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.reportIssueModalTitle) !== null && _a !== void 0 ? _a : 'Report issue'
|
|
48858
|
-
: (_b = feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.rateResponseModalTitle) !== null && _b !== void 0 ? _b : 'Rate this response' }), !isReportIssueMode && (jsx("div", { className: styles$5.stars, children: [1, 2, 3, 4, 5].map((star) => (jsx("span", { onClick: () => setMessageRatings((previousRatings) => {
|
|
48859
|
-
const nextRatings = new Map(previousRatings);
|
|
48860
|
-
nextRatings.set(selectedMessage.id || selectedMessage.content /* <-[??] */, star);
|
|
48861
|
-
return nextRatings;
|
|
48862
|
-
}), onMouseEnter: () => setHoveredRating(star), onMouseLeave: () => setHoveredRating(0), className: classNames(styles$5.ratingModalStar), style: {
|
|
48863
|
-
color: star <=
|
|
48864
|
-
(hoveredRating ||
|
|
48865
|
-
messageRatings.get(selectedMessage.id || selectedMessage.content /* <-[??] */) ||
|
|
48866
|
-
0)
|
|
48867
|
-
? '#FFD700'
|
|
48868
|
-
: mode === 'LIGHT'
|
|
48869
|
-
? '#ccc'
|
|
48870
|
-
: '#555',
|
|
48871
|
-
}, children: "\u2B50" }, star))) })), (_c = feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.userQuestionLabel) !== null && _c !== void 0 ? _c : 'Your question:', jsx("textarea", { readOnly: true, value: userQuestion, className: styles$5.ratingInput }), isReportIssueMode
|
|
48872
|
-
? (_d = feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.reportIssueExpectedAnswerLabel) !== null && _d !== void 0 ? _d : 'What should the answer include?'
|
|
48873
|
-
: (_e = feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.expectedAnswerLabel) !== null && _e !== void 0 ? _e : 'Expected answer:', jsx("textarea", { placeholder: selectedMessage.content ||
|
|
48874
|
-
(feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.expectedAnswerPlaceholder) ||
|
|
48875
|
-
'Expected answer (optional)', defaultValue: selectedMessage.expectedAnswer || selectedMessage.content, onChange: (event) => {
|
|
50390
|
+
const modalCopy = createChatRatingModalCopy({
|
|
50391
|
+
feedbackMode,
|
|
50392
|
+
feedbackTranslations,
|
|
50393
|
+
});
|
|
50394
|
+
const userQuestion = resolveChatRatingUserQuestion({
|
|
50395
|
+
postprocessedMessages,
|
|
50396
|
+
messages,
|
|
50397
|
+
selectedMessage,
|
|
50398
|
+
});
|
|
50399
|
+
const handleBackdropClick = createChatRatingBackdropClickHandler({
|
|
50400
|
+
isMobile,
|
|
50401
|
+
onClose,
|
|
50402
|
+
});
|
|
50403
|
+
return (jsx("div", { className: styles$5.ratingModal, "data-chat-modal": "rating", "data-chat-theme": mode.toLowerCase(), onClick: handleBackdropClick, children: jsxs("div", { className: styles$5.ratingModalContent, children: [jsx("h3", { children: modalCopy.title }), jsx(ChatRatingModalStarsSection, { feedbackMode: feedbackMode, hoveredRating: hoveredRating, messageRatings: messageRatings, mode: mode, selectedMessage: selectedMessage, setHoveredRating: setHoveredRating, setMessageRatings: setMessageRatings }), modalCopy.userQuestionLabel, jsx("textarea", { readOnly: true, value: userQuestion, className: styles$5.ratingInput }), modalCopy.expectedAnswerLabel, jsx("textarea", { placeholder: resolveExpectedAnswerPlaceholder(selectedMessage, modalCopy), defaultValue: selectedMessage.expectedAnswer || selectedMessage.content, onChange: (event) => {
|
|
48876
50404
|
if (selectedMessage) {
|
|
48877
50405
|
setSelectedMessage({ ...selectedMessage, expectedAnswer: event.target.value });
|
|
48878
50406
|
}
|
|
48879
|
-
}, className: styles$5.ratingInput }),
|
|
48880
|
-
? (_f = feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.reportIssueDetailsLabel) !== null && _f !== void 0 ? _f : 'Issue details:'
|
|
48881
|
-
: (_g = feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.noteLabel) !== null && _g !== void 0 ? _g : 'Note:', jsx("textarea", { placeholder: isReportIssueMode
|
|
48882
|
-
? (_h = feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.reportIssueDetailsPlaceholder) !== null && _h !== void 0 ? _h : 'Describe what went wrong (optional)'
|
|
48883
|
-
: (_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 }), jsxs("div", { className: styles$5.ratingActions, children: [jsx("button", { onClick: onClose, children: (feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.cancelLabel) || 'Cancel' }), jsx("button", { onClick: submitRating, children: isReportIssueMode
|
|
48884
|
-
? (_k = feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.reportIssueSubmitLabel) !== null && _k !== void 0 ? _k : 'Report issue'
|
|
48885
|
-
: (_l = feedbackTranslations === null || feedbackTranslations === void 0 ? void 0 : feedbackTranslations.submitLabel) !== null && _l !== void 0 ? _l : 'Submit' })] })] }) }));
|
|
50407
|
+
}, className: styles$5.ratingInput }), modalCopy.noteLabel, jsx("textarea", { placeholder: modalCopy.notePlaceholder, defaultValue: textRating, onChange: (event) => setTextRating(event.target.value), className: styles$5.ratingInput }), jsx(ChatRatingModalActions, { cancelLabel: modalCopy.cancelLabel, onClose: onClose, submitLabel: modalCopy.submitLabel, submitRating: submitRating })] }) }));
|
|
48886
50408
|
}
|
|
48887
50409
|
|
|
48888
50410
|
/**
|
|
@@ -49988,7 +51510,7 @@ function renderSearchToolCallDetails(options) {
|
|
|
49988
51510
|
const { results, rawText } = extractSearchResults(resultRaw);
|
|
49989
51511
|
const hasResults = results.length > 0;
|
|
49990
51512
|
const hasRawText = !hasResults && !!rawText && rawText.trim().length > 0;
|
|
49991
|
-
return (jsxs(Fragment, { children: [jsxs("div", { className: styles$5.searchModalHeader, children: [jsx("span", { className: styles$5.searchModalIcon, children: "\uD83D\uDD0E" }), jsx("h3", { className: styles$5.searchModalQuery, children: args.query || args.searchText || 'Search Results' })] }), jsx("div", { className: styles$5.searchModalContent, children: hasResults ? (jsx("div", { className: styles$5.searchResultsList, children: results.map((item, index) => (jsxs("div", { className: styles$5.searchResultItem, children: [jsx("div", { className: styles$5.searchResultUrl, children: item.url && (jsx("a", { href: item.url, target: "_blank", rel: "noreferrer", children: item.url })) }), jsx("h4", { className: styles$5.searchResultTitle, children: item.url ? (jsx("a", { href: item.url, target: "_blank", rel: "noreferrer", children: item.title || 'Untitled' })) : (item.title || 'Untitled') }), jsx("p", { className: styles$5.searchResultSnippet, children: item.snippet || item.content || '' })] }, index))) })) : hasRawText ? (jsx(MarkdownContent, { className: styles$5.searchResultsRaw, content: rawText })) : toolCallState !== 'COMPLETE' ? (jsxs(Fragment, { children: [renderToolCallProgressPlaceholder({
|
|
51513
|
+
return (jsxs(Fragment, { children: [jsxs("div", { className: styles$5.searchModalHeader, children: [jsx("span", { className: styles$5.searchModalIcon, children: "\uD83D\uDD0E" }), jsx("h3", { className: styles$5.searchModalQuery, children: args.query || args.searchText || 'Search Results' })] }), jsx("div", { className: styles$5.searchModalContent, children: hasResults ? (jsx("div", { className: styles$5.searchResultsList, children: results.map((item, index) => (jsxs("div", { className: styles$5.searchResultItem, children: [jsx("div", { className: styles$5.searchResultUrl, children: item.url && (jsx("a", { href: item.url, target: "_blank", rel: "noreferrer", children: item.url })) }), jsx("h4", { className: styles$5.searchResultTitle, children: item.url ? (jsx("a", { href: item.url, target: "_blank", rel: "noreferrer", children: item.title || item.source || 'Untitled' })) : (item.title || item.source || 'Untitled') }), jsx("p", { className: styles$5.searchResultSnippet, children: item.snippet || item.content || item.excerpt || '' })] }, index))) })) : hasRawText ? (jsx(MarkdownContent, { className: styles$5.searchResultsRaw, content: rawText })) : toolCallState !== 'COMPLETE' ? (jsxs(Fragment, { children: [renderToolCallProgressPlaceholder({
|
|
49992
51514
|
title: 'Search results pending',
|
|
49993
51515
|
message: resolveToolCallProgressMessage(toolCall),
|
|
49994
51516
|
}), jsxs("div", { className: styles$5.toolCallDetailsCard, children: [jsxs("div", { className: styles$5.toolCallDetailsCardRow, children: [jsx("strong", { children: "Query" }), jsx("span", { children: String(args.query || args.searchText || 'Search query is being prepared.') })] }), args.location && (jsxs("div", { className: styles$5.toolCallDetailsCardRow, children: [jsx("strong", { children: "Location" }), jsx("span", { children: String(args.location) })] })), args.engine && (jsxs("div", { className: styles$5.toolCallDetailsCardRow, children: [jsx("strong", { children: "Engine" }), jsx("span", { children: String(args.engine) })] }))] })] })) : (jsx("div", { className: styles$5.noResults, children: resultRaw ? 'No search results found.' : 'Search results are not available.' })) })] }));
|
|
@@ -50193,6 +51715,18 @@ const DEFAULT_TIMEOUT_SNOOZE_DURATION_MILLISECONDS = 5 * 60 * 1000;
|
|
|
50193
51715
|
* @private function of ChatToolCallModal
|
|
50194
51716
|
*/
|
|
50195
51717
|
function renderTimeoutToolCallDetails(options) {
|
|
51718
|
+
const viewModel = createTimeoutToolCallDetailsViewModel(options);
|
|
51719
|
+
return (jsxs(Fragment, { children: [jsxs("div", { className: styles$5.searchModalHeader, children: [jsx("span", { className: styles$5.searchModalIcon, children: "\u23F1\uFE0F" }), jsx("h3", { className: styles$5.searchModalQuery, children: viewModel.title })] }), jsxs("div", { className: styles$5.searchModalContent, children: [renderTimeoutToolCallClock(viewModel), renderTimeoutToolCallSummary(viewModel), renderTimeoutToolCallActions(viewModel)] })] }));
|
|
51720
|
+
}
|
|
51721
|
+
/**
|
|
51722
|
+
* Prepares the values rendered by the timeout detail view.
|
|
51723
|
+
*
|
|
51724
|
+
* @param options - Raw timeout tool call data.
|
|
51725
|
+
* @returns Derived timeout detail view model.
|
|
51726
|
+
*
|
|
51727
|
+
* @private function of ChatToolCallModal
|
|
51728
|
+
*/
|
|
51729
|
+
function createTimeoutToolCallDetailsViewModel(options) {
|
|
50196
51730
|
const { toolCallName, args, resultRaw, toolCallDate, onRequestAdvancedView, locale, chatUiTranslations } = options;
|
|
50197
51731
|
const timeoutPresentation = resolveTimeoutToolCallPresentation({
|
|
50198
51732
|
toolCallName,
|
|
@@ -50201,40 +51735,120 @@ function renderTimeoutToolCallDetails(options) {
|
|
|
50201
51735
|
currentDate: new Date(),
|
|
50202
51736
|
locale,
|
|
50203
51737
|
});
|
|
50204
|
-
const
|
|
50205
|
-
|
|
50206
|
-
|
|
50207
|
-
|
|
50208
|
-
|
|
50209
|
-
|
|
50210
|
-
: (
|
|
50211
|
-
|
|
50212
|
-
|
|
50213
|
-
|
|
50214
|
-
|
|
50215
|
-
|
|
50216
|
-
|
|
50217
|
-
|
|
50218
|
-
|
|
50219
|
-
|
|
50220
|
-
|
|
50221
|
-
|
|
50222
|
-
|
|
50223
|
-
: null
|
|
50224
|
-
|
|
50225
|
-
|
|
50226
|
-
|
|
50227
|
-
|
|
50228
|
-
|
|
50229
|
-
|
|
50230
|
-
|
|
50231
|
-
|
|
50232
|
-
|
|
50233
|
-
|
|
50234
|
-
|
|
50235
|
-
|
|
50236
|
-
|
|
50237
|
-
|
|
51738
|
+
const labels = resolveTimeoutToolCallDetailLabels(chatUiTranslations);
|
|
51739
|
+
return {
|
|
51740
|
+
labels,
|
|
51741
|
+
locale,
|
|
51742
|
+
onRequestAdvancedView,
|
|
51743
|
+
title: resolveTimeoutToolCallTitle(timeoutPresentation, labels),
|
|
51744
|
+
clockDate: (timeoutPresentation === null || timeoutPresentation === void 0 ? void 0 : timeoutPresentation.dueAtDate) || toolCallDate,
|
|
51745
|
+
primarySentence: timeoutPresentation
|
|
51746
|
+
? buildTimeoutToolPrimarySentence(timeoutPresentation, chatUiTranslations)
|
|
51747
|
+
: labels.loadingMessage,
|
|
51748
|
+
scheduleSentence: timeoutPresentation
|
|
51749
|
+
? buildTimeoutToolScheduleSentence(timeoutPresentation, chatUiTranslations)
|
|
51750
|
+
: null,
|
|
51751
|
+
relativeDueLabel: (timeoutPresentation === null || timeoutPresentation === void 0 ? void 0 : timeoutPresentation.relativeDueLabel) || null,
|
|
51752
|
+
timezoneLine: (timeoutPresentation === null || timeoutPresentation === void 0 ? void 0 : timeoutPresentation.localTimezone)
|
|
51753
|
+
? `${labels.timezoneLabel} ${timeoutPresentation.localTimezone}`
|
|
51754
|
+
: null,
|
|
51755
|
+
localDueDateLabel: (timeoutPresentation === null || timeoutPresentation === void 0 ? void 0 : timeoutPresentation.localDueDateLabel) || null,
|
|
51756
|
+
message: (timeoutPresentation === null || timeoutPresentation === void 0 ? void 0 : timeoutPresentation.message) || null,
|
|
51757
|
+
cancelCommand: (timeoutPresentation === null || timeoutPresentation === void 0 ? void 0 : timeoutPresentation.timeoutId) && timeoutPresentation.action !== 'cancel'
|
|
51758
|
+
? createCancelTimeoutQuickActionCommand(timeoutPresentation.timeoutId)
|
|
51759
|
+
: null,
|
|
51760
|
+
snoozeCommand: createSnoozeTimeoutQuickActionCommand((timeoutPresentation === null || timeoutPresentation === void 0 ? void 0 : timeoutPresentation.message) || undefined),
|
|
51761
|
+
};
|
|
51762
|
+
}
|
|
51763
|
+
/**
|
|
51764
|
+
* Resolves the timeout detail title for the current presentation state.
|
|
51765
|
+
*
|
|
51766
|
+
* @param timeoutPresentation - Friendly timeout presentation data.
|
|
51767
|
+
* @param labels - Localized labels with fallbacks.
|
|
51768
|
+
* @returns One timeout modal title.
|
|
51769
|
+
*
|
|
51770
|
+
* @private function of ChatToolCallModal
|
|
51771
|
+
*/
|
|
51772
|
+
function resolveTimeoutToolCallTitle(timeoutPresentation, labels) {
|
|
51773
|
+
if ((timeoutPresentation === null || timeoutPresentation === void 0 ? void 0 : timeoutPresentation.action) !== 'cancel') {
|
|
51774
|
+
return labels.titleScheduled;
|
|
51775
|
+
}
|
|
51776
|
+
return timeoutPresentation.status === 'cancelled' ? labels.titleCancelled : labels.titleUpdated;
|
|
51777
|
+
}
|
|
51778
|
+
/**
|
|
51779
|
+
* Resolves timeout-detail translations with stable fallback strings.
|
|
51780
|
+
*
|
|
51781
|
+
* @param chatUiTranslations - Optional translation overrides.
|
|
51782
|
+
* @returns Complete timeout detail labels.
|
|
51783
|
+
*
|
|
51784
|
+
* @private function of ChatToolCallModal
|
|
51785
|
+
*/
|
|
51786
|
+
function resolveTimeoutToolCallDetailLabels(chatUiTranslations) {
|
|
51787
|
+
return {
|
|
51788
|
+
titleScheduled: (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallTimeoutTitle) || 'Timeout scheduled',
|
|
51789
|
+
titleCancelled: (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallTimeoutCancelledTitle) || 'Timeout cancelled',
|
|
51790
|
+
titleUpdated: (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallTimeoutUpdateTitle) || 'Timeout update',
|
|
51791
|
+
loadingMessage: (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallTimeoutLoadingMessage) || 'Timeout details are still loading.',
|
|
51792
|
+
unavailableMessage: (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallTimeoutUnavailableMessage) || 'Scheduled time is unavailable.',
|
|
51793
|
+
dateLabel: (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallTimeoutDateLabel) || 'Date:',
|
|
51794
|
+
messageLabel: (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallTimeoutMessageLabel) || 'Message:',
|
|
51795
|
+
timezoneLabel: (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallTimeoutTimezoneLabel) || 'Timezone:',
|
|
51796
|
+
actionGroupLabel: (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallTimeoutActionGroupLabel) || 'Timeout quick actions',
|
|
51797
|
+
cancelAriaLabel: (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallTimeoutCancelAriaLabel) || 'Cancel timeout',
|
|
51798
|
+
cancelButton: (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallTimeoutCancelButton) || 'Cancel',
|
|
51799
|
+
snoozeAriaLabel: (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallTimeoutSnoozeAriaLabel) || 'Snooze timeout',
|
|
51800
|
+
snoozeButton: (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallTimeoutSnoozeButton) || 'Snooze',
|
|
51801
|
+
viewAdvancedAriaLabel: (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallTimeoutViewAdvancedAriaLabel) || 'View advanced timeout details',
|
|
51802
|
+
viewAdvancedButton: (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallTimeoutViewAdvancedButton) || 'View advanced',
|
|
51803
|
+
};
|
|
51804
|
+
}
|
|
51805
|
+
/**
|
|
51806
|
+
* Renders the timeout clock panel or its unavailable fallback.
|
|
51807
|
+
*
|
|
51808
|
+
* @param viewModel - Prepared timeout detail state.
|
|
51809
|
+
* @returns Clock section element.
|
|
51810
|
+
*
|
|
51811
|
+
* @private function of ChatToolCallModal
|
|
51812
|
+
*/
|
|
51813
|
+
function renderTimeoutToolCallClock(viewModel) {
|
|
51814
|
+
if (viewModel.clockDate && !Number.isNaN(viewModel.clockDate.getTime())) {
|
|
51815
|
+
return renderToolCallClockPanel({
|
|
51816
|
+
date: viewModel.clockDate,
|
|
51817
|
+
relativeLabel: viewModel.relativeDueLabel,
|
|
51818
|
+
timezoneLabel: viewModel.timezoneLine,
|
|
51819
|
+
locale: viewModel.locale,
|
|
51820
|
+
});
|
|
51821
|
+
}
|
|
51822
|
+
return jsx("p", { className: styles$5.toolCallEmpty, children: viewModel.labels.unavailableMessage });
|
|
51823
|
+
}
|
|
51824
|
+
/**
|
|
51825
|
+
* Renders the timeout summary sentences and optional metadata lines.
|
|
51826
|
+
*
|
|
51827
|
+
* @param viewModel - Prepared timeout detail state.
|
|
51828
|
+
* @returns Summary section element.
|
|
51829
|
+
*
|
|
51830
|
+
* @private function of ChatToolCallModal
|
|
51831
|
+
*/
|
|
51832
|
+
function renderTimeoutToolCallSummary(viewModel) {
|
|
51833
|
+
return (jsxs("div", { className: styles$5.timeoutToolSummary, children: [jsx("p", { className: styles$5.timeoutToolPrimarySentence, children: viewModel.primarySentence }), viewModel.scheduleSentence && (jsx("p", { className: styles$5.timeoutToolSecondarySentence, children: viewModel.scheduleSentence })), viewModel.localDueDateLabel && (jsxs("p", { className: styles$5.timeoutToolSecondarySentence, children: [viewModel.labels.dateLabel, " ", viewModel.localDueDateLabel] })), viewModel.message && (jsxs("p", { className: styles$5.timeoutToolSecondarySentence, children: [viewModel.labels.messageLabel, " ", viewModel.message] }))] }));
|
|
51834
|
+
}
|
|
51835
|
+
/**
|
|
51836
|
+
* Renders timeout quick-action buttons.
|
|
51837
|
+
*
|
|
51838
|
+
* @param viewModel - Prepared timeout detail state.
|
|
51839
|
+
* @returns Quick actions element.
|
|
51840
|
+
*
|
|
51841
|
+
* @private function of ChatToolCallModal
|
|
51842
|
+
*/
|
|
51843
|
+
function renderTimeoutToolCallActions(viewModel) {
|
|
51844
|
+
return (jsxs("div", { className: styles$5.timeoutToolActionRow, role: "group", "aria-label": viewModel.labels.actionGroupLabel, children: [jsx("button", { type: "button", className: styles$5.timeoutToolActionButton, onClick: () => {
|
|
51845
|
+
if (!viewModel.cancelCommand) {
|
|
51846
|
+
return;
|
|
51847
|
+
}
|
|
51848
|
+
copyTimeoutQuickActionCommand(viewModel.cancelCommand);
|
|
51849
|
+
}, disabled: !viewModel.cancelCommand, "aria-label": viewModel.labels.cancelAriaLabel, children: viewModel.labels.cancelButton }), jsx("button", { type: "button", className: styles$5.timeoutToolActionButton, onClick: () => {
|
|
51850
|
+
copyTimeoutQuickActionCommand(viewModel.snoozeCommand);
|
|
51851
|
+
}, "aria-label": viewModel.labels.snoozeAriaLabel, children: viewModel.labels.snoozeButton }), viewModel.onRequestAdvancedView && (jsx("button", { type: "button", className: styles$5.timeoutToolActionButton, onClick: viewModel.onRequestAdvancedView, "aria-label": viewModel.labels.viewAdvancedAriaLabel, children: viewModel.labels.viewAdvancedButton }))] }));
|
|
50238
51852
|
}
|
|
50239
51853
|
/**
|
|
50240
51854
|
* Copies one quick action command to clipboard when supported.
|
|
@@ -50476,6 +52090,7 @@ function renderGenericToolCallDetails(options) {
|
|
|
50476
52090
|
function isSearchToolCallName(toolName) {
|
|
50477
52091
|
return (toolName === 'web_search' ||
|
|
50478
52092
|
toolName === 'deep_search' ||
|
|
52093
|
+
toolName === 'knowledge_search' ||
|
|
50479
52094
|
toolName === 'useSearchEngine' ||
|
|
50480
52095
|
toolName === 'search');
|
|
50481
52096
|
}
|
|
@@ -50822,6 +52437,279 @@ const PauseIcon = ({ size = 16 }) => (jsx("svg", { width: size, height: size, vi
|
|
|
50822
52437
|
*/
|
|
50823
52438
|
const PlayIcon = ({ size = 16 }) => (jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "currentColor", role: "img", "aria-label": "Play", children: jsx("path", { d: "M8 5v14l11-7L8 5z" }) }));
|
|
50824
52439
|
|
|
52440
|
+
/**
|
|
52441
|
+
* Resolves one delay value, including random ranges.
|
|
52442
|
+
*
|
|
52443
|
+
* @private function of `MockedChat`
|
|
52444
|
+
*/
|
|
52445
|
+
function getDelay(val, fallback) {
|
|
52446
|
+
if (Array.isArray(val) && val.length === 2) {
|
|
52447
|
+
const [min, max] = val;
|
|
52448
|
+
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
52449
|
+
}
|
|
52450
|
+
if (typeof val === 'number') {
|
|
52451
|
+
return val;
|
|
52452
|
+
}
|
|
52453
|
+
return fallback;
|
|
52454
|
+
}
|
|
52455
|
+
/**
|
|
52456
|
+
* Normalizes message offsets into a monotonic deterministic timeline.
|
|
52457
|
+
*
|
|
52458
|
+
* @private function of `MockedChat`
|
|
52459
|
+
*/
|
|
52460
|
+
function normalizeMessageOffsets(messageOffsetsMs, originalMessages) {
|
|
52461
|
+
if (!messageOffsetsMs || messageOffsetsMs.length === 0) {
|
|
52462
|
+
return null;
|
|
52463
|
+
}
|
|
52464
|
+
let previousOffset = 0;
|
|
52465
|
+
return originalMessages.map((_message, messageIndex) => {
|
|
52466
|
+
const rawOffset = messageOffsetsMs[messageIndex];
|
|
52467
|
+
const nextOffset = Number.isFinite(rawOffset) ? Math.max(0, Math.floor(rawOffset || 0)) : previousOffset;
|
|
52468
|
+
previousOffset = Math.max(previousOffset, nextOffset);
|
|
52469
|
+
return previousOffset;
|
|
52470
|
+
});
|
|
52471
|
+
}
|
|
52472
|
+
/**
|
|
52473
|
+
* Resolves the deterministic wait before one message when fixed offsets are supplied.
|
|
52474
|
+
*
|
|
52475
|
+
* @private function of `MockedChat`
|
|
52476
|
+
*/
|
|
52477
|
+
function resolveOffsetDelayBeforeMessage(normalizedMessageOffsetsMs, messageIndex) {
|
|
52478
|
+
if (!normalizedMessageOffsetsMs) {
|
|
52479
|
+
return 0;
|
|
52480
|
+
}
|
|
52481
|
+
const currentOffset = normalizedMessageOffsetsMs[messageIndex] || 0;
|
|
52482
|
+
const previousOffset = messageIndex > 0 ? normalizedMessageOffsetsMs[messageIndex - 1] || 0 : 0;
|
|
52483
|
+
return Math.max(0, currentOffset - previousOffset);
|
|
52484
|
+
}
|
|
52485
|
+
/**
|
|
52486
|
+
* Creates the message shape rendered while the mocked transcript is being simulated.
|
|
52487
|
+
*
|
|
52488
|
+
* @private function of `MockedChat`
|
|
52489
|
+
*/
|
|
52490
|
+
function createDisplayedMockedChatMessage(originalMessage, content, isComplete) {
|
|
52491
|
+
return {
|
|
52492
|
+
id: originalMessage.id,
|
|
52493
|
+
createdAt: originalMessage.createdAt,
|
|
52494
|
+
sender: originalMessage.sender,
|
|
52495
|
+
content,
|
|
52496
|
+
isComplete,
|
|
52497
|
+
expectedAnswer: originalMessage.expectedAnswer,
|
|
52498
|
+
isVoiceCall: originalMessage.isVoiceCall,
|
|
52499
|
+
};
|
|
52500
|
+
}
|
|
52501
|
+
/**
|
|
52502
|
+
* Appends one new rendered message to the mocked transcript.
|
|
52503
|
+
*
|
|
52504
|
+
* @private function of `MockedChat`
|
|
52505
|
+
*/
|
|
52506
|
+
function appendDisplayedMessage(setDisplayedMessages, nextMessage) {
|
|
52507
|
+
setDisplayedMessages((previousMessages) => [...previousMessages, nextMessage]);
|
|
52508
|
+
}
|
|
52509
|
+
/**
|
|
52510
|
+
* Replaces the most recently rendered message in the mocked transcript.
|
|
52511
|
+
*
|
|
52512
|
+
* @private function of `MockedChat`
|
|
52513
|
+
*/
|
|
52514
|
+
function replaceLastDisplayedMessage(setDisplayedMessages, nextMessage) {
|
|
52515
|
+
setDisplayedMessages((previousMessages) => {
|
|
52516
|
+
const nextMessages = [...previousMessages];
|
|
52517
|
+
nextMessages[nextMessages.length - 1] = nextMessage;
|
|
52518
|
+
return nextMessages;
|
|
52519
|
+
});
|
|
52520
|
+
}
|
|
52521
|
+
/**
|
|
52522
|
+
* Resets all simulation-owned UI state before one playback run starts.
|
|
52523
|
+
*
|
|
52524
|
+
* @private function of `MockedChat`
|
|
52525
|
+
*/
|
|
52526
|
+
function resetSimulationState(params) {
|
|
52527
|
+
const { setDisplayedMessages, setLocalAppendedMessages, setIsSimulationComplete } = params;
|
|
52528
|
+
setDisplayedMessages([]);
|
|
52529
|
+
setLocalAppendedMessages([]);
|
|
52530
|
+
setIsSimulationComplete(false);
|
|
52531
|
+
}
|
|
52532
|
+
/**
|
|
52533
|
+
* Finalizes the playback run and notifies the caller.
|
|
52534
|
+
*
|
|
52535
|
+
* @private function of `MockedChat`
|
|
52536
|
+
*/
|
|
52537
|
+
function completeSimulation(params) {
|
|
52538
|
+
var _a;
|
|
52539
|
+
const { setIsSimulationComplete, onSimulationCompleteRef } = params;
|
|
52540
|
+
setIsSimulationComplete(true);
|
|
52541
|
+
(_a = onSimulationCompleteRef.current) === null || _a === void 0 ? void 0 : _a.call(onSimulationCompleteRef);
|
|
52542
|
+
}
|
|
52543
|
+
/**
|
|
52544
|
+
* Waits for one delay and reports whether the current playback was cancelled.
|
|
52545
|
+
*
|
|
52546
|
+
* @private function of `MockedChat`
|
|
52547
|
+
*/
|
|
52548
|
+
async function waitForDelay(delayMs, isCancelledRef) {
|
|
52549
|
+
if (delayMs > 0) {
|
|
52550
|
+
await forTime(delayMs);
|
|
52551
|
+
}
|
|
52552
|
+
return isCancelledRef();
|
|
52553
|
+
}
|
|
52554
|
+
/**
|
|
52555
|
+
* Waits for a paused simulation to resume when a pause has been requested.
|
|
52556
|
+
*
|
|
52557
|
+
* @private function of `MockedChat`
|
|
52558
|
+
*/
|
|
52559
|
+
async function waitForPauseIfRequested(params) {
|
|
52560
|
+
const { pauseRequestedRef, waitIfPaused, isCancelledRef } = params;
|
|
52561
|
+
if (!pauseRequestedRef.current) {
|
|
52562
|
+
return false;
|
|
52563
|
+
}
|
|
52564
|
+
await waitIfPaused(isCancelledRef);
|
|
52565
|
+
return isCancelledRef();
|
|
52566
|
+
}
|
|
52567
|
+
/**
|
|
52568
|
+
* Resolves the first wait before the simulated transcript starts rendering.
|
|
52569
|
+
*
|
|
52570
|
+
* @private function of `MockedChat`
|
|
52571
|
+
*/
|
|
52572
|
+
function resolveInitialSimulationDelay(params) {
|
|
52573
|
+
const { delays, normalizedMessageOffsetsMs, firstMessageIndex } = params;
|
|
52574
|
+
if (normalizedMessageOffsetsMs) {
|
|
52575
|
+
return resolveOffsetDelayBeforeMessage(normalizedMessageOffsetsMs, firstMessageIndex);
|
|
52576
|
+
}
|
|
52577
|
+
return getDelay(delays.beforeFirstMessage, 1000);
|
|
52578
|
+
}
|
|
52579
|
+
/**
|
|
52580
|
+
* Returns true when the next inter-message wait should use a longer pause.
|
|
52581
|
+
*
|
|
52582
|
+
* @private function of `MockedChat`
|
|
52583
|
+
*/
|
|
52584
|
+
function shouldUseLongPause(params) {
|
|
52585
|
+
const { delays, originalMessages, messageIndex } = params;
|
|
52586
|
+
return (!!delays.longPauseChance &&
|
|
52587
|
+
Math.random() < delays.longPauseChance &&
|
|
52588
|
+
messageIndex > 0 &&
|
|
52589
|
+
originalMessages[messageIndex].sender !== originalMessages[messageIndex - 1].sender);
|
|
52590
|
+
}
|
|
52591
|
+
/**
|
|
52592
|
+
* Resolves the wait between two rendered messages.
|
|
52593
|
+
*
|
|
52594
|
+
* @private function of `MockedChat`
|
|
52595
|
+
*/
|
|
52596
|
+
function resolveInterMessageDelay(params) {
|
|
52597
|
+
const { delays, originalMessages, normalizedMessageOffsetsMs, messageIndex } = params;
|
|
52598
|
+
if (normalizedMessageOffsetsMs) {
|
|
52599
|
+
return resolveOffsetDelayBeforeMessage(normalizedMessageOffsetsMs, messageIndex);
|
|
52600
|
+
}
|
|
52601
|
+
if (shouldUseLongPause({
|
|
52602
|
+
delays,
|
|
52603
|
+
originalMessages,
|
|
52604
|
+
messageIndex,
|
|
52605
|
+
})) {
|
|
52606
|
+
return getDelay(delays.longPauseDuration, 2000);
|
|
52607
|
+
}
|
|
52608
|
+
return getDelay(delays.thinkingBetweenMessages, 2000);
|
|
52609
|
+
}
|
|
52610
|
+
/**
|
|
52611
|
+
* Types out one message word by word and marks it complete when finished.
|
|
52612
|
+
*
|
|
52613
|
+
* @private function of `MockedChat`
|
|
52614
|
+
*/
|
|
52615
|
+
async function typeMockedChatMessage(params) {
|
|
52616
|
+
const { currentMessage, delays, normalizedMessageOffsetsMs, setDisplayedMessages, isCancelledRef } = params;
|
|
52617
|
+
const incompleteMessage = createDisplayedMockedChatMessage(currentMessage, '', false);
|
|
52618
|
+
appendDisplayedMessage(setDisplayedMessages, incompleteMessage);
|
|
52619
|
+
const words = currentMessage.content.split(' ');
|
|
52620
|
+
let currentContent = '';
|
|
52621
|
+
for (let wordIndex = 0; wordIndex < words.length; wordIndex++) {
|
|
52622
|
+
if (isCancelledRef()) {
|
|
52623
|
+
return true;
|
|
52624
|
+
}
|
|
52625
|
+
const word = words[wordIndex];
|
|
52626
|
+
currentContent += (wordIndex > 0 ? ' ' : '') + word;
|
|
52627
|
+
replaceLastDisplayedMessage(setDisplayedMessages, createDisplayedMockedChatMessage(currentMessage, currentContent, false));
|
|
52628
|
+
const wordDelayMs = getDelay(delays.waitAfterWord, 100) + getDelay(delays.extraWordDelay, 50);
|
|
52629
|
+
if (await waitForDelay(wordDelayMs, isCancelledRef)) {
|
|
52630
|
+
return true;
|
|
52631
|
+
}
|
|
52632
|
+
}
|
|
52633
|
+
replaceLastDisplayedMessage(setDisplayedMessages, createDisplayedMockedChatMessage(currentMessage, currentMessage.content, true));
|
|
52634
|
+
if (!normalizedMessageOffsetsMs && (await waitForDelay(200, isCancelledRef))) {
|
|
52635
|
+
return true;
|
|
52636
|
+
}
|
|
52637
|
+
return false;
|
|
52638
|
+
}
|
|
52639
|
+
/**
|
|
52640
|
+
* Runs one full mocked-chat playback cycle.
|
|
52641
|
+
*
|
|
52642
|
+
* @private function of `MockedChat`
|
|
52643
|
+
*/
|
|
52644
|
+
async function simulateMockedChatPlayback(props) {
|
|
52645
|
+
const { delays, originalMessages, normalizedMessageOffsetsMs, pauseRequestedRef, waitIfPaused, setDisplayedMessages, setLocalAppendedMessages, setIsSimulationComplete, onSimulationCompleteRef, isCancelledRef, } = props;
|
|
52646
|
+
resetSimulationState({
|
|
52647
|
+
setDisplayedMessages,
|
|
52648
|
+
setLocalAppendedMessages,
|
|
52649
|
+
setIsSimulationComplete,
|
|
52650
|
+
});
|
|
52651
|
+
if (originalMessages.length === 0) {
|
|
52652
|
+
completeSimulation({
|
|
52653
|
+
setIsSimulationComplete,
|
|
52654
|
+
onSimulationCompleteRef,
|
|
52655
|
+
});
|
|
52656
|
+
return;
|
|
52657
|
+
}
|
|
52658
|
+
const showIntermediateMessages = delays.showIntermediateMessages || 0;
|
|
52659
|
+
if (showIntermediateMessages > 0) {
|
|
52660
|
+
setDisplayedMessages(originalMessages.slice(0, showIntermediateMessages));
|
|
52661
|
+
}
|
|
52662
|
+
if (await waitForDelay(resolveInitialSimulationDelay({
|
|
52663
|
+
delays,
|
|
52664
|
+
normalizedMessageOffsetsMs,
|
|
52665
|
+
firstMessageIndex: showIntermediateMessages,
|
|
52666
|
+
}), isCancelledRef)) {
|
|
52667
|
+
return;
|
|
52668
|
+
}
|
|
52669
|
+
for (let messageIndex = showIntermediateMessages; messageIndex < originalMessages.length; messageIndex++) {
|
|
52670
|
+
if (await waitForPauseIfRequested({
|
|
52671
|
+
pauseRequestedRef,
|
|
52672
|
+
waitIfPaused,
|
|
52673
|
+
isCancelledRef,
|
|
52674
|
+
})) {
|
|
52675
|
+
return;
|
|
52676
|
+
}
|
|
52677
|
+
const currentMessage = originalMessages[messageIndex];
|
|
52678
|
+
if (!currentMessage) {
|
|
52679
|
+
continue;
|
|
52680
|
+
}
|
|
52681
|
+
if (messageIndex > showIntermediateMessages) {
|
|
52682
|
+
if (await waitForDelay(resolveInterMessageDelay({
|
|
52683
|
+
delays,
|
|
52684
|
+
originalMessages,
|
|
52685
|
+
normalizedMessageOffsetsMs,
|
|
52686
|
+
messageIndex,
|
|
52687
|
+
}), isCancelledRef)) {
|
|
52688
|
+
return;
|
|
52689
|
+
}
|
|
52690
|
+
if (await waitForPauseIfRequested({
|
|
52691
|
+
pauseRequestedRef,
|
|
52692
|
+
waitIfPaused,
|
|
52693
|
+
isCancelledRef,
|
|
52694
|
+
})) {
|
|
52695
|
+
return;
|
|
52696
|
+
}
|
|
52697
|
+
}
|
|
52698
|
+
if (await typeMockedChatMessage({
|
|
52699
|
+
currentMessage,
|
|
52700
|
+
delays,
|
|
52701
|
+
normalizedMessageOffsetsMs,
|
|
52702
|
+
setDisplayedMessages,
|
|
52703
|
+
isCancelledRef,
|
|
52704
|
+
})) {
|
|
52705
|
+
return;
|
|
52706
|
+
}
|
|
52707
|
+
}
|
|
52708
|
+
completeSimulation({
|
|
52709
|
+
setIsSimulationComplete,
|
|
52710
|
+
onSimulationCompleteRef,
|
|
52711
|
+
});
|
|
52712
|
+
}
|
|
50825
52713
|
/**
|
|
50826
52714
|
* MockedChat component that shows the same chat as Chat but emulates ongoing discussion
|
|
50827
52715
|
* with realistic typing delays and thinking pauses.
|
|
@@ -50830,16 +52718,6 @@ const PlayIcon = ({ size = 16 }) => (jsx("svg", { width: size, height: size, vie
|
|
|
50830
52718
|
*/
|
|
50831
52719
|
function MockedChat(props) {
|
|
50832
52720
|
const { delayConfig, messages: originalMessages, isResettable = true, isPausable = true, messageOffsetsMs, appendMessagesLocallyOnSend = false, onSimulationComplete, isSaveButtonEnabled = true, ...chatProps } = props;
|
|
50833
|
-
// Helper to get random delay from config
|
|
50834
|
-
function getDelay(val, fallback) {
|
|
50835
|
-
if (Array.isArray(val) && val.length === 2) {
|
|
50836
|
-
const [min, max] = val;
|
|
50837
|
-
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
50838
|
-
}
|
|
50839
|
-
if (typeof val === 'number')
|
|
50840
|
-
return val;
|
|
50841
|
-
return fallback;
|
|
50842
|
-
}
|
|
50843
52721
|
const delays = {
|
|
50844
52722
|
...MOCKED_CHAT_DELAY_CONFIGS.NORMAL_FLOW,
|
|
50845
52723
|
...delayConfig,
|
|
@@ -50847,18 +52725,7 @@ function MockedChat(props) {
|
|
|
50847
52725
|
const [displayedMessages, setDisplayedMessages] = useState([]);
|
|
50848
52726
|
const [localAppendedMessages, setLocalAppendedMessages] = useState([]);
|
|
50849
52727
|
const [isSimulationComplete, setIsSimulationComplete] = useState(false);
|
|
50850
|
-
const normalizedMessageOffsetsMs = useMemo(() =>
|
|
50851
|
-
if (!messageOffsetsMs || messageOffsetsMs.length === 0) {
|
|
50852
|
-
return null;
|
|
50853
|
-
}
|
|
50854
|
-
let previousOffset = 0;
|
|
50855
|
-
return originalMessages.map((_message, messageIndex) => {
|
|
50856
|
-
const rawOffset = messageOffsetsMs[messageIndex];
|
|
50857
|
-
const nextOffset = Number.isFinite(rawOffset) ? Math.max(0, Math.floor(rawOffset || 0)) : previousOffset;
|
|
50858
|
-
previousOffset = Math.max(previousOffset, nextOffset);
|
|
50859
|
-
return previousOffset;
|
|
50860
|
-
});
|
|
50861
|
-
}, [messageOffsetsMs, originalMessages]);
|
|
52728
|
+
const normalizedMessageOffsetsMs = useMemo(() => normalizeMessageOffsets(messageOffsetsMs, originalMessages), [messageOffsetsMs, originalMessages]);
|
|
50862
52729
|
// Playback state machine
|
|
50863
52730
|
// RUNNING -> (user clicks Pause) -> PAUSING (finish current message) -> PAUSED
|
|
50864
52731
|
// PAUSED -> (user clicks Resume) -> RUNNING
|
|
@@ -50879,193 +52746,48 @@ function MockedChat(props) {
|
|
|
50879
52746
|
setIsSimulationComplete(false);
|
|
50880
52747
|
setResetNonce((nonce) => nonce + 1);
|
|
50881
52748
|
};
|
|
50882
|
-
}, [
|
|
52749
|
+
}, [isResettable]);
|
|
50883
52750
|
// Helper: Wait while paused (entered only between messages, never mid-typing)
|
|
50884
52751
|
const waitIfPaused = async (isCancelledRef) => {
|
|
50885
|
-
if (!pauseRequestedRef.current)
|
|
52752
|
+
if (!pauseRequestedRef.current) {
|
|
50886
52753
|
return;
|
|
52754
|
+
}
|
|
50887
52755
|
setPlaybackState('PAUSED');
|
|
50888
52756
|
// Busy wait with small sleeps until resume
|
|
50889
52757
|
while (pauseRequestedRef.current) {
|
|
50890
|
-
if (isCancelledRef())
|
|
52758
|
+
if (isCancelledRef()) {
|
|
50891
52759
|
return;
|
|
52760
|
+
}
|
|
50892
52761
|
await forTime(100);
|
|
50893
52762
|
}
|
|
50894
|
-
// Resumed
|
|
50895
52763
|
setPlaybackState('RUNNING');
|
|
50896
52764
|
};
|
|
50897
52765
|
const requestPause = () => {
|
|
50898
52766
|
if (playbackState === 'RUNNING') {
|
|
50899
52767
|
pauseRequestedRef.current = true;
|
|
50900
|
-
// Will flip to PAUSING when current message completes
|
|
50901
52768
|
setPlaybackState('PAUSING');
|
|
50902
52769
|
}
|
|
50903
52770
|
};
|
|
50904
52771
|
const resume = () => {
|
|
50905
52772
|
pauseRequestedRef.current = false;
|
|
50906
52773
|
if (playbackState !== 'RUNNING') {
|
|
50907
|
-
// Actual state will become RUNNING after loop exits waitIfPaused
|
|
50908
52774
|
setPlaybackState('RUNNING');
|
|
50909
52775
|
}
|
|
50910
52776
|
};
|
|
50911
|
-
/**
|
|
50912
|
-
* Resolves deterministic waiting time before one message when fixed offsets are supplied.
|
|
50913
|
-
*/
|
|
50914
|
-
const resolveOffsetDelayBeforeMessage = useCallback((messageIndex) => {
|
|
50915
|
-
if (!normalizedMessageOffsetsMs) {
|
|
50916
|
-
return 0;
|
|
50917
|
-
}
|
|
50918
|
-
const currentOffset = normalizedMessageOffsetsMs[messageIndex] || 0;
|
|
50919
|
-
const previousOffset = messageIndex > 0 ? normalizedMessageOffsetsMs[messageIndex - 1] || 0 : 0;
|
|
50920
|
-
return Math.max(0, currentOffset - previousOffset);
|
|
50921
|
-
}, [normalizedMessageOffsetsMs]);
|
|
50922
52777
|
useEffect(() => {
|
|
50923
52778
|
let isCancelled = false;
|
|
50924
|
-
|
|
50925
|
-
|
|
50926
|
-
|
|
50927
|
-
|
|
50928
|
-
|
|
50929
|
-
|
|
50930
|
-
|
|
50931
|
-
|
|
50932
|
-
|
|
50933
|
-
|
|
50934
|
-
|
|
50935
|
-
|
|
50936
|
-
const showIntermediateMessages = delays.showIntermediateMessages || 0;
|
|
50937
|
-
if (showIntermediateMessages > 0) {
|
|
50938
|
-
setDisplayedMessages(originalMessages.slice(0, showIntermediateMessages));
|
|
50939
|
-
}
|
|
50940
|
-
// Wait before first rendered message.
|
|
50941
|
-
const firstMessageIndex = showIntermediateMessages;
|
|
50942
|
-
const initialDelay = normalizedMessageOffsetsMs
|
|
50943
|
-
? resolveOffsetDelayBeforeMessage(firstMessageIndex)
|
|
50944
|
-
: getDelay(delays.beforeFirstMessage, 1000);
|
|
50945
|
-
if (initialDelay > 0) {
|
|
50946
|
-
await forTime(initialDelay);
|
|
50947
|
-
}
|
|
50948
|
-
if (isCancelled)
|
|
50949
|
-
return;
|
|
50950
|
-
for (let i = showIntermediateMessages; i < originalMessages.length; i++) {
|
|
50951
|
-
// If a pause was requested earlier, we only pause between messages
|
|
50952
|
-
if (pauseRequestedRef.current) {
|
|
50953
|
-
await waitIfPaused(() => isCancelled);
|
|
50954
|
-
if (isCancelled)
|
|
50955
|
-
return;
|
|
50956
|
-
}
|
|
50957
|
-
if (isCancelled)
|
|
50958
|
-
return;
|
|
50959
|
-
const currentMessage = originalMessages[i];
|
|
50960
|
-
if (!currentMessage)
|
|
50961
|
-
continue;
|
|
50962
|
-
// Add delay between rendered messages.
|
|
50963
|
-
if (i > showIntermediateMessages) {
|
|
50964
|
-
if (normalizedMessageOffsetsMs) {
|
|
50965
|
-
const deterministicDelay = resolveOffsetDelayBeforeMessage(i);
|
|
50966
|
-
if (deterministicDelay > 0) {
|
|
50967
|
-
await forTime(deterministicDelay);
|
|
50968
|
-
if (isCancelled)
|
|
50969
|
-
return;
|
|
50970
|
-
}
|
|
50971
|
-
}
|
|
50972
|
-
else {
|
|
50973
|
-
// Sometimes do a longer pause (agent switch or random)
|
|
50974
|
-
let didLongPause = false;
|
|
50975
|
-
if (delays.longPauseChance &&
|
|
50976
|
-
Math.random() < delays.longPauseChance &&
|
|
50977
|
-
i > 0 &&
|
|
50978
|
-
originalMessages[i].sender !== originalMessages[i - 1].sender) {
|
|
50979
|
-
await forTime(getDelay(delays.longPauseDuration, 2000));
|
|
50980
|
-
didLongPause = true;
|
|
50981
|
-
if (isCancelled)
|
|
50982
|
-
return;
|
|
50983
|
-
}
|
|
50984
|
-
// Otherwise normal thinking delay
|
|
50985
|
-
if (!didLongPause) {
|
|
50986
|
-
await forTime(getDelay(delays.thinkingBetweenMessages, 2000));
|
|
50987
|
-
if (isCancelled)
|
|
50988
|
-
return;
|
|
50989
|
-
}
|
|
50990
|
-
}
|
|
50991
|
-
// Pause check (still between messages)
|
|
50992
|
-
if (pauseRequestedRef.current) {
|
|
50993
|
-
await waitIfPaused(() => isCancelled);
|
|
50994
|
-
if (isCancelled)
|
|
50995
|
-
return;
|
|
50996
|
-
}
|
|
50997
|
-
}
|
|
50998
|
-
// Show incomplete message first (for typing effect)
|
|
50999
|
-
const incompleteMessage = {
|
|
51000
|
-
// channel: 'PROMPTBOOK_CHAT',
|
|
51001
|
-
id: currentMessage.id,
|
|
51002
|
-
createdAt: currentMessage.createdAt,
|
|
51003
|
-
sender: currentMessage.sender,
|
|
51004
|
-
content: '',
|
|
51005
|
-
isComplete: false,
|
|
51006
|
-
expectedAnswer: currentMessage.expectedAnswer,
|
|
51007
|
-
isVoiceCall: currentMessage.isVoiceCall,
|
|
51008
|
-
};
|
|
51009
|
-
setDisplayedMessages((prev) => [...prev, incompleteMessage]);
|
|
51010
|
-
// Split message content into words
|
|
51011
|
-
const words = currentMessage.content.split(' ');
|
|
51012
|
-
let currentContent = '';
|
|
51013
|
-
// Type each word with delay (randomized)
|
|
51014
|
-
for (let wordIndex = 0; wordIndex < words.length; wordIndex++) {
|
|
51015
|
-
if (isCancelled)
|
|
51016
|
-
return;
|
|
51017
|
-
const word = words[wordIndex];
|
|
51018
|
-
currentContent += (wordIndex > 0 ? ' ' : '') + word;
|
|
51019
|
-
// Update the message with current content
|
|
51020
|
-
const updatingMessage = {
|
|
51021
|
-
// channel: 'PROMPTBOOK_CHAT',
|
|
51022
|
-
id: currentMessage.id,
|
|
51023
|
-
createdAt: currentMessage.createdAt,
|
|
51024
|
-
sender: currentMessage.sender,
|
|
51025
|
-
content: currentContent,
|
|
51026
|
-
isComplete: false,
|
|
51027
|
-
expectedAnswer: currentMessage.expectedAnswer,
|
|
51028
|
-
isVoiceCall: currentMessage.isVoiceCall,
|
|
51029
|
-
};
|
|
51030
|
-
setDisplayedMessages((prev) => {
|
|
51031
|
-
const newMessages = [...prev];
|
|
51032
|
-
newMessages[newMessages.length - 1] = updatingMessage;
|
|
51033
|
-
return newMessages;
|
|
51034
|
-
});
|
|
51035
|
-
// Wait after word with extra delay (randomized)
|
|
51036
|
-
await forTime(getDelay(delays.waitAfterWord, 100) + getDelay(delays.extraWordDelay, 50));
|
|
51037
|
-
if (isCancelled)
|
|
51038
|
-
return;
|
|
51039
|
-
}
|
|
51040
|
-
// Mark message as complete
|
|
51041
|
-
const completeMessage = {
|
|
51042
|
-
// channel: 'PROMPTBOOK_CHAT',
|
|
51043
|
-
id: currentMessage.id,
|
|
51044
|
-
createdAt: currentMessage.createdAt,
|
|
51045
|
-
sender: currentMessage.sender,
|
|
51046
|
-
content: currentMessage.content,
|
|
51047
|
-
isComplete: true,
|
|
51048
|
-
expectedAnswer: currentMessage.expectedAnswer,
|
|
51049
|
-
isVoiceCall: currentMessage.isVoiceCall,
|
|
51050
|
-
};
|
|
51051
|
-
setDisplayedMessages((prev) => {
|
|
51052
|
-
const newMessages = [...prev];
|
|
51053
|
-
newMessages[newMessages.length - 1] = completeMessage;
|
|
51054
|
-
return newMessages;
|
|
51055
|
-
});
|
|
51056
|
-
if (!normalizedMessageOffsetsMs) {
|
|
51057
|
-
// Small pause after completing the message
|
|
51058
|
-
await forTime(200);
|
|
51059
|
-
if (isCancelled)
|
|
51060
|
-
return;
|
|
51061
|
-
}
|
|
51062
|
-
// Transition PAUSING -> PAUSED (after finishing current message)
|
|
51063
|
-
if (pauseRequestedRef.current && playbackState === 'PAUSING') ;
|
|
51064
|
-
}
|
|
51065
|
-
setIsSimulationComplete(true);
|
|
51066
|
-
(_b = onSimulationCompleteRef.current) === null || _b === void 0 ? void 0 : _b.call(onSimulationCompleteRef);
|
|
51067
|
-
};
|
|
51068
|
-
simulateChat().catch((error) => {
|
|
52779
|
+
simulateMockedChatPlayback({
|
|
52780
|
+
delays,
|
|
52781
|
+
originalMessages,
|
|
52782
|
+
normalizedMessageOffsetsMs,
|
|
52783
|
+
pauseRequestedRef,
|
|
52784
|
+
waitIfPaused,
|
|
52785
|
+
setDisplayedMessages,
|
|
52786
|
+
setLocalAppendedMessages,
|
|
52787
|
+
setIsSimulationComplete,
|
|
52788
|
+
onSimulationCompleteRef,
|
|
52789
|
+
isCancelledRef: () => isCancelled,
|
|
52790
|
+
}).catch((error) => {
|
|
51069
52791
|
var _a;
|
|
51070
52792
|
if (!isCancelled) {
|
|
51071
52793
|
console.error('Error in MockedChat simulation:', error);
|
|
@@ -51085,7 +52807,6 @@ function MockedChat(props) {
|
|
|
51085
52807
|
delays.thinkingBetweenMessages,
|
|
51086
52808
|
delays.waitAfterWord,
|
|
51087
52809
|
delays.extraWordDelay,
|
|
51088
|
-
resolveOffsetDelayBeforeMessage,
|
|
51089
52810
|
resetNonce,
|
|
51090
52811
|
]);
|
|
51091
52812
|
const renderedMessages = useMemo(() => [...displayedMessages, ...localAppendedMessages], [displayedMessages, localAppendedMessages]);
|
|
@@ -51133,103 +52854,273 @@ function MockedChat(props) {
|
|
|
51133
52854
|
}
|
|
51134
52855
|
|
|
51135
52856
|
/**
|
|
51136
|
-
*
|
|
52857
|
+
* Default label used when the agent name is unavailable.
|
|
51137
52858
|
*
|
|
51138
|
-
* @
|
|
52859
|
+
* @private function of ChatToolCallModal
|
|
52860
|
+
*/
|
|
52861
|
+
const DEFAULT_AGENT_LABEL = 'Agent';
|
|
52862
|
+
/**
|
|
52863
|
+
* Default label used when the teammate name is unavailable.
|
|
51139
52864
|
*
|
|
51140
52865
|
* @private function of ChatToolCallModal
|
|
51141
52866
|
*/
|
|
51142
|
-
|
|
51143
|
-
|
|
51144
|
-
|
|
51145
|
-
|
|
51146
|
-
|
|
51147
|
-
|
|
51148
|
-
|
|
51149
|
-
|
|
51150
|
-
|
|
51151
|
-
|
|
51152
|
-
|
|
51153
|
-
|
|
52867
|
+
const DEFAULT_TEAMMATE_LABEL = 'Teammate';
|
|
52868
|
+
/**
|
|
52869
|
+
* Default header color used when the agent does not define one.
|
|
52870
|
+
*
|
|
52871
|
+
* @private function of ChatToolCallModal
|
|
52872
|
+
*/
|
|
52873
|
+
const DEFAULT_AGENT_HEADER_COLOR = '#64748b';
|
|
52874
|
+
/**
|
|
52875
|
+
* Default teammate accent color used across the TEAM modal.
|
|
52876
|
+
*
|
|
52877
|
+
* @private function of ChatToolCallModal
|
|
52878
|
+
*/
|
|
52879
|
+
const DEFAULT_TEAMMATE_COLOR = '#0ea5e9';
|
|
52880
|
+
/**
|
|
52881
|
+
* Returns whether one TEAM conversation entry has renderable content.
|
|
52882
|
+
*
|
|
52883
|
+
* @private function of ChatToolCallModal
|
|
52884
|
+
*/
|
|
52885
|
+
function hasTeamConversationContent(entry) {
|
|
52886
|
+
return Boolean(entry && entry.content);
|
|
52887
|
+
}
|
|
52888
|
+
/**
|
|
52889
|
+
* Resolves which side of the TEAM conversation one entry belongs to.
|
|
52890
|
+
*
|
|
52891
|
+
* @private function of ChatToolCallModal
|
|
52892
|
+
*/
|
|
52893
|
+
function resolveTeamConversationSender(entry) {
|
|
52894
|
+
return entry.sender === 'TEAMMATE' || entry.role === 'TEAMMATE' ? 'TEAMMATE' : 'AGENT';
|
|
52895
|
+
}
|
|
52896
|
+
/**
|
|
52897
|
+
* Creates one conversation message for the TEAM preview chat.
|
|
52898
|
+
*
|
|
52899
|
+
* @private function of ChatToolCallModal
|
|
52900
|
+
*/
|
|
52901
|
+
function createTeamConversationMessage(entry, index, baseTime) {
|
|
52902
|
+
return {
|
|
51154
52903
|
id: `team-${index}`,
|
|
51155
52904
|
createdAt: new Date(baseTime + index * 1000).toISOString(),
|
|
51156
|
-
sender: entry
|
|
51157
|
-
content: entry.content
|
|
52905
|
+
sender: resolveTeamConversationSender(entry),
|
|
52906
|
+
content: entry.content,
|
|
51158
52907
|
isComplete: true,
|
|
51159
|
-
}
|
|
51160
|
-
|
|
51161
|
-
|
|
51162
|
-
|
|
51163
|
-
|
|
51164
|
-
|
|
51165
|
-
|
|
51166
|
-
|
|
51167
|
-
|
|
51168
|
-
|
|
51169
|
-
|
|
51170
|
-
|
|
51171
|
-
|
|
51172
|
-
|
|
51173
|
-
|
|
51174
|
-
|
|
51175
|
-
|
|
51176
|
-
|
|
51177
|
-
|
|
51178
|
-
|
|
52908
|
+
};
|
|
52909
|
+
}
|
|
52910
|
+
/**
|
|
52911
|
+
* Appends the request/response fallback pair when no structured conversation is available.
|
|
52912
|
+
*
|
|
52913
|
+
* @private function of ChatToolCallModal
|
|
52914
|
+
*/
|
|
52915
|
+
function appendFallbackConversationMessages(messages, teamResult, baseTime) {
|
|
52916
|
+
if (messages.length > 0) {
|
|
52917
|
+
return;
|
|
52918
|
+
}
|
|
52919
|
+
if (teamResult.request) {
|
|
52920
|
+
messages.push({
|
|
52921
|
+
id: 'team-request',
|
|
52922
|
+
createdAt: new Date(baseTime).toISOString(),
|
|
52923
|
+
sender: 'AGENT',
|
|
52924
|
+
content: teamResult.request,
|
|
52925
|
+
isComplete: true,
|
|
52926
|
+
});
|
|
52927
|
+
}
|
|
52928
|
+
if (teamResult.response) {
|
|
52929
|
+
messages.push({
|
|
52930
|
+
id: 'team-response',
|
|
52931
|
+
createdAt: new Date(baseTime + 1000).toISOString(),
|
|
52932
|
+
sender: 'TEAMMATE',
|
|
52933
|
+
content: teamResult.response,
|
|
52934
|
+
isComplete: true,
|
|
52935
|
+
});
|
|
51179
52936
|
}
|
|
51180
|
-
|
|
51181
|
-
|
|
52937
|
+
}
|
|
52938
|
+
/**
|
|
52939
|
+
* Builds the conversation transcript shown in the TEAM modal.
|
|
52940
|
+
*
|
|
52941
|
+
* @private function of ChatToolCallModal
|
|
52942
|
+
*/
|
|
52943
|
+
function createTeamConversationMessages(teamResult, baseTime) {
|
|
52944
|
+
const messages = (teamResult.conversation || [])
|
|
52945
|
+
.filter(hasTeamConversationContent)
|
|
52946
|
+
.map((entry, index) => createTeamConversationMessage(entry, index, baseTime));
|
|
52947
|
+
appendFallbackConversationMessages(messages, teamResult, baseTime);
|
|
52948
|
+
return messages;
|
|
52949
|
+
}
|
|
52950
|
+
/**
|
|
52951
|
+
* Finds the first conversation name used by a specific sender.
|
|
52952
|
+
*
|
|
52953
|
+
* @private function of ChatToolCallModal
|
|
52954
|
+
*/
|
|
52955
|
+
function resolveConversationName(teamResult, sender, fallbackName) {
|
|
52956
|
+
var _a, _b;
|
|
52957
|
+
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);
|
|
52958
|
+
}
|
|
52959
|
+
/**
|
|
52960
|
+
* Resolves the public teammate link when it points to a real agent.
|
|
52961
|
+
*
|
|
52962
|
+
* @private function of ChatToolCallModal
|
|
52963
|
+
*/
|
|
52964
|
+
function resolveTeammateLink(teammateUrl) {
|
|
52965
|
+
return teammateUrl && teammateUrl !== 'VOID' && !isPseudoAgentUrl(teammateUrl) ? teammateUrl : undefined;
|
|
52966
|
+
}
|
|
52967
|
+
/**
|
|
52968
|
+
* Builds the TEAM modal header data and chat participants.
|
|
52969
|
+
*
|
|
52970
|
+
* @private function of ChatToolCallModal
|
|
52971
|
+
*/
|
|
52972
|
+
function createTeamConversationViewModel({ agentParticipant, teamProfiles, teamResult, }) {
|
|
52973
|
+
var _a, _b;
|
|
52974
|
+
const teammateUrl = ((_a = teamResult.teammate) === null || _a === void 0 ? void 0 : _a.url) || '';
|
|
51182
52975
|
const teammateProfile = teammateUrl ? teamProfiles[teammateUrl] : undefined;
|
|
51183
52976
|
const teammateFallbackProfile = teammateUrl
|
|
51184
52977
|
? resolveAgentProfileFallback({
|
|
51185
52978
|
url: teammateUrl,
|
|
51186
|
-
label: (
|
|
52979
|
+
label: (_b = teamResult.teammate) === null || _b === void 0 ? void 0 : _b.label,
|
|
51187
52980
|
})
|
|
51188
|
-
: { label:
|
|
52981
|
+
: { label: DEFAULT_TEAMMATE_LABEL, imageUrl: null };
|
|
52982
|
+
const agentName = resolveConversationName(teamResult, 'AGENT', DEFAULT_AGENT_LABEL);
|
|
52983
|
+
const teammateConversationName = resolveConversationName(teamResult, 'TEAMMATE', '');
|
|
51189
52984
|
const resolvedAgentLabel = resolvePreferredAgentLabel([agentParticipant === null || agentParticipant === void 0 ? void 0 : agentParticipant.fullname, agentName], agentName);
|
|
51190
|
-
const resolvedAgentAvatar = (agentParticipant === null || agentParticipant === void 0 ? void 0 : agentParticipant.avatarSrc) || null;
|
|
51191
|
-
const resolvedAgentAvatarDefinition = agentParticipant === null || agentParticipant === void 0 ? void 0 : agentParticipant.avatarDefinition;
|
|
51192
|
-
const resolvedAgentAvatarVisualId = agentParticipant === null || agentParticipant === void 0 ? void 0 : agentParticipant.avatarVisualId;
|
|
51193
|
-
const resolvedAgentHeaderColor = (agentParticipant === null || agentParticipant === void 0 ? void 0 : agentParticipant.color)
|
|
51194
|
-
? Color.fromSafe(agentParticipant.color).toHex()
|
|
51195
|
-
: '#64748b';
|
|
51196
52985
|
const resolvedTeammateLabel = resolvePreferredAgentLabel([teammateConversationName, teammateProfile === null || teammateProfile === void 0 ? void 0 : teammateProfile.label, teammateFallbackProfile.label], teammateFallbackProfile.label);
|
|
51197
|
-
const
|
|
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
|
-
|
|
51223
|
-
|
|
51224
|
-
|
|
51225
|
-
|
|
51226
|
-
|
|
51227
|
-
|
|
51228
|
-
|
|
51229
|
-
|
|
51230
|
-
|
|
51231
|
-
|
|
51232
|
-
|
|
52986
|
+
const agentHeader = {
|
|
52987
|
+
label: resolvedAgentLabel,
|
|
52988
|
+
avatarSrc: (agentParticipant === null || agentParticipant === void 0 ? void 0 : agentParticipant.avatarSrc) || null,
|
|
52989
|
+
avatarDefinition: agentParticipant === null || agentParticipant === void 0 ? void 0 : agentParticipant.avatarDefinition,
|
|
52990
|
+
avatarVisualId: agentParticipant === null || agentParticipant === void 0 ? void 0 : agentParticipant.avatarVisualId,
|
|
52991
|
+
fallbackColor: (agentParticipant === null || agentParticipant === void 0 ? void 0 : agentParticipant.color)
|
|
52992
|
+
? Color.fromSafe(agentParticipant.color).toHex()
|
|
52993
|
+
: DEFAULT_AGENT_HEADER_COLOR,
|
|
52994
|
+
};
|
|
52995
|
+
const teammateHeader = {
|
|
52996
|
+
label: resolvedTeammateLabel,
|
|
52997
|
+
avatarSrc: (teammateProfile === null || teammateProfile === void 0 ? void 0 : teammateProfile.imageUrl) || teammateFallbackProfile.imageUrl || null,
|
|
52998
|
+
fallbackColor: DEFAULT_TEAMMATE_COLOR,
|
|
52999
|
+
href: resolveTeammateLink(teammateUrl),
|
|
53000
|
+
};
|
|
53001
|
+
return {
|
|
53002
|
+
agentHeader,
|
|
53003
|
+
teammateHeader,
|
|
53004
|
+
participants: [
|
|
53005
|
+
{
|
|
53006
|
+
name: 'AGENT',
|
|
53007
|
+
fullname: agentHeader.label,
|
|
53008
|
+
color: (agentParticipant === null || agentParticipant === void 0 ? void 0 : agentParticipant.color) || DEFAULT_AGENT_HEADER_COLOR,
|
|
53009
|
+
avatarSrc: agentHeader.avatarSrc || undefined,
|
|
53010
|
+
avatarDefinition: agentHeader.avatarDefinition,
|
|
53011
|
+
avatarVisualId: agentHeader.avatarVisualId,
|
|
53012
|
+
isMe: true,
|
|
53013
|
+
},
|
|
53014
|
+
{
|
|
53015
|
+
name: 'TEAMMATE',
|
|
53016
|
+
fullname: teammateHeader.label,
|
|
53017
|
+
color: DEFAULT_TEAMMATE_COLOR,
|
|
53018
|
+
avatarSrc: teammateHeader.avatarSrc || undefined,
|
|
53019
|
+
},
|
|
53020
|
+
],
|
|
53021
|
+
};
|
|
53022
|
+
}
|
|
53023
|
+
/**
|
|
53024
|
+
* Renders the TEAM conversation header profiles.
|
|
53025
|
+
*
|
|
53026
|
+
* @private component of ChatToolCallModal
|
|
53027
|
+
*/
|
|
53028
|
+
function TeamConversationHeader(props) {
|
|
53029
|
+
const { agentHeader, teammateHeader } = props;
|
|
53030
|
+
return (jsx("div", { className: classNames(styles$5.searchModalHeader, styles$5.teamModalHeader), children: jsxs("div", { className: styles$5.teamHeaderParticipants, children: [jsx(TeamHeaderProfile, { label: agentHeader.label, avatarSrc: agentHeader.avatarSrc, avatarDefinition: agentHeader.avatarDefinition, avatarVisualId: agentHeader.avatarVisualId, fallbackColor: agentHeader.fallbackColor }), jsx("span", { className: styles$5.teamHeaderDivider, children: "talking with" }), jsx(TeamHeaderProfile, { label: teammateHeader.label, avatarSrc: teammateHeader.avatarSrc, fallbackColor: teammateHeader.fallbackColor, href: teammateHeader.href })] }) }));
|
|
53031
|
+
}
|
|
53032
|
+
/**
|
|
53033
|
+
* Renders the TEAM conversation transcript or the empty-state fallback.
|
|
53034
|
+
*
|
|
53035
|
+
* @private component of ChatToolCallModal
|
|
53036
|
+
*/
|
|
53037
|
+
function TeamConversationSection(props) {
|
|
53038
|
+
const { agentLabel, teammateLabel, messages, participants } = props;
|
|
53039
|
+
if (messages.length === 0) {
|
|
53040
|
+
return jsx("div", { className: styles$5.noResults, children: "No teammate conversation available." });
|
|
53041
|
+
}
|
|
53042
|
+
return (jsx("div", { className: styles$5.teamChatContainer, children: jsx(MockedChat, { title: `Chat between ${agentLabel} and ${teammateLabel}`, messages: messages, participants: participants, isResettable: false, isPausable: false, isSaveButtonEnabled: false, isCopyButtonEnabled: false, layout: "STANDALONE", delayConfig: {
|
|
53043
|
+
// 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}`
|
|
53044
|
+
...FAST_FLOW,
|
|
53045
|
+
beforeFirstMessage: 0,
|
|
53046
|
+
showIntermediateMessages: messages.length,
|
|
53047
|
+
}, visualMode: "BUBBLE_MODE" }) }));
|
|
53048
|
+
}
|
|
53049
|
+
/**
|
|
53050
|
+
* Renders action chips for teammate-executed tool calls.
|
|
53051
|
+
*
|
|
53052
|
+
* @private component of ChatToolCallModal
|
|
53053
|
+
*/
|
|
53054
|
+
function TeamToolCallActionsGroup(props) {
|
|
53055
|
+
const { onSelectTeamToolCall, teamToolCalls, toolTitles } = props;
|
|
53056
|
+
if (teamToolCalls.length === 0) {
|
|
53057
|
+
return null;
|
|
53058
|
+
}
|
|
53059
|
+
return (jsxs("div", { className: styles$5.teamToolCallGroup, children: [jsx("div", { className: styles$5.teamToolCallHeading, children: "Actions" }), jsx("div", { className: styles$5.teamToolCallChips, children: teamToolCalls.map((toolCallEntry, index) => {
|
|
53060
|
+
const chipletInfo = getToolCallChipletInfo(toolCallEntry.toolCall, undefined, toolTitles);
|
|
53061
|
+
const chipletText = buildToolCallChipText(chipletInfo);
|
|
53062
|
+
return (jsxs("button", { className: styles$5.completedToolCall, onClick: () => {
|
|
53063
|
+
onSelectTeamToolCall(toolCallEntry);
|
|
53064
|
+
}, children: [jsx("span", { children: chipletText }), jsxs("span", { className: styles$5.toolCallOrigin, children: ["by ", toolCallEntry.origin.label] })] }, `team-tool-${index}`));
|
|
53065
|
+
}) })] }));
|
|
53066
|
+
}
|
|
53067
|
+
/**
|
|
53068
|
+
* Renders citations surfaced from the teammate exchange.
|
|
53069
|
+
*
|
|
53070
|
+
* @private component of ChatToolCallModal
|
|
53071
|
+
*/
|
|
53072
|
+
function TeamToolCallSourcesGroup(props) {
|
|
53073
|
+
const { teamCitations } = props;
|
|
53074
|
+
if (teamCitations.length === 0) {
|
|
53075
|
+
return null;
|
|
53076
|
+
}
|
|
53077
|
+
return (jsxs("div", { className: styles$5.teamToolCallGroup, children: [jsx("div", { className: styles$5.teamToolCallHeading, children: "Sources" }), jsx("div", { className: styles$5.teamToolCallChips, children: teamCitations.map((citation, index) => (jsx(SourceChip, { citation: citation, suffix: `by ${citation.origin.label}` }, `team-source-${citation.source}-${index}`))) })] }));
|
|
53078
|
+
}
|
|
53079
|
+
/**
|
|
53080
|
+
* Renders the nested action details for the selected teammate tool call.
|
|
53081
|
+
*
|
|
53082
|
+
* @private component of ChatToolCallModal
|
|
53083
|
+
*/
|
|
53084
|
+
function TeamToolCallDetailsPanel(props) {
|
|
53085
|
+
const { agentParticipant, buttonColor, onClearSelectedTeamToolCall, selectedTeamToolCall, toolTitles } = props;
|
|
53086
|
+
if (!selectedTeamToolCall) {
|
|
53087
|
+
return null;
|
|
53088
|
+
}
|
|
53089
|
+
return (jsxs("div", { className: styles$5.teamToolCallDetails, children: [jsxs("div", { className: styles$5.teamToolCallDetailsHeader, children: [jsxs("span", { className: styles$5.teamToolCallDetailsTitle, children: ["Action details", jsxs("span", { className: styles$5.toolCallOrigin, children: ["by ", selectedTeamToolCall.origin.label] })] }), jsx("button", { type: "button", className: styles$5.teamToolCallDetailsClear, onClick: onClearSelectedTeamToolCall, children: "Clear" })] }), renderToolCallDetails({
|
|
53090
|
+
toolCall: selectedTeamToolCall.toolCall,
|
|
53091
|
+
toolTitles,
|
|
53092
|
+
agentParticipant,
|
|
53093
|
+
buttonColor,
|
|
53094
|
+
})] }));
|
|
53095
|
+
}
|
|
53096
|
+
/**
|
|
53097
|
+
* Renders the nested tool-call and citation summary below the TEAM transcript.
|
|
53098
|
+
*
|
|
53099
|
+
* @private component of ChatToolCallModal
|
|
53100
|
+
*/
|
|
53101
|
+
function TeamToolCallSummarySection(props) {
|
|
53102
|
+
const { agentParticipant, buttonColor, onClearSelectedTeamToolCall, onSelectTeamToolCall, selectedTeamToolCall, teamToolCallSummary, toolTitles, } = props;
|
|
53103
|
+
const hasSummaryContent = teamToolCallSummary.toolCalls.length > 0 || teamToolCallSummary.citations.length > 0;
|
|
53104
|
+
if (!hasSummaryContent) {
|
|
53105
|
+
return null;
|
|
53106
|
+
}
|
|
53107
|
+
return (jsxs("div", { className: styles$5.teamToolCallSection, children: [jsx(TeamToolCallActionsGroup, { teamToolCalls: teamToolCallSummary.toolCalls, onSelectTeamToolCall: onSelectTeamToolCall, toolTitles: toolTitles }), jsx(TeamToolCallSourcesGroup, { teamCitations: teamToolCallSummary.citations }), jsx(TeamToolCallDetailsPanel, { selectedTeamToolCall: selectedTeamToolCall, onClearSelectedTeamToolCall: onClearSelectedTeamToolCall, toolTitles: toolTitles, agentParticipant: agentParticipant, buttonColor: buttonColor })] }));
|
|
53108
|
+
}
|
|
53109
|
+
/**
|
|
53110
|
+
* Renders TEAM conversation details, nested actions, and citations.
|
|
53111
|
+
*
|
|
53112
|
+
* @private function of ChatToolCallModal
|
|
53113
|
+
*/
|
|
53114
|
+
function TeamToolCallModalContent(options) {
|
|
53115
|
+
const { teamResult, toolCallDate, teamToolCallSummary, selectedTeamToolCall, onSelectTeamToolCall, onClearSelectedTeamToolCall, teamProfiles, toolTitles, agentParticipant, buttonColor, } = options;
|
|
53116
|
+
const baseTime = toolCallDate ? toolCallDate.getTime() : Date.now();
|
|
53117
|
+
const messages = createTeamConversationMessages(teamResult, baseTime);
|
|
53118
|
+
const { agentHeader, teammateHeader, participants } = createTeamConversationViewModel({
|
|
53119
|
+
teamResult,
|
|
53120
|
+
teamProfiles,
|
|
53121
|
+
agentParticipant,
|
|
53122
|
+
});
|
|
53123
|
+
return (jsxs(Fragment, { children: [jsx(TeamConversationHeader, { agentHeader: agentHeader, teammateHeader: teammateHeader }), jsxs("div", { className: styles$5.searchModalContent, children: [jsx(TeamConversationSection, { agentLabel: agentHeader.label, teammateLabel: teammateHeader.label, messages: messages, participants: participants }), jsx(TeamToolCallSummarySection, { teamToolCallSummary: teamToolCallSummary, selectedTeamToolCall: selectedTeamToolCall, onSelectTeamToolCall: onSelectTeamToolCall, onClearSelectedTeamToolCall: onClearSelectedTeamToolCall, toolTitles: toolTitles, agentParticipant: agentParticipant, buttonColor: buttonColor })] })] }));
|
|
51233
53124
|
}
|
|
51234
53125
|
|
|
51235
53126
|
/**
|
|
@@ -51681,8 +53572,314 @@ function useChatActionsOverlap(config) {
|
|
|
51681
53572
|
|
|
51682
53573
|
/**
|
|
51683
53574
|
* Debounce window for synchronizing `isAutoScrolling` with the latest scroll position.
|
|
53575
|
+
*
|
|
53576
|
+
* @private function of `useChatAutoScroll`
|
|
51684
53577
|
*/
|
|
51685
53578
|
const SCROLL_EVENT_DEBOUNCE_MS = 50;
|
|
53579
|
+
/**
|
|
53580
|
+
* Faster follow-up used while the latest assistant message is still streaming.
|
|
53581
|
+
*
|
|
53582
|
+
* @private function of `useChatAutoScroll`
|
|
53583
|
+
*/
|
|
53584
|
+
const STREAMING_SCROLL_CHECK_DELAY_MS = 10;
|
|
53585
|
+
/**
|
|
53586
|
+
* Viewport width treated as mobile for chat scrolling behavior.
|
|
53587
|
+
*
|
|
53588
|
+
* @private function of `useChatAutoScroll`
|
|
53589
|
+
*/
|
|
53590
|
+
const MOBILE_BREAKPOINT_PX = 768;
|
|
53591
|
+
/**
|
|
53592
|
+
* Mobile-device user agent matcher used by the chat viewport detection.
|
|
53593
|
+
*
|
|
53594
|
+
* @private function of `useChatAutoScroll`
|
|
53595
|
+
*/
|
|
53596
|
+
const MOBILE_DEVICE_USER_AGENT_PATTERN = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
|
|
53597
|
+
/**
|
|
53598
|
+
* Detects whether the current viewport should use the mobile scrolling path.
|
|
53599
|
+
*
|
|
53600
|
+
* @returns `true` when the viewport or device matches the mobile heuristics.
|
|
53601
|
+
*
|
|
53602
|
+
* @private function of `useChatAutoScroll`
|
|
53603
|
+
*/
|
|
53604
|
+
function isMobileChatViewport() {
|
|
53605
|
+
return window.innerWidth <= MOBILE_BREAKPOINT_PX || MOBILE_DEVICE_USER_AGENT_PATTERN.test(navigator.userAgent);
|
|
53606
|
+
}
|
|
53607
|
+
/**
|
|
53608
|
+
* Tracks whether the chat should currently use mobile scrolling behavior.
|
|
53609
|
+
*
|
|
53610
|
+
* @private function of `useChatAutoScroll`
|
|
53611
|
+
*/
|
|
53612
|
+
function useMobileChatViewport() {
|
|
53613
|
+
const [isMobile, setIsMobile] = useState(false);
|
|
53614
|
+
useEffect(() => {
|
|
53615
|
+
const handleViewportChange = () => {
|
|
53616
|
+
setIsMobile(isMobileChatViewport());
|
|
53617
|
+
};
|
|
53618
|
+
handleViewportChange();
|
|
53619
|
+
window.addEventListener('resize', handleViewportChange);
|
|
53620
|
+
return () => window.removeEventListener('resize', handleViewportChange);
|
|
53621
|
+
}, []);
|
|
53622
|
+
return isMobile;
|
|
53623
|
+
}
|
|
53624
|
+
/**
|
|
53625
|
+
* Checks whether the messages container is effectively scrolled to the bottom.
|
|
53626
|
+
*
|
|
53627
|
+
* @param element - Scrollable chat messages container.
|
|
53628
|
+
* @param bottomThreshold - Distance from the bottom still considered "at bottom".
|
|
53629
|
+
* @returns `true` when the container is within the threshold from the bottom.
|
|
53630
|
+
*
|
|
53631
|
+
* @private function of `useChatAutoScroll`
|
|
53632
|
+
*/
|
|
53633
|
+
function isChatScrolledToBottom(element, bottomThreshold) {
|
|
53634
|
+
const { scrollTop, scrollHeight, clientHeight } = element;
|
|
53635
|
+
return scrollTop + clientHeight >= scrollHeight - bottomThreshold;
|
|
53636
|
+
}
|
|
53637
|
+
/**
|
|
53638
|
+
* Clears a scheduled timeout when one is currently active.
|
|
53639
|
+
*
|
|
53640
|
+
* @private function of `useChatAutoScroll`
|
|
53641
|
+
*/
|
|
53642
|
+
function clearScheduledTimeout(timeoutRef) {
|
|
53643
|
+
if (timeoutRef.current) {
|
|
53644
|
+
clearTimeout(timeoutRef.current);
|
|
53645
|
+
timeoutRef.current = null;
|
|
53646
|
+
}
|
|
53647
|
+
}
|
|
53648
|
+
/**
|
|
53649
|
+
* Performs the concrete scroll-to-bottom operation for the current platform.
|
|
53650
|
+
*
|
|
53651
|
+
* @private function of `useChatAutoScroll`
|
|
53652
|
+
*/
|
|
53653
|
+
function performScrollToBottom({ behavior, chatMessagesElement, isMobile, smoothScroll, }) {
|
|
53654
|
+
if (isMobile) {
|
|
53655
|
+
chatMessagesElement.scrollTo({
|
|
53656
|
+
top: chatMessagesElement.scrollHeight,
|
|
53657
|
+
behavior: smoothScroll ? behavior : 'auto',
|
|
53658
|
+
});
|
|
53659
|
+
return;
|
|
53660
|
+
}
|
|
53661
|
+
if (smoothScroll && behavior === 'smooth') {
|
|
53662
|
+
chatMessagesElement.style.scrollBehavior = 'smooth';
|
|
53663
|
+
chatMessagesElement.scrollTop = chatMessagesElement.scrollHeight;
|
|
53664
|
+
chatMessagesElement.style.scrollBehavior = 'auto';
|
|
53665
|
+
return;
|
|
53666
|
+
}
|
|
53667
|
+
chatMessagesElement.scrollTop = chatMessagesElement.scrollHeight;
|
|
53668
|
+
}
|
|
53669
|
+
/**
|
|
53670
|
+
* Detects whether the user currently has a non-collapsed selection inside the chat container.
|
|
53671
|
+
*
|
|
53672
|
+
* @private function of `useChatAutoScroll`
|
|
53673
|
+
*/
|
|
53674
|
+
function hasExpandedSelectionInChat(chatMessagesElement) {
|
|
53675
|
+
const selection = window.getSelection();
|
|
53676
|
+
if (!selection || selection.rangeCount === 0 || selection.isCollapsed) {
|
|
53677
|
+
return false;
|
|
53678
|
+
}
|
|
53679
|
+
const range = selection.getRangeAt(0);
|
|
53680
|
+
return chatMessagesElement.contains(range.startContainer) || chatMessagesElement.contains(range.endContainer);
|
|
53681
|
+
}
|
|
53682
|
+
/**
|
|
53683
|
+
* Computes the scroll metrics needed to decide whether new content should trigger auto-scroll.
|
|
53684
|
+
*
|
|
53685
|
+
* @private function of `useChatAutoScroll`
|
|
53686
|
+
*/
|
|
53687
|
+
function getMessagesChangeMetrics({ bottomThreshold, chatMessagesElement, previousScrollHeight, }) {
|
|
53688
|
+
const currentScrollHeight = chatMessagesElement.scrollHeight;
|
|
53689
|
+
return {
|
|
53690
|
+
currentScrollHeight,
|
|
53691
|
+
hasNewContent: currentScrollHeight > previousScrollHeight,
|
|
53692
|
+
wasAtBottomBeforeNewContent: chatMessagesElement.scrollTop + chatMessagesElement.clientHeight >= previousScrollHeight - bottomThreshold,
|
|
53693
|
+
};
|
|
53694
|
+
}
|
|
53695
|
+
/**
|
|
53696
|
+
* Evaluates whether new content should keep the chat pinned to the latest message.
|
|
53697
|
+
*
|
|
53698
|
+
* @private function of `useChatAutoScroll`
|
|
53699
|
+
*/
|
|
53700
|
+
function shouldAutoScrollForMessagesChange({ hasManualScroll, hasNewContent, hasSelectionInChat, isAutoScrolling, wasAtBottomBeforeNewContent, }) {
|
|
53701
|
+
return hasNewContent && isAutoScrolling && wasAtBottomBeforeNewContent && !hasSelectionInChat && !hasManualScroll;
|
|
53702
|
+
}
|
|
53703
|
+
/**
|
|
53704
|
+
* Evaluates whether the latest DOM update should schedule a follow-up scroll.
|
|
53705
|
+
*
|
|
53706
|
+
* @private function of `useChatAutoScroll`
|
|
53707
|
+
*/
|
|
53708
|
+
function evaluateMessagesChangeAutoScroll({ bottomThreshold, chatMessagesElement, hasManualScroll, isAutoScrolling, previousScrollHeight, }) {
|
|
53709
|
+
const metrics = getMessagesChangeMetrics({
|
|
53710
|
+
bottomThreshold,
|
|
53711
|
+
chatMessagesElement,
|
|
53712
|
+
previousScrollHeight,
|
|
53713
|
+
});
|
|
53714
|
+
return {
|
|
53715
|
+
currentScrollHeight: metrics.currentScrollHeight,
|
|
53716
|
+
shouldScheduleScroll: shouldAutoScrollForMessagesChange({
|
|
53717
|
+
hasManualScroll,
|
|
53718
|
+
hasNewContent: metrics.hasNewContent,
|
|
53719
|
+
hasSelectionInChat: hasExpandedSelectionInChat(chatMessagesElement),
|
|
53720
|
+
isAutoScrolling,
|
|
53721
|
+
wasAtBottomBeforeNewContent: metrics.wasAtBottomBeforeNewContent,
|
|
53722
|
+
}),
|
|
53723
|
+
};
|
|
53724
|
+
}
|
|
53725
|
+
/**
|
|
53726
|
+
* Chooses the follow-up delay for the next automatic scroll attempt.
|
|
53727
|
+
*
|
|
53728
|
+
* @private function of `useChatAutoScroll`
|
|
53729
|
+
*/
|
|
53730
|
+
function getMessagesChangeDelay(isStreaming, scrollCheckDelay) {
|
|
53731
|
+
return isStreaming ? STREAMING_SCROLL_CHECK_DELAY_MS : scrollCheckDelay;
|
|
53732
|
+
}
|
|
53733
|
+
/**
|
|
53734
|
+
* Chooses the scroll behavior for the next automatic scroll attempt.
|
|
53735
|
+
*
|
|
53736
|
+
* @private function of `useChatAutoScroll`
|
|
53737
|
+
*/
|
|
53738
|
+
function getMessagesChangeScrollBehavior(isStreaming) {
|
|
53739
|
+
return isStreaming ? 'auto' : 'smooth';
|
|
53740
|
+
}
|
|
53741
|
+
/**
|
|
53742
|
+
* Updates the manual-scroll and auto-scroll flags from the latest direct scroll event.
|
|
53743
|
+
*
|
|
53744
|
+
* @private function of `useChatAutoScroll`
|
|
53745
|
+
*/
|
|
53746
|
+
function updateAutoScrollStateFromScroll({ hasManualScrollRef, isAtBottom, isAutoScrolling, setIsAutoScrolling, }) {
|
|
53747
|
+
hasManualScrollRef.current = !isAtBottom;
|
|
53748
|
+
if (!isAtBottom && isAutoScrolling) {
|
|
53749
|
+
setIsAutoScrolling(false);
|
|
53750
|
+
}
|
|
53751
|
+
}
|
|
53752
|
+
/**
|
|
53753
|
+
* Schedules the debounced reconciliation that keeps `isAutoScrolling` in sync with the viewport.
|
|
53754
|
+
*
|
|
53755
|
+
* @private function of `useChatAutoScroll`
|
|
53756
|
+
*/
|
|
53757
|
+
function scheduleAutoScrollStateSync({ element, scrollTimeoutRef, syncAutoScrollState, }) {
|
|
53758
|
+
clearScheduledTimeout(scrollTimeoutRef);
|
|
53759
|
+
scrollTimeoutRef.current = setTimeout(() => {
|
|
53760
|
+
syncAutoScrollState(element);
|
|
53761
|
+
}, SCROLL_EVENT_DEBOUNCE_MS);
|
|
53762
|
+
}
|
|
53763
|
+
/**
|
|
53764
|
+
* Schedules the delayed scroll that follows a new message or streaming chunk.
|
|
53765
|
+
*
|
|
53766
|
+
* @private function of `useChatAutoScroll`
|
|
53767
|
+
*/
|
|
53768
|
+
function scheduleMessagesChangeAutoScroll({ hasManualScrollRef, isStreaming, messagesChangeTimeoutRef, scrollCheckDelay, scrollToBottom, }) {
|
|
53769
|
+
clearScheduledTimeout(messagesChangeTimeoutRef);
|
|
53770
|
+
messagesChangeTimeoutRef.current = setTimeout(() => {
|
|
53771
|
+
if (hasManualScrollRef.current) {
|
|
53772
|
+
return;
|
|
53773
|
+
}
|
|
53774
|
+
scrollToBottom(getMessagesChangeScrollBehavior(isStreaming));
|
|
53775
|
+
}, getMessagesChangeDelay(isStreaming, scrollCheckDelay));
|
|
53776
|
+
}
|
|
53777
|
+
/**
|
|
53778
|
+
* Initializes the chat container reference and restores the pinned-to-bottom state when needed.
|
|
53779
|
+
*
|
|
53780
|
+
* @private function of `useChatAutoScroll`
|
|
53781
|
+
*/
|
|
53782
|
+
function initializeChatMessagesElement({ element, isAutoScrolling, lastScrollHeightRef, scrollToBottom, }) {
|
|
53783
|
+
lastScrollHeightRef.current = element.scrollHeight;
|
|
53784
|
+
if (!isAutoScrolling) {
|
|
53785
|
+
return;
|
|
53786
|
+
}
|
|
53787
|
+
requestAnimationFrame(() => {
|
|
53788
|
+
scrollToBottom('auto');
|
|
53789
|
+
});
|
|
53790
|
+
}
|
|
53791
|
+
/**
|
|
53792
|
+
* Creates the debounced scroll handler that reacts to direct user scrolling.
|
|
53793
|
+
*
|
|
53794
|
+
* @private function of `useChatAutoScroll`
|
|
53795
|
+
*/
|
|
53796
|
+
function useChatScrollHandler({ checkIfAtBottom, hasManualScrollRef, isAutoScrolling, scrollTimeoutRef, setIsAutoScrolling, }) {
|
|
53797
|
+
const syncAutoScrollState = useCallback((element) => {
|
|
53798
|
+
const isAtBottom = checkIfAtBottom(element);
|
|
53799
|
+
setIsAutoScrolling((currentValue) => (currentValue === isAtBottom ? currentValue : isAtBottom));
|
|
53800
|
+
}, [checkIfAtBottom, setIsAutoScrolling]);
|
|
53801
|
+
return useCallback((event) => {
|
|
53802
|
+
const element = event.currentTarget;
|
|
53803
|
+
const isAtBottom = checkIfAtBottom(element);
|
|
53804
|
+
updateAutoScrollStateFromScroll({
|
|
53805
|
+
hasManualScrollRef,
|
|
53806
|
+
isAtBottom,
|
|
53807
|
+
isAutoScrolling,
|
|
53808
|
+
setIsAutoScrolling,
|
|
53809
|
+
});
|
|
53810
|
+
scheduleAutoScrollStateSync({
|
|
53811
|
+
element,
|
|
53812
|
+
scrollTimeoutRef,
|
|
53813
|
+
syncAutoScrollState,
|
|
53814
|
+
});
|
|
53815
|
+
}, [
|
|
53816
|
+
checkIfAtBottom,
|
|
53817
|
+
hasManualScrollRef,
|
|
53818
|
+
isAutoScrolling,
|
|
53819
|
+
scrollTimeoutRef,
|
|
53820
|
+
setIsAutoScrolling,
|
|
53821
|
+
syncAutoScrollState,
|
|
53822
|
+
]);
|
|
53823
|
+
}
|
|
53824
|
+
/**
|
|
53825
|
+
* Creates the message-change handler that decides whether the chat should follow new content.
|
|
53826
|
+
*
|
|
53827
|
+
* @private function of `useChatAutoScroll`
|
|
53828
|
+
*/
|
|
53829
|
+
function useChatMessagesChangeHandler({ bottomThreshold, chatMessagesRef, hasManualScrollRef, isAutoScrolling, lastScrollHeightRef, messagesChangeTimeoutRef, scrollCheckDelay, scrollToBottom, }) {
|
|
53830
|
+
return useCallback((isStreaming = false) => {
|
|
53831
|
+
const chatMessagesElement = chatMessagesRef.current;
|
|
53832
|
+
if (!chatMessagesElement) {
|
|
53833
|
+
return;
|
|
53834
|
+
}
|
|
53835
|
+
const autoScrollDecision = evaluateMessagesChangeAutoScroll({
|
|
53836
|
+
bottomThreshold,
|
|
53837
|
+
chatMessagesElement,
|
|
53838
|
+
hasManualScroll: hasManualScrollRef.current,
|
|
53839
|
+
isAutoScrolling,
|
|
53840
|
+
previousScrollHeight: lastScrollHeightRef.current,
|
|
53841
|
+
});
|
|
53842
|
+
lastScrollHeightRef.current = autoScrollDecision.currentScrollHeight;
|
|
53843
|
+
if (!autoScrollDecision.shouldScheduleScroll) {
|
|
53844
|
+
return;
|
|
53845
|
+
}
|
|
53846
|
+
scheduleMessagesChangeAutoScroll({
|
|
53847
|
+
hasManualScrollRef,
|
|
53848
|
+
isStreaming,
|
|
53849
|
+
messagesChangeTimeoutRef,
|
|
53850
|
+
scrollCheckDelay,
|
|
53851
|
+
scrollToBottom,
|
|
53852
|
+
});
|
|
53853
|
+
}, [
|
|
53854
|
+
bottomThreshold,
|
|
53855
|
+
chatMessagesRef,
|
|
53856
|
+
hasManualScrollRef,
|
|
53857
|
+
isAutoScrolling,
|
|
53858
|
+
lastScrollHeightRef,
|
|
53859
|
+
messagesChangeTimeoutRef,
|
|
53860
|
+
scrollCheckDelay,
|
|
53861
|
+
scrollToBottom,
|
|
53862
|
+
]);
|
|
53863
|
+
}
|
|
53864
|
+
/**
|
|
53865
|
+
* Creates the ref callback that captures the chat container and initializes its scroll state.
|
|
53866
|
+
*
|
|
53867
|
+
* @private function of `useChatAutoScroll`
|
|
53868
|
+
*/
|
|
53869
|
+
function useChatMessagesRefCallback({ chatMessagesRef, isAutoScrolling, lastScrollHeightRef, scrollToBottom, }) {
|
|
53870
|
+
return useCallback((element) => {
|
|
53871
|
+
chatMessagesRef.current = element;
|
|
53872
|
+
if (!element) {
|
|
53873
|
+
return;
|
|
53874
|
+
}
|
|
53875
|
+
initializeChatMessagesElement({
|
|
53876
|
+
element,
|
|
53877
|
+
isAutoScrolling,
|
|
53878
|
+
lastScrollHeightRef,
|
|
53879
|
+
scrollToBottom,
|
|
53880
|
+
});
|
|
53881
|
+
}, [chatMessagesRef, isAutoScrolling, lastScrollHeightRef, scrollToBottom]);
|
|
53882
|
+
}
|
|
51686
53883
|
/**
|
|
51687
53884
|
* Hook for managing auto-scroll behavior in chat components
|
|
51688
53885
|
*
|
|
@@ -51696,153 +53893,61 @@ const SCROLL_EVENT_DEBOUNCE_MS = 50;
|
|
|
51696
53893
|
*/
|
|
51697
53894
|
function useChatAutoScroll(config = {}) {
|
|
51698
53895
|
const { bottomThreshold = 100, smoothScroll = true, scrollCheckDelay = 100 } = config;
|
|
53896
|
+
const isMobile = useMobileChatViewport();
|
|
51699
53897
|
const [isAutoScrolling, setIsAutoScrolling] = useState(true);
|
|
51700
|
-
const [isMobile, setIsMobile] = useState(false);
|
|
51701
53898
|
const chatMessagesRef = useRef(null);
|
|
51702
53899
|
const scrollTimeoutRef = useRef(null);
|
|
51703
53900
|
const lastScrollHeightRef = useRef(0);
|
|
51704
|
-
// Tracks whether the user moved away from the bottom so we can suspend auto-scroll.
|
|
51705
53901
|
const hasManualScrollRef = useRef(false);
|
|
51706
|
-
|
|
51707
|
-
|
|
51708
|
-
const checkMobile = () => {
|
|
51709
|
-
const isMobileDevice = window.innerWidth <= 768 ||
|
|
51710
|
-
/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
|
51711
|
-
setIsMobile(isMobileDevice);
|
|
51712
|
-
};
|
|
51713
|
-
checkMobile();
|
|
51714
|
-
window.addEventListener('resize', checkMobile);
|
|
51715
|
-
return () => window.removeEventListener('resize', checkMobile);
|
|
51716
|
-
}, []);
|
|
51717
|
-
// Check if user is at the bottom of the chat
|
|
51718
|
-
const checkIfAtBottom = useCallback((element) => {
|
|
51719
|
-
const { scrollTop, scrollHeight, clientHeight } = element;
|
|
51720
|
-
return scrollTop + clientHeight >= scrollHeight - bottomThreshold;
|
|
51721
|
-
}, [bottomThreshold]);
|
|
51722
|
-
// Scroll to bottom function
|
|
53902
|
+
const messagesChangeTimeoutRef = useRef(null);
|
|
53903
|
+
const checkIfAtBottom = useCallback((element) => isChatScrolledToBottom(element, bottomThreshold), [bottomThreshold]);
|
|
51723
53904
|
const scrollToBottom = useCallback((behavior = 'smooth') => {
|
|
51724
53905
|
const chatMessagesElement = chatMessagesRef.current;
|
|
51725
|
-
if (!chatMessagesElement)
|
|
53906
|
+
if (!chatMessagesElement) {
|
|
51726
53907
|
return;
|
|
51727
|
-
if (isMobile) {
|
|
51728
|
-
// Mobile-optimized scrolling
|
|
51729
|
-
chatMessagesElement.scrollTo({
|
|
51730
|
-
top: chatMessagesElement.scrollHeight,
|
|
51731
|
-
behavior: smoothScroll ? behavior : 'auto',
|
|
51732
|
-
});
|
|
51733
|
-
}
|
|
51734
|
-
else {
|
|
51735
|
-
// Desktop scrolling
|
|
51736
|
-
if (smoothScroll && behavior === 'smooth') {
|
|
51737
|
-
chatMessagesElement.style.scrollBehavior = 'smooth';
|
|
51738
|
-
chatMessagesElement.scrollTop = chatMessagesElement.scrollHeight;
|
|
51739
|
-
chatMessagesElement.style.scrollBehavior = 'auto';
|
|
51740
|
-
}
|
|
51741
|
-
else {
|
|
51742
|
-
chatMessagesElement.scrollTop = chatMessagesElement.scrollHeight;
|
|
51743
|
-
}
|
|
51744
53908
|
}
|
|
53909
|
+
performScrollToBottom({
|
|
53910
|
+
behavior,
|
|
53911
|
+
chatMessagesElement,
|
|
53912
|
+
isMobile,
|
|
53913
|
+
smoothScroll,
|
|
53914
|
+
});
|
|
51745
53915
|
hasManualScrollRef.current = false;
|
|
51746
53916
|
}, [isMobile, smoothScroll]);
|
|
51747
|
-
|
|
51748
|
-
|
|
51749
|
-
|
|
51750
|
-
|
|
51751
|
-
|
|
51752
|
-
|
|
51753
|
-
|
|
51754
|
-
|
|
51755
|
-
|
|
51756
|
-
|
|
51757
|
-
|
|
51758
|
-
|
|
51759
|
-
|
|
51760
|
-
|
|
51761
|
-
|
|
51762
|
-
|
|
51763
|
-
|
|
51764
|
-
|
|
51765
|
-
|
|
51766
|
-
|
|
51767
|
-
|
|
51768
|
-
|
|
51769
|
-
|
|
51770
|
-
const
|
|
51771
|
-
const chatMessagesElement = chatMessagesRef.current;
|
|
51772
|
-
if (!chatMessagesElement)
|
|
51773
|
-
return;
|
|
51774
|
-
// Check if this is a new message (scroll height increased)
|
|
51775
|
-
const previousScrollHeight = lastScrollHeightRef.current;
|
|
51776
|
-
const currentScrollHeight = chatMessagesElement.scrollHeight;
|
|
51777
|
-
const hasNewContent = currentScrollHeight > previousScrollHeight;
|
|
51778
|
-
const wasAtBottomBeforeNewContent = chatMessagesElement.scrollTop + chatMessagesElement.clientHeight >=
|
|
51779
|
-
previousScrollHeight - bottomThreshold;
|
|
51780
|
-
lastScrollHeightRef.current = currentScrollHeight;
|
|
51781
|
-
if (!hasNewContent)
|
|
51782
|
-
return;
|
|
51783
|
-
// Only auto-scroll if user does NOT have a selection inside chat container
|
|
51784
|
-
const selection = window.getSelection();
|
|
51785
|
-
let hasSelectionInChat = false;
|
|
51786
|
-
if (selection && selection.rangeCount > 0) {
|
|
51787
|
-
const range = selection.getRangeAt(0);
|
|
51788
|
-
if (chatMessagesElement.contains(range.startContainer) ||
|
|
51789
|
-
chatMessagesElement.contains(range.endContainer)) {
|
|
51790
|
-
if (!selection.isCollapsed) {
|
|
51791
|
-
hasSelectionInChat = true;
|
|
51792
|
-
}
|
|
51793
|
-
}
|
|
51794
|
-
}
|
|
51795
|
-
if (isAutoScrolling && wasAtBottomBeforeNewContent && !hasSelectionInChat && !hasManualScrollRef.current) {
|
|
51796
|
-
if (messagesChangeTimeoutRef.current) {
|
|
51797
|
-
clearTimeout(messagesChangeTimeoutRef.current);
|
|
51798
|
-
}
|
|
51799
|
-
// Delay scroll slightly to ensure DOM has updated
|
|
51800
|
-
messagesChangeTimeoutRef.current = setTimeout(() => {
|
|
51801
|
-
if (hasManualScrollRef.current) {
|
|
51802
|
-
return;
|
|
51803
|
-
}
|
|
51804
|
-
scrollToBottom(isStreaming ? 'auto' : 'smooth');
|
|
51805
|
-
}, isStreaming ? 10 : scrollCheckDelay);
|
|
51806
|
-
}
|
|
51807
|
-
}, [bottomThreshold, isAutoScrolling, scrollToBottom, scrollCheckDelay]);
|
|
51808
|
-
// Ref callback for chat messages container
|
|
51809
|
-
const chatMessagesRefCallback = useCallback((element) => {
|
|
51810
|
-
chatMessagesRef.current = element;
|
|
51811
|
-
if (element) {
|
|
51812
|
-
// Update last scroll height
|
|
51813
|
-
lastScrollHeightRef.current = element.scrollHeight;
|
|
51814
|
-
// If auto-scrolling is enabled, scroll to bottom
|
|
51815
|
-
if (isAutoScrolling) {
|
|
51816
|
-
// Use requestAnimationFrame for smoother initial scroll
|
|
51817
|
-
requestAnimationFrame(() => {
|
|
51818
|
-
scrollToBottom('auto');
|
|
51819
|
-
});
|
|
51820
|
-
}
|
|
51821
|
-
}
|
|
51822
|
-
}, [isAutoScrolling, scrollToBottom]);
|
|
51823
|
-
// Manual scroll to bottom (for button click)
|
|
51824
|
-
const handleScrollToBottomClick = useCallback(() => {
|
|
51825
|
-
setIsAutoScrolling(true);
|
|
51826
|
-
scrollToBottom('smooth');
|
|
51827
|
-
}, [scrollToBottom]);
|
|
51828
|
-
// Force auto-scroll back on (useful for programmatic control)
|
|
51829
|
-
const enableAutoScroll = useCallback(() => {
|
|
53917
|
+
const handleScroll = useChatScrollHandler({
|
|
53918
|
+
checkIfAtBottom,
|
|
53919
|
+
hasManualScrollRef,
|
|
53920
|
+
isAutoScrolling,
|
|
53921
|
+
scrollTimeoutRef,
|
|
53922
|
+
setIsAutoScrolling,
|
|
53923
|
+
});
|
|
53924
|
+
const handleMessagesChange = useChatMessagesChangeHandler({
|
|
53925
|
+
bottomThreshold,
|
|
53926
|
+
chatMessagesRef,
|
|
53927
|
+
hasManualScrollRef,
|
|
53928
|
+
isAutoScrolling,
|
|
53929
|
+
lastScrollHeightRef,
|
|
53930
|
+
messagesChangeTimeoutRef,
|
|
53931
|
+
scrollCheckDelay,
|
|
53932
|
+
scrollToBottom,
|
|
53933
|
+
});
|
|
53934
|
+
const chatMessagesRefCallback = useChatMessagesRefCallback({
|
|
53935
|
+
chatMessagesRef,
|
|
53936
|
+
isAutoScrolling,
|
|
53937
|
+
lastScrollHeightRef,
|
|
53938
|
+
scrollToBottom,
|
|
53939
|
+
});
|
|
53940
|
+
const activateAutoScroll = useCallback(() => {
|
|
51830
53941
|
setIsAutoScrolling(true);
|
|
51831
53942
|
scrollToBottom('smooth');
|
|
51832
53943
|
}, [scrollToBottom]);
|
|
51833
|
-
// Disable auto-scroll (useful for programmatic control)
|
|
51834
53944
|
const disableAutoScroll = useCallback(() => {
|
|
51835
53945
|
setIsAutoScrolling(false);
|
|
51836
53946
|
}, []);
|
|
51837
|
-
// Cleanup timeout on unmount
|
|
51838
53947
|
useEffect(() => {
|
|
51839
53948
|
return () => {
|
|
51840
|
-
|
|
51841
|
-
|
|
51842
|
-
}
|
|
51843
|
-
if (messagesChangeTimeoutRef.current) {
|
|
51844
|
-
clearTimeout(messagesChangeTimeoutRef.current);
|
|
51845
|
-
}
|
|
53949
|
+
clearScheduledTimeout(scrollTimeoutRef);
|
|
53950
|
+
clearScheduledTimeout(messagesChangeTimeoutRef);
|
|
51846
53951
|
};
|
|
51847
53952
|
}, []);
|
|
51848
53953
|
return {
|
|
@@ -51850,8 +53955,8 @@ function useChatAutoScroll(config = {}) {
|
|
|
51850
53955
|
chatMessagesRef: chatMessagesRefCallback,
|
|
51851
53956
|
handleScroll,
|
|
51852
53957
|
handleMessagesChange,
|
|
51853
|
-
scrollToBottom:
|
|
51854
|
-
enableAutoScroll,
|
|
53958
|
+
scrollToBottom: activateAutoScroll,
|
|
53959
|
+
enableAutoScroll: activateAutoScroll,
|
|
51855
53960
|
disableAutoScroll,
|
|
51856
53961
|
isMobile,
|
|
51857
53962
|
};
|