@promptbook/components 0.105.0-11 → 0.105.0-12

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 CHANGED
@@ -35,7 +35,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
35
35
  * @generated
36
36
  * @see https://github.com/webgptorg/promptbook
37
37
  */
38
- const PROMPTBOOK_ENGINE_VERSION = '0.105.0-11';
38
+ const PROMPTBOOK_ENGINE_VERSION = '0.105.0-12';
39
39
  /**
40
40
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
41
41
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -11456,7 +11456,9 @@ const ChatMessageItem = memo(({ message, participant, participants, isLastMessag
11456
11456
  const toolInfo = TOOL_TITLES[toolCall.name];
11457
11457
  const toolTitle = (toolTitles === null || toolTitles === void 0 ? void 0 : toolTitles[toolCall.name]) || (toolInfo === null || toolInfo === void 0 ? void 0 : toolInfo.title);
11458
11458
  const emoji = (toolInfo === null || toolInfo === void 0 ? void 0 : toolInfo.emoji) || '🛠️';
11459
- return (jsxs("div", { className: chatStyles.ongoingToolCall, children: [jsx("div", { className: chatStyles.ongoingToolCallSpinner }), jsx("span", { className: chatStyles.ongoingToolCallName, children: toolTitle ? `${emoji} ${toolTitle}...` : `${emoji} Executing ${toolCall.name}...` })] }, index));
11459
+ return (jsxs("div", { className: chatStyles.ongoingToolCall, children: [jsx("div", { className: chatStyles.ongoingToolCallSpinner }), jsx("span", { className: chatStyles.ongoingToolCallName, children: toolTitle
11460
+ ? `${emoji} ${toolTitle}...`
11461
+ : `${emoji} Executing ${toolCall.name}...` })] }, index));
11460
11462
  })) : (jsx("span", { className: chatStyles.NonCompleteMessageFiller, children: '_'.repeat(70) })) })), shouldShowButtons && (jsx("div", { className: chatStyles.messageButtons, children: buttons.map((button, buttonIndex) => (jsx("button", { className: chatStyles.messageButton, onClick: (event) => {
11461
11463
  event.stopPropagation();
11462
11464
  if (onMessage) {
@@ -11576,7 +11578,6 @@ function Chat(props) {
11576
11578
  const [isUploading, setIsUploading] = useState(false);
11577
11579
  // Voice recognition state
11578
11580
  const [speechRecognitionState, setSpeechRecognitionState] = useState('IDLE');
11579
- const [speechRecognitionText, setSpeechRecognitionText] = useState('');
11580
11581
  // Use mobile detection from the hook
11581
11582
  const isMobile = isMobileFromHook;
11582
11583
  useEffect(( /* Focus textarea on page load */) => {
@@ -11597,23 +11598,16 @@ function Chat(props) {
11597
11598
  const unsubscribe = speechRecognition.subscribe((event) => {
11598
11599
  if (event.type === 'START') {
11599
11600
  setSpeechRecognitionState('RECORDING');
11600
- setSpeechRecognitionText('');
11601
11601
  }
11602
11602
  else if (event.type === 'RESULT') {
11603
- setSpeechRecognitionText(event.text);
11603
+ // [🧠] Note: This logic assumes that interim results are being updated.
11604
+ // For OpenAiSpeechRecognition, it's just one final result.
11604
11605
  if (textareaRef.current) {
11605
11606
  const textarea = textareaRef.current;
11606
11607
  const currentValue = textarea.value;
11607
- const lastResult = speechRecognitionText;
11608
- // If the current value ends with the last interim result, replace it
11609
- if (lastResult && currentValue.endsWith(lastResult)) {
11610
- textarea.value = currentValue.slice(0, -lastResult.length) + event.text;
11611
- }
11612
- else {
11613
- // Otherwise just append with a space if needed
11614
- const separator = currentValue && !currentValue.endsWith(' ') ? ' ' : '';
11615
- textarea.value += separator + event.text;
11616
- }
11608
+ // Append the transcribed text with a space if needed
11609
+ const separator = currentValue && !currentValue.endsWith(' ') && !currentValue.endsWith('\n') ? ' ' : '';
11610
+ textarea.value += separator + event.text;
11617
11611
  if (onChange) {
11618
11612
  onChange(textarea.value);
11619
11613
  }
@@ -11625,13 +11619,12 @@ function Chat(props) {
11625
11619
  }
11626
11620
  else if (event.type === 'STOP') {
11627
11621
  setSpeechRecognitionState('IDLE');
11628
- setSpeechRecognitionText('');
11629
11622
  }
11630
11623
  });
11631
11624
  return () => {
11632
11625
  unsubscribe();
11633
11626
  };
11634
- }, [speechRecognition, onChange, speechRecognitionText]);
11627
+ }, [speechRecognition, onChange]);
11635
11628
  const handleToggleVoiceInput = useCallback(() => {
11636
11629
  if (!speechRecognition) {
11637
11630
  return;
@@ -11688,6 +11681,15 @@ function Chat(props) {
11688
11681
  event.preventDefault();
11689
11682
  setIsDragOver(false);
11690
11683
  }, []);
11684
+ const handlePaste = useCallback((event) => {
11685
+ if (!onFileUpload)
11686
+ return;
11687
+ const files = event.clipboardData.files;
11688
+ if (files.length > 0) {
11689
+ // event.preventDefault(); // [🧠] Do NOT prevent default, because we want to allow pasting text too
11690
+ handleFileUpload(files);
11691
+ }
11692
+ }, [onFileUpload, handleFileUpload]);
11691
11693
  const handleFileInputChange = useCallback((event) => {
11692
11694
  const files = event.target.files;
11693
11695
  if (files && files.length > 0) {
@@ -11934,7 +11936,7 @@ function Chat(props) {
11934
11936
  '--brand-color': buttonColor.toHex(),
11935
11937
  }, children: [jsx("textarea", { ref: (element) => {
11936
11938
  textareaRef.current = element;
11937
- }, style: {
11939
+ }, onPaste: handlePaste, style: {
11938
11940
  height: Math.max(countLines(((_b = textareaRef.current) === null || _b === void 0 ? void 0 : _b.value) || defaultMessage || ''), (((_c = textareaRef.current) === null || _c === void 0 ? void 0 : _c.value) || defaultMessage || '').split('\n')
11939
11941
  .length, 3) *
11940
11942
  25 +
@@ -12002,13 +12004,27 @@ function Chat(props) {
12002
12004
  ? JSON.parse(selectedToolCall.arguments)
12003
12005
  : selectedToolCall.arguments || {};
12004
12006
  const resultRaw = selectedToolCall.result;
12005
- const results = Array.isArray(resultRaw)
12007
+ let results = Array.isArray(resultRaw)
12006
12008
  ? resultRaw
12007
12009
  : resultRaw && typeof resultRaw === 'object' && Array.isArray(resultRaw.results)
12008
12010
  ? resultRaw.results
12009
12011
  : [];
12012
+ // [🧠] In Agent Server, search result might be wrapped in another result object
12013
+ if (results.length === 0 &&
12014
+ resultRaw &&
12015
+ typeof resultRaw === 'object' &&
12016
+ resultRaw.result) {
12017
+ const subResult = resultRaw.result;
12018
+ results = Array.isArray(subResult)
12019
+ ? subResult
12020
+ : subResult && typeof subResult === 'object' && Array.isArray(subResult.results)
12021
+ ? subResult.results
12022
+ : [];
12023
+ }
12010
12024
  if (isSearch) {
12011
- return (jsxs(Fragment, { children: [jsxs("div", { className: chatStyles.searchModalHeader, children: [jsx("span", { className: chatStyles.searchModalIcon, children: "\uD83D\uDD0E" }), jsx("h3", { className: chatStyles.searchModalQuery, children: args.query || 'Search Results' })] }), jsx("div", { className: chatStyles.searchModalContent, children: results.length > 0 ? (jsx("div", { className: chatStyles.searchResultsList, children: results.map((item, i) => (jsxs("div", { className: chatStyles.searchResultItem, children: [jsx("div", { className: chatStyles.searchResultUrl, children: item.url && (jsx("a", { href: item.url, target: "_blank", rel: "noreferrer", children: item.url })) }), jsx("h4", { className: chatStyles.searchResultTitle, children: item.url ? (jsx("a", { href: item.url, target: "_blank", rel: "noreferrer", children: item.title || 'Untitled' })) : (item.title || 'Untitled') }), jsx("p", { className: chatStyles.searchResultSnippet, children: item.snippet || item.content || '' })] }, i))) })) : (jsx("div", { className: chatStyles.noResults, children: resultRaw ? 'No search results found.' : 'Executing search...' })) })] }));
12025
+ return (jsxs(Fragment, { children: [jsxs("div", { className: chatStyles.searchModalHeader, children: [jsx("span", { className: chatStyles.searchModalIcon, children: "\uD83D\uDD0E" }), jsx("h3", { className: chatStyles.searchModalQuery, children: args.query || args.searchText || 'Search Results' })] }), jsx("div", { className: chatStyles.searchModalContent, children: results.length > 0 ? (jsx("div", { className: chatStyles.searchResultsList, children: results.map((item, i) => (jsxs("div", { className: chatStyles.searchResultItem, children: [jsx("div", { className: chatStyles.searchResultUrl, children: item.url && (jsx("a", { href: item.url, target: "_blank", rel: "noreferrer", children: item.url })) }), jsx("h4", { className: chatStyles.searchResultTitle, children: item.url ? (jsx("a", { href: item.url, target: "_blank", rel: "noreferrer", children: item.title || 'Untitled' })) : (item.title || 'Untitled') }), jsx("p", { className: chatStyles.searchResultSnippet, children: item.snippet || item.content || '' })] }, i))) })) : (jsx("div", { className: chatStyles.noResults, children: resultRaw
12026
+ ? 'No search results found.'
12027
+ : 'Search results are not available.' })) })] }));
12012
12028
  }
12013
12029
  // Fallback for other tools
12014
12030
  return (jsxs(Fragment, { children: [jsxs("h3", { children: ["Tool Call: ", (toolTitles === null || toolTitles === void 0 ? void 0 : toolTitles[selectedToolCall.name]) || selectedToolCall.name] }), jsxs("div", { className: chatStyles.toolCallDetails, children: [jsx("p", { children: jsx("strong", { children: "Arguments:" }) }), jsx("div", { className: chatStyles.toolCallDataContainer, children: args && typeof args === 'object' ? (jsx("ul", { className: chatStyles.toolCallArgsList, children: Object.entries(args).map(([key, value]) => (jsxs("li", { children: [jsxs("strong", { children: [key, ":"] }), ' ', typeof value === 'object'
@@ -18361,11 +18377,35 @@ class OpenAiCompatibleExecutionTools {
18361
18377
  },
18362
18378
  ]),
18363
18379
  ...threadMessages,
18364
- {
18380
+ ];
18381
+ if ('files' in prompt && Array.isArray(prompt.files) && prompt.files.length > 0) {
18382
+ const filesContent = await Promise.all(prompt.files.map(async (file) => {
18383
+ const arrayBuffer = await file.arrayBuffer();
18384
+ const base64 = Buffer.from(arrayBuffer).toString('base64');
18385
+ return {
18386
+ type: 'image_url',
18387
+ image_url: {
18388
+ url: `data:${file.type};base64,${base64}`,
18389
+ },
18390
+ };
18391
+ }));
18392
+ messages.push({
18393
+ role: 'user',
18394
+ content: [
18395
+ {
18396
+ type: 'text',
18397
+ text: rawPromptContent,
18398
+ },
18399
+ ...filesContent,
18400
+ ],
18401
+ });
18402
+ }
18403
+ else {
18404
+ messages.push({
18365
18405
  role: 'user',
18366
18406
  content: rawPromptContent,
18367
- },
18368
- ];
18407
+ });
18408
+ }
18369
18409
  let totalUsage = {
18370
18410
  price: uncertainNumber(0),
18371
18411
  input: {
@@ -19148,6 +19188,27 @@ class OpenAiExecutionTools extends OpenAiCompatibleExecutionTools {
19148
19188
  }
19149
19189
  }
19150
19190
 
19191
+ /**
19192
+ * Uploads files to OpenAI and returns their IDs
19193
+ *
19194
+ * @private utility for `OpenAiAssistantExecutionTools` and `OpenAiCompatibleExecutionTools`
19195
+ */
19196
+ async function uploadFilesToOpenAi(client, files) {
19197
+ const fileIds = [];
19198
+ for (const file of files) {
19199
+ // Note: OpenAI API expects a File object or a ReadStream
19200
+ // In browser environment, we can pass the File object directly
19201
+ // In Node.js environment, we might need to convert it or use a different approach
19202
+ // But since `Prompt.files` already contains `File` objects, we try to pass them directly
19203
+ const uploadedFile = await client.files.create({
19204
+ file: file,
19205
+ purpose: 'assistants',
19206
+ });
19207
+ fileIds.push(uploadedFile.id);
19208
+ }
19209
+ return fileIds;
19210
+ }
19211
+
19151
19212
  /**
19152
19213
  * Execution Tools for calling OpenAI API Assistants
19153
19214
  *
@@ -19244,16 +19305,26 @@ class OpenAiAssistantExecutionTools extends OpenAiExecutionTools {
19244
19305
  const threadMessages = [];
19245
19306
  // TODO: [🈹] Maybe this should not be here but in other place, look at commit 39d705e75e5bcf7a818c3af36bc13e1c8475c30c
19246
19307
  // Add previous messages from thread (if any)
19247
- if ('thread' in prompt &&
19248
- Array.isArray(prompt.thread)) {
19308
+ if ('thread' in prompt && Array.isArray(prompt.thread)) {
19249
19309
  const previousMessages = prompt.thread.map((msg) => ({
19250
- role: (msg.role === 'assistant' ? 'assistant' : 'user'),
19310
+ role: (msg.sender === 'assistant' ? 'assistant' : 'user'),
19251
19311
  content: msg.content,
19252
19312
  }));
19253
19313
  threadMessages.push(...previousMessages);
19254
19314
  }
19255
19315
  // Always add the current user message
19256
- threadMessages.push({ role: 'user', content: rawPromptContent });
19316
+ const currentUserMessage = {
19317
+ role: 'user',
19318
+ content: rawPromptContent,
19319
+ };
19320
+ if ('files' in prompt && Array.isArray(prompt.files) && prompt.files.length > 0) {
19321
+ const fileIds = await uploadFilesToOpenAi(client, prompt.files);
19322
+ currentUserMessage.attachments = fileIds.map((fileId) => ({
19323
+ file_id: fileId,
19324
+ tools: [{ type: 'file_search' }, { type: 'code_interpreter' }],
19325
+ }));
19326
+ }
19327
+ threadMessages.push(currentUserMessage);
19257
19328
  // Check if tools are being used - if so, use non-streaming mode
19258
19329
  const hasTools = modelRequirements.tools !== undefined && modelRequirements.tools.length > 0;
19259
19330
  const start = $getCurrentDate();