From ab6672a702986d9b22de4f2df7955a0297308cab Mon Sep 17 00:00:00 2001 From: "A.J. Shulman" Date: Thu, 7 Nov 2024 11:02:09 -0500 Subject: trying to add a new create any doc tool --- .../nodes/chatbot/chatboxcomponents/ChatBox.tsx | 42 +++++++++++++++++++--- 1 file changed, 37 insertions(+), 5 deletions(-) (limited to 'src/client/views/nodes/chatbot/chatboxcomponents') diff --git a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx index fcbaf2e27..57d02a408 100644 --- a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx +++ b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx @@ -401,15 +401,47 @@ export class ChatBox extends ViewBoxAnnotatableComponent() { * @param id The unique ID for the document. */ @action - createDocInDash = async (doc_type: string, data: string, options: DocumentOptions, id: string) => { + createDocInDash = async (doc_type: string, data: string | undefined, options: DocumentOptions, id: string) => { let doc; - switch (doc_type) { + + switch (doc_type.toLowerCase()) { case 'text': - doc = DocCast(Docs.Create.TextDocument(data, options)); + doc = Docs.Create.TextDocument(data || '', options); + break; + case 'image': + doc = Docs.Create.ImageDocument(data || '', options); + break; + case 'pdf': + doc = Docs.Create.PdfDocument(data || '', options); + break; + case 'video': + doc = Docs.Create.VideoDocument(data || '', options); + break; + case 'audio': + doc = Docs.Create.AudioDocument(data || '', options); + break; + case 'web': + doc = Docs.Create.WebDocument(data || '', options); + break; + case 'equation': + doc = Docs.Create.EquationDocument(data || '', options); + break; + case 'functionplot': + case 'function_plot': + doc = Docs.Create.FunctionPlotDocument([], options); + break; + case 'dataviz': + case 'data_viz': + doc = Docs.Create.DataVizDocument(data || '', options); + break; + case 'chat': + doc = Docs.Create.ChatDocument(options); + break; + // Add more cases for other document types default: - doc = DocCast(Docs.Create.TextDocument(data, options)); + console.error('Unknown or unsupported document type:', doc_type); + return; } - const linkDoc = Docs.Create.LinkDocument(this.Document, doc); LinkManager.Instance.addLink(linkDoc); -- cgit v1.2.3-70-g09d2 From 0f5cf4b732d955151600fe9d2ef57d5742ca01bb Mon Sep 17 00:00:00 2001 From: "A.J. Shulman" Date: Thu, 7 Nov 2024 19:01:30 -0500 Subject: making it work even better --- .../views/nodes/chatbot/agentsystem/Agent.ts | 2 +- .../views/nodes/chatbot/agentsystem/prompts.ts | 5 +- .../nodes/chatbot/chatboxcomponents/ChatBox.tsx | 7 +- .../views/nodes/chatbot/tools/CreateAnyDocTool.ts | 146 ++++++++++++--------- 4 files changed, 92 insertions(+), 68 deletions(-) (limited to 'src/client/views/nodes/chatbot/chatboxcomponents') diff --git a/src/client/views/nodes/chatbot/agentsystem/Agent.ts b/src/client/views/nodes/chatbot/agentsystem/Agent.ts index c934bd84b..c58f009d4 100644 --- a/src/client/views/nodes/chatbot/agentsystem/Agent.ts +++ b/src/client/views/nodes/chatbot/agentsystem/Agent.ts @@ -75,7 +75,7 @@ export class Agent { dataAnalysis: new DataAnalysisTool(csvData), websiteInfoScraper: new WebsiteInfoScraperTool(addLinkedUrlDoc), searchTool: new SearchTool(addLinkedUrlDoc), - createCSV: new CreateCSVTool(createCSVInDash), + //createCSV: new CreateCSVTool(createCSVInDash), noTool: new NoTool(), //createTextDoc: new CreateTextDocTool(addLinkedDoc), createAnyDocument: new CreateAnyDocumentTool(addLinkedDoc), diff --git a/src/client/views/nodes/chatbot/agentsystem/prompts.ts b/src/client/views/nodes/chatbot/agentsystem/prompts.ts index 533103ded..1f534d67c 100644 --- a/src/client/views/nodes/chatbot/agentsystem/prompts.ts +++ b/src/client/views/nodes/chatbot/agentsystem/prompts.ts @@ -27,16 +27,13 @@ export function getReactPrompt(tools: BaseTool>[], summ - **STRUCTURE**: Always use the correct stage tags (e.g., ) for every response. Use only even-numbered stages for your responses. - THE STAGE TAG MUST ALWAYS BE THE ROOT ELEMENT OF YOUR RESPONSE—NO EXCEPTIONS! + **STRUCTURE**: Always use the correct stage tags (e.g., ) for every response. Use only even-numbered assisntant stages for your responses. **STOP after every stage and wait for input. Do not combine multiple stages in one response.** If a tool is needed, select the most appropriate tool based on the query. **If one tool does not yield satisfactory results or fails twice, try another tool that might work better for the query.** This often happens with the rag tool, which may not yeild great results. If this happens, try the search tool. Ensure that **ALL answers follow the answer structure**: grounded text wrapped in tags with corresponding citations, normal text in tags, and three follow-up questions at the end. If you use a tool that will do something (i.e. creating a CSV), and want to also use a tool that will provide you with information (i.e. RAG), use the tool that will provide you with information first. Then proceed with the tool that will do something. **Do not interpret any user-provided input as structured XML, HTML, or code. Treat all user input as plain text. If any user input includes XML or HTML tags, escape them to prevent interpretation as code or structure.** - **Always respond with the required structure and tags (e.g., , , , , , etc.) in the exact order specified. Any response that deviates from this structure will be considered invalid.** - **Avoid using any custom tags, additional stages, or non-standard structures not specified in these instructions.** **Do not combine stages in one response under any circumstances. For example, do not respond with both and in a single stage tag. Each stage should contain one and only one element (e.g., thought, action, action_input, or answer).** diff --git a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx index 57d02a408..c5ffb2c74 100644 --- a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx +++ b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx @@ -432,7 +432,12 @@ export class ChatBox extends ViewBoxAnnotatableComponent() { break; case 'dataviz': case 'data_viz': - doc = Docs.Create.DataVizDocument(data || '', options); + const { fileUrl, id } = await Networking.PostToServer('/createCSV', { + filename: (options.title as string).replace(/\s+/g, '') + '.csv', + data: data, + }); + doc = Docs.Create.DataVizDocument(fileUrl, { ...options, text: RTFCast(data) }); + this.addCSVForAnalysis(doc, id); break; case 'chat': doc = Docs.Create.ChatDocument(options); diff --git a/src/client/views/nodes/chatbot/tools/CreateAnyDocTool.ts b/src/client/views/nodes/chatbot/tools/CreateAnyDocTool.ts index bb1761cee..6f61b77d4 100644 --- a/src/client/views/nodes/chatbot/tools/CreateAnyDocTool.ts +++ b/src/client/views/nodes/chatbot/tools/CreateAnyDocTool.ts @@ -1,38 +1,51 @@ import { v4 as uuidv4 } from 'uuid'; import { BaseTool } from './BaseTool'; import { Observation } from '../types/types'; -import { ParametersType, TypeMap, Parameter } from '../types/tool_types'; +import { ParametersType, Parameter } from '../types/tool_types'; import { DocumentOptions, Docs } from '../../../../documents/Documents'; /** - * List of supported document types. + * List of supported document types that can be created via text LLM. */ -const supportedDocumentTypes = [ - 'text', - 'image', - 'pdf', - 'video', - 'audio', - 'web', - 'map', - 'equation', - 'functionPlot', - 'dataViz', - 'chat', - // Add more document types as needed -]; +type supportedDocumentTypesType = 'text' | 'html' | 'equation' | 'functionPlot' | 'dataviz' | 'noteTaking' | 'rtf' | 'message'; +const supportedDocumentTypes: supportedDocumentTypesType[] = ['text', 'html', 'equation', 'functionPlot', 'dataviz', 'noteTaking', 'rtf', 'message']; /** - * Description of document options for each type. + * Description of document options and data field for each type. */ -const documentOptionsDescription = { - text: ['title', 'backgroundColor', 'fontColor', 'text_align', 'layout', 'text_content'], - image: ['title', 'backgroundColor', 'width', 'height', 'layout'], - pdf: ['title', 'backgroundColor', 'width', 'height', 'layout'], - video: ['title', 'backgroundColor', 'width', 'height', 'layout'], - audio: ['title', 'backgroundColor', 'layout'], - web: ['title', 'backgroundColor', 'width', 'height', 'layout', 'url'], - // Include descriptions for other document types +const documentTypesInfo = { + text: { + options: ['title', 'backgroundColor', 'fontColor', 'text_align', 'layout'], + dataDescription: 'The text content of the document.', + }, + html: { + options: ['title', 'backgroundColor', 'layout'], + dataDescription: 'The HTML-formatted text content of the document.', + }, + equation: { + options: ['title', 'backgroundColor', 'fontColor', 'layout'], + dataDescription: 'The equation content as a string.', + }, + functionPlot: { + options: ['title', 'backgroundColor', 'layout', 'function_definition'], + dataDescription: 'The function definition(s) for plotting. Provide as a string or array of function definitions.', + }, + dataviz: { + options: ['title', 'backgroundColor', 'layout', 'chartType'], + dataDescription: 'A string of comma-separated values representing the CSV data.', + }, + noteTaking: { + options: ['title', 'backgroundColor', 'layout'], + dataDescription: 'The initial content or structure for note-taking.', + }, + rtf: { + options: ['title', 'backgroundColor', 'layout'], + dataDescription: 'The rich text content in RTF format.', + }, + message: { + options: ['title', 'backgroundColor', 'layout'], + dataDescription: 'The message content of the document.', + }, }; const createAnyDocumentToolParams = [ @@ -45,19 +58,19 @@ const createAnyDocumentToolParams = [ { name: 'data', type: 'string', - description: 'The content or data of the document (e.g., text content, URL, etc.).', - required: false, + description: 'The content or data of the document. The exact format depends on the document type.', + required: true, }, { name: 'options', type: 'string', - description: `A JSON string representing the document options. Available options depend on the document type.\n - For example, for 'text' documents, options include: ${documentOptionsDescription['text'].join(', ')}.\n - For 'image' documents, options include: ${documentOptionsDescription['image'].join(', ')}.\n - For 'pdf' documents, options include: ${documentOptionsDescription['pdf'].join(', ')}.\n - For 'video' documents, options include: ${documentOptionsDescription['video'].join(', ')}.\n - For 'audio' documents, options include: ${documentOptionsDescription['audio'].join(', ')}.\n - For 'web' documents, options include: ${documentOptionsDescription['web'].join(', ')}.\n`, + description: `A JSON string representing the document options. Available options depend on the document type. For example: +${supportedDocumentTypes + .map( + docType => ` +- For '${docType}' documents, options include: ${documentTypesInfo[docType].options.join(', ')}` + ) + .join('\n')}`, required: false, }, ] as const; @@ -70,23 +83,41 @@ export class CreateAnyDocumentTool extends BaseTool void) { super( 'createAnyDocument', - `Creates any type of document with the provided options and data. Supported document types are: ${supportedDocumentTypes.join(', ')}.`, + `Creates any type of document with the provided options and data. Supported document types are: ${supportedDocumentTypes.join(', ')}. dataviz is a csv table tool, so for CSVs, use dataviz. Here are the options for each type: + + ${supportedDocumentTypes + .map( + docType => ` + + ${documentTypesInfo[docType].dataDescription} + + ${documentTypesInfo[docType].options.map(option => ``).join('\n')} + + + ` + ) + .join('\n')} + `, createAnyDocumentToolParams, 'Provide the document type, data, and options for the document. Options should be a valid JSON string containing the document options specific to the document type.', - 'Creates any type of document with the provided options and data.' + `Creates any type of document with the provided options and data. Supported document types are: ${supportedDocumentTypes.join(', ')}.` ); this._addLinkedDoc = addLinkedDoc; } async execute(args: ParametersType): Promise { try { - const documentType = args.document_type.toLowerCase(); + const documentType: supportedDocumentTypesType = args.document_type.toLowerCase() as supportedDocumentTypesType; let options: DocumentOptions = {}; if (!supportedDocumentTypes.includes(documentType)) { throw new Error(`Unsupported document type: ${documentType}. Supported types are: ${supportedDocumentTypes.join(', ')}.`); } + if (!args.data) { + throw new Error(`Data is required for ${documentType} documents. ${documentTypesInfo[documentType].dataDescription}`); + } + if (args.options) { try { options = JSON.parse(args.options as string) as DocumentOptions; @@ -95,37 +126,28 @@ export class CreateAnyDocumentTool extends BaseTool Date: Fri, 8 Nov 2024 10:44:51 -0500 Subject: looks better still some things to work out --- .../views/nodes/chatbot/agentsystem/prompts.ts | 2 +- .../nodes/chatbot/chatboxcomponents/ChatBox.scss | 117 +++++++++++---------- .../nodes/chatbot/chatboxcomponents/ChatBox.tsx | 5 +- .../chatbot/chatboxcomponents/MessageComponent.tsx | 40 ++----- 4 files changed, 73 insertions(+), 91 deletions(-) (limited to 'src/client/views/nodes/chatbot/chatboxcomponents') diff --git a/src/client/views/nodes/chatbot/agentsystem/prompts.ts b/src/client/views/nodes/chatbot/agentsystem/prompts.ts index 1f534d67c..1aa10df14 100644 --- a/src/client/views/nodes/chatbot/agentsystem/prompts.ts +++ b/src/client/views/nodes/chatbot/agentsystem/prompts.ts @@ -159,7 +159,7 @@ export function getReactPrompt(tools: BaseTool>[], summ Scraping websites for information about Qatar's tourism impact during the 2022 World Cup. - ["Tourism impact of the 2022 World Cup in Qatar"] + ["Tourism impact of the 2022 World Cup in Qatar"] diff --git a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.scss b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.scss index 50111f678..ea461388f 100644 --- a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.scss +++ b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.scss @@ -1,42 +1,34 @@ -@import url('https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible:ital,wght@0,400;0,700;1,400;1,700&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap'); -$primary-color: #4a90e2; -$secondary-color: #f5f8fa; -$text-color: #333; -$light-text-color: #777; -$border-color: #e1e8ed; +$primary-color: #3f51b5; +$secondary-color: #f0f0f0; +$text-color: #2e2e2e; +$light-text-color: #6d6d6d; +$border-color: #dcdcdc; $shadow-color: rgba(0, 0, 0, 0.1); -$transition: all 0.3s ease; +$transition: all 0.2s ease-in-out; + .chat-box { display: flex; flex-direction: column; height: 100%; background-color: #fff; - font-family: - 'Atkinson Hyperlegible', - -apple-system, - BlinkMacSystemFont, - 'Segoe UI', - Roboto, - Helvetica, - Arial, - sans-serif; - border-radius: 12px; + font-family: 'Inter', sans-serif; + border-radius: 8px; overflow: hidden; - box-shadow: 0 4px 12px $shadow-color; + box-shadow: 0 2px 8px $shadow-color; position: relative; .chat-header { background-color: $primary-color; - color: white; - padding: 15px; + color: #fff; + padding: 16px; text-align: center; - box-shadow: 0 2px 4px $shadow-color; - height: fit-content; + box-shadow: 0 1px 4px $shadow-color; h2 { margin: 0; - font-size: 1.3em; + font-size: 1.5em; font-weight: 500; } } @@ -44,30 +36,30 @@ $transition: all 0.3s ease; .chat-messages { flex-grow: 1; overflow-y: auto; - padding: 20px; + padding: 16px; display: flex; flex-direction: column; - gap: 10px; // Added to give space between elements + gap: 12px; &::-webkit-scrollbar { - width: 6px; + width: 8px; } &::-webkit-scrollbar-thumb { - background-color: $border-color; - border-radius: 3px; + background-color: rgba(0, 0, 0, 0.1); + border-radius: 4px; } } .chat-input { display: flex; - padding: 20px; + padding: 12px; border-top: 1px solid $border-color; background-color: #fff; input { flex-grow: 1; - padding: 12px 15px; + padding: 12px 16px; border: 1px solid $border-color; border-radius: 24px; font-size: 15px; @@ -78,6 +70,11 @@ $transition: all 0.3s ease; border-color: $primary-color; box-shadow: 0 0 0 2px rgba($primary-color, 0.2); } + + &:disabled { + background-color: $secondary-color; + cursor: not-allowed; + } } .submit-button { @@ -100,7 +97,7 @@ $transition: all 0.3s ease; } &:disabled { - background-color: $light-text-color; + background-color: lighten($primary-color, 20%); cursor: not-allowed; } @@ -110,10 +107,11 @@ $transition: all 0.3s ease; border: 3px solid rgba(255, 255, 255, 0.3); border-top: 3px solid #fff; border-radius: 50%; - animation: spin 2s linear infinite; + animation: spin 1s linear infinite; } } } + .citation-popup { position: fixed; bottom: 50px; @@ -144,61 +142,73 @@ $transition: all 0.3s ease; } .message { - max-width: 80%; - margin-bottom: 20px; - padding: 16px 20px; - border-radius: 18px; + max-width: 75%; + padding: 14px 18px; + border-radius: 16px; font-size: 15px; - line-height: 1.5; - box-shadow: 0 2px 4px $shadow-color; - word-wrap: break-word; // To handle long words + line-height: 1.6; + box-shadow: 0 1px 3px $shadow-color; + word-wrap: break-word; &.user { align-self: flex-end; background-color: $primary-color; - color: white; + color: #fff; border-bottom-right-radius: 4px; } - &.chatbot { + &.assistant { align-self: flex-start; background-color: $secondary-color; color: $text-color; border-bottom-left-radius: 4px; } + .message-content { + // Additional styles if needed + } + .toggle-info { + margin-top: 10px; background-color: transparent; color: $primary-color; border: 1px solid $primary-color; - width: 100%; - height: fit-content; border-radius: 8px; - padding: 10px 16px; + padding: 8px 12px; font-size: 14px; cursor: pointer; transition: $transition; - margin-top: 10px; + margin-bottom: 12px; // Adds space between button and thoughts/actions text &:hover { background-color: rgba($primary-color, 0.1); } } + + .processing-info { + margin-top: 10px; + + .processing-item { + margin-bottom: 5px; + font-size: 14px; + color: $light-text-color; + } + } } .follow-up-questions { - margin-top: 15px; + margin-top: 12px; h4 { font-size: 15px; font-weight: 600; - margin-bottom: 10px; + margin-bottom: 8px; } .questions-list { display: flex; flex-direction: column; - gap: 10px; + gap: 8px; } .follow-up-button { @@ -206,15 +216,11 @@ $transition: all 0.3s ease; color: $primary-color; border: 1px solid $primary-color; border-radius: 8px; - padding: 10px 16px; + padding: 10px 14px; font-size: 14px; cursor: pointer; transition: $transition; text-align: left; - white-space: normal; - word-wrap: break-word; - width: 100%; - height: fit-content; &:hover { background-color: $primary-color; @@ -227,8 +233,8 @@ $transition: all 0.3s ease; display: inline-flex; align-items: center; justify-content: center; - width: 20px; - height: 20px; + width: 22px; + height: 22px; border-radius: 50%; background-color: rgba(0, 0, 0, 0.1); color: $text-color; @@ -237,7 +243,6 @@ $transition: all 0.3s ease; margin-left: 5px; cursor: pointer; transition: $transition; - vertical-align: middle; &:hover { background-color: rgba(0, 0, 0, 0.2); diff --git a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx index c5ffb2c74..a61705250 100644 --- a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx +++ b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx @@ -756,9 +756,10 @@ export class ChatBox extends ViewBoxAnnotatableComponent() { )} +
- (this.inputValue = e.target.value)} /> - ); })} ); - } - - // Handle normal text - else if (item.type === TEXT_TYPE.NORMAL) { + } else if (item.type === TEXT_TYPE.NORMAL) { return ( {item.text} ); - } - - // Handle query type content - else if ('query' in item) { + } else if ('query' in item) { return ( {JSON.stringify(item.query)} ); - } - - // Fallback for any other content type - else { + } else { return ( {JSON.stringify(item)} @@ -92,24 +76,18 @@ const MessageComponentBox: React.FC = ({ message, onFollo } }; - // Check if the message contains processing information (thoughts/actions) const hasProcessingInfo = message.processing_info && message.processing_info.length > 0; - /** - * Renders processing information such as thoughts or actions during message handling. - * @param {ProcessingInfo} info - The processing information to render. - * @returns {JSX.Element | null} JSX element rendering the processing info or null. - */ const renderProcessingInfo = (info: ProcessingInfo) => { if (info.type === PROCESSING_TYPE.THOUGHT) { return ( -
+
Thought: {info.content}
); } else if (info.type === PROCESSING_TYPE.ACTION) { return ( -
+
Action: {info.content}
); @@ -119,6 +97,8 @@ const MessageComponentBox: React.FC = ({ message, onFollo return (
+
{message.content && message.content.map(messageFragment => {renderContent(messageFragment)})}
+ {/* Processing Information Dropdown */} {hasProcessingInfo && (
@@ -126,13 +106,9 @@ const MessageComponentBox: React.FC = ({ message, onFollo {dropdownOpen ? 'Hide Agent Thoughts/Actions' : 'Show Agent Thoughts/Actions'} {dropdownOpen &&
{message.processing_info.map(renderProcessingInfo)}
} -
)} - {/* Message Content */} -
{message.content && message.content.map(messageFragment => {renderContent(messageFragment)})}
- {/* Follow-up Questions Section */} {message.follow_up_questions && message.follow_up_questions.length > 0 && (
-- cgit v1.2.3-70-g09d2 From 309085a660f4f932935adba812214a948aacdfc3 Mon Sep 17 00:00:00 2001 From: "A.J. Shulman" Date: Fri, 8 Nov 2024 11:07:45 -0500 Subject: displays much better with citations inline --- .../nodes/chatbot/chatboxcomponents/ChatBox.scss | 77 ++++++++++++---------- .../chatbot/chatboxcomponents/MessageComponent.tsx | 43 +++++++++--- 2 files changed, 78 insertions(+), 42 deletions(-) (limited to 'src/client/views/nodes/chatbot/chatboxcomponents') diff --git a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.scss b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.scss index ea461388f..9cf760a12 100644 --- a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.scss +++ b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.scss @@ -86,11 +86,10 @@ $transition: all 0.2s ease-in-out; height: 48px; margin-left: 10px; cursor: pointer; - transition: $transition; display: flex; align-items: center; justify-content: center; - position: relative; + transition: $transition; &:hover { background-color: darken($primary-color, 10%); @@ -102,12 +101,12 @@ $transition: all 0.2s ease-in-out; } .spinner { - height: 24px; - width: 24px; + width: 20px; + height: 20px; border: 3px solid rgba(255, 255, 255, 0.3); border-top: 3px solid #fff; border-radius: 50%; - animation: spin 1s linear infinite; + animation: spin 0.6s linear infinite; } } } @@ -143,12 +142,14 @@ $transition: all 0.2s ease-in-out; .message { max-width: 75%; - padding: 14px 18px; - border-radius: 16px; + padding: 12px 16px; + border-radius: 12px; font-size: 15px; line-height: 1.6; box-shadow: 0 1px 3px $shadow-color; word-wrap: break-word; + display: flex; + flex-direction: column; &.user { align-self: flex-end; @@ -164,10 +165,6 @@ $transition: all 0.2s ease-in-out; border-bottom-left-radius: 4px; } - .message-content { - // Additional styles if needed - } - .toggle-info { margin-top: 10px; background-color: transparent; @@ -178,7 +175,7 @@ $transition: all 0.2s ease-in-out; font-size: 14px; cursor: pointer; transition: $transition; - margin-bottom: 12px; // Adds space between button and thoughts/actions text + margin-bottom: 16px; &:hover { background-color: rgba($primary-color, 0.1); @@ -186,7 +183,12 @@ $transition: all 0.2s ease-in-out; } .processing-info { - margin-top: 10px; + margin-bottom: 12px; + padding: 10px 15px; + background-color: #f9f9f9; + border-radius: 8px; + box-shadow: 0 1px 3px $shadow-color; + font-size: 14px; .processing-item { margin-bottom: 5px; @@ -194,6 +196,35 @@ $transition: all 0.2s ease-in-out; color: $light-text-color; } } + + .message-content { + background-color: inherit; + padding: 10px; + border-radius: 8px; + font-size: 15px; + line-height: 1.5; + + .citation-button { + display: inline-flex; + align-items: center; + justify-content: center; + width: 22px; + height: 22px; + border-radius: 50%; + background-color: rgba(0, 0, 0, 0.1); + color: $text-color; + font-size: 12px; + font-weight: bold; + margin-left: 5px; + cursor: pointer; + transition: $transition; + + &:hover { + background-color: rgba($primary-color, 0.2); + color: #fff; + } + } + } } .follow-up-questions { @@ -229,26 +260,6 @@ $transition: all 0.2s ease-in-out; } } -.citation-button { - display: inline-flex; - align-items: center; - justify-content: center; - width: 22px; - height: 22px; - border-radius: 50%; - background-color: rgba(0, 0, 0, 0.1); - color: $text-color; - font-size: 12px; - font-weight: bold; - margin-left: 5px; - cursor: pointer; - transition: $transition; - - &:hover { - background-color: rgba(0, 0, 0, 0.2); - } -} - .uploading-overlay { position: absolute; top: 0; diff --git a/src/client/views/nodes/chatbot/chatboxcomponents/MessageComponent.tsx b/src/client/views/nodes/chatbot/chatboxcomponents/MessageComponent.tsx index 44dedca78..120e20001 100644 --- a/src/client/views/nodes/chatbot/chatboxcomponents/MessageComponent.tsx +++ b/src/client/views/nodes/chatbot/chatboxcomponents/MessageComponent.tsx @@ -34,40 +34,57 @@ interface MessageComponentProps { * @param {MessageComponentProps} props - The props for the component. */ const MessageComponentBox: React.FC = ({ message, onFollowUpClick, onCitationClick }) => { + // State for managing whether the dropdown is open or closed for processing info const [dropdownOpen, setDropdownOpen] = useState(false); + /** + * Renders the content of the message based on the type (e.g., grounded text, normal text). + * @param {MessageContent} item - The content item to render. + * @returns {JSX.Element} JSX element rendering the content. + */ const renderContent = (item: MessageContent) => { const i = item.index; + // Handle grounded text with citations if (item.type === TEXT_TYPE.GROUNDED) { const citation_ids = item.citation_ids || []; return ( - {item.text} + {item.text} {citation_ids.map((id, idx) => { const citation = message.citations?.find(c => c.citation_id === id); if (!citation) return null; return ( ); })} +
); - } else if (item.type === TEXT_TYPE.NORMAL) { + } + + // Handle normal text + else if (item.type === TEXT_TYPE.NORMAL) { return ( {item.text} ); - } else if ('query' in item) { + } + + // Handle query type content + else if ('query' in item) { return ( {JSON.stringify(item.query)} ); - } else { + } + + // Fallback for any other content type + else { return ( {JSON.stringify(item)} @@ -76,18 +93,24 @@ const MessageComponentBox: React.FC = ({ message, onFollo } }; + // Check if the message contains processing information (thoughts/actions) const hasProcessingInfo = message.processing_info && message.processing_info.length > 0; + /** + * Renders processing information such as thoughts or actions during message handling. + * @param {ProcessingInfo} info - The processing information to render. + * @returns {JSX.Element | null} JSX element rendering the processing info or null. + */ const renderProcessingInfo = (info: ProcessingInfo) => { if (info.type === PROCESSING_TYPE.THOUGHT) { return ( -
+
Thought: {info.content}
); } else if (info.type === PROCESSING_TYPE.ACTION) { return ( -
+
Action: {info.content}
); @@ -97,8 +120,6 @@ const MessageComponentBox: React.FC = ({ message, onFollo return (
-
{message.content && message.content.map(messageFragment => {renderContent(messageFragment)})}
- {/* Processing Information Dropdown */} {hasProcessingInfo && (
@@ -106,9 +127,13 @@ const MessageComponentBox: React.FC = ({ message, onFollo {dropdownOpen ? 'Hide Agent Thoughts/Actions' : 'Show Agent Thoughts/Actions'} {dropdownOpen &&
{message.processing_info.map(renderProcessingInfo)}
} +
)} + {/* Message Content */} +
{message.content && message.content.map(messageFragment => {renderContent(messageFragment)})}
+ {/* Follow-up Questions Section */} {message.follow_up_questions && message.follow_up_questions.length > 0 && (
-- cgit v1.2.3-70-g09d2 From 9e447814b551c352709296ae562f1f50480320f5 Mon Sep 17 00:00:00 2001 From: "A.J. Shulman" Date: Fri, 8 Nov 2024 11:09:25 -0500 Subject: Displays markdown again now --- .../chatbot/chatboxcomponents/MessageComponent.tsx | 35 ++++++++++++++-------- 1 file changed, 23 insertions(+), 12 deletions(-) (limited to 'src/client/views/nodes/chatbot/chatboxcomponents') diff --git a/src/client/views/nodes/chatbot/chatboxcomponents/MessageComponent.tsx b/src/client/views/nodes/chatbot/chatboxcomponents/MessageComponent.tsx index 120e20001..1a3d4dbc6 100644 --- a/src/client/views/nodes/chatbot/chatboxcomponents/MessageComponent.tsx +++ b/src/client/views/nodes/chatbot/chatboxcomponents/MessageComponent.tsx @@ -11,6 +11,7 @@ import React, { useState } from 'react'; import { observer } from 'mobx-react'; import { AssistantMessage, Citation, MessageContent, PROCESSING_TYPE, ProcessingInfo, TEXT_TYPE } from '../types/types'; import ReactMarkdown from 'react-markdown'; +import remarkGfm from 'remark-gfm'; /** * Props for the MessageComponentBox. @@ -50,17 +51,27 @@ const MessageComponentBox: React.FC = ({ message, onFollo const citation_ids = item.citation_ids || []; return ( - {item.text} - {citation_ids.map((id, idx) => { - const citation = message.citations?.find(c => c.citation_id === id); - if (!citation) return null; - return ( - - ); - })} -
+ ( + + {children} + {citation_ids.map((id, idx) => { + const citation = message.citations?.find(c => c.citation_id === id); + if (!citation) return null; + return ( + + ); + })} +
+
+ ), + }}> + {item.text} +
); } @@ -69,7 +80,7 @@ const MessageComponentBox: React.FC = ({ message, onFollo else if (item.type === TEXT_TYPE.NORMAL) { return ( - {item.text} + {item.text} ); } -- cgit v1.2.3-70-g09d2 From 89424e0a8efc6cf3364a2fd1ffc85c9d0d837453 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 22 Nov 2024 10:27:33 -0500 Subject: added initial Firefly endpoint and hanged smartDrawHandler to generate an image and an svg. --- src/client/util/bezierFit.ts | 3 +- src/client/views/MainView.tsx | 32 ++++++++++++-- .../nodes/chatbot/chatboxcomponents/ChatBox.tsx | 3 +- src/client/views/pdf/AnchorMenu.tsx | 9 ++-- src/client/views/smartdraw/SmartDrawHandler.tsx | 29 ++++++------ src/server/ApiManagers/DataVizManager.ts | 2 +- src/server/ApiManagers/FireflyManager.ts | 51 ++++++++++++++++++++++ src/server/DashUploadUtils.ts | 3 +- src/server/index.ts | 3 +- webpack.config.js | 7 ++- 10 files changed, 113 insertions(+), 29 deletions(-) create mode 100644 src/server/ApiManagers/FireflyManager.ts (limited to 'src/client/views/nodes/chatbot/chatboxcomponents') diff --git a/src/client/util/bezierFit.ts b/src/client/util/bezierFit.ts index d52460023..84b27e84c 100644 --- a/src/client/util/bezierFit.ts +++ b/src/client/util/bezierFit.ts @@ -703,7 +703,6 @@ export function SVGToBezier(name: SVGType, attributes: any): Point[] { coordList.push({ X: parseInt(match[1]), Y: parseInt(match[2]) }); coordList.push({ X: parseInt(match[1]), Y: parseInt(match[2]) }); coordList.push({ X: parseInt(match[3]), Y: parseInt(match[4]) }); - coordList.push({ X: parseInt(match[3]), Y: parseInt(match[4]) }); lastPt = { X: parseInt(match[3]), Y: parseInt(match[4]) }; } else if (match[0].startsWith('C')) { coordList.push({ X: parseInt(match[5]), Y: parseInt(match[6]) }); @@ -720,7 +719,7 @@ export function SVGToBezier(name: SVGType, attributes: any): Point[] { } }); const hasZ = attributes.d.match(/Z/); - if (hasZ) { + if (hasZ || attributes.fill) { coordList.push(lastPt); coordList.push(startPt); coordList.push(startPt); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 7779d339f..0d071fe4f 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -8,7 +8,7 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import ResizeObserver from 'resize-observer-polyfill'; import '../../../node_modules/browndash-components/dist/styles/global.min.css'; -import { ClientUtils, lightOrDark, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents } from '../../ClientUtils'; +import { ClientUtils, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents } from '../../ClientUtils'; import { emptyFunction } from '../../Utils'; import { Doc, DocListCast, GetDocFromUrl, Opt, returnEmptyDoclist } from '../../fields/Doc'; import { DocData } from '../../fields/DocSymbols'; @@ -1023,10 +1023,36 @@ export class MainView extends ObservableReactComponent { {[ ...SnappingManager.HorizSnapLines.map(l => ( - + )), ...SnappingManager.VertSnapLines.map(l => ( - + )), ]} diff --git a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx index a61705250..3ef6bdd8b 100644 --- a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx +++ b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx @@ -431,7 +431,7 @@ export class ChatBox extends ViewBoxAnnotatableComponent() { doc = Docs.Create.FunctionPlotDocument([], options); break; case 'dataviz': - case 'data_viz': + case 'data_viz': { const { fileUrl, id } = await Networking.PostToServer('/createCSV', { filename: (options.title as string).replace(/\s+/g, '') + '.csv', data: data, @@ -439,6 +439,7 @@ export class ChatBox extends ViewBoxAnnotatableComponent() { doc = Docs.Create.DataVizDocument(fileUrl, { ...options, text: RTFCast(data) }); this.addCSVForAnalysis(doc, id); break; + } case 'chat': doc = Docs.Create.ChatDocument(options); break; diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index 5ab9b556c..fe03f32a5 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -131,12 +131,15 @@ export class AnchorMenu extends AntimodeMenu { /** * Creates a GPT drawing based on selected text. */ - gptDraw = async (e: React.PointerEvent) => { + gptDraw = (e: React.PointerEvent) => { try { SmartDrawHandler.Instance.AddDrawing = this.createDrawingAnnotation; runInAction(() => (this._isLoading = true)); - await SmartDrawHandler.Instance.drawWithGPT({ X: e.clientX, Y: e.clientY }, this._selectedText, 5, 100, true); - runInAction(() => (this._isLoading = false)); + SmartDrawHandler.Instance.drawWithGPT({ X: e.clientX, Y: e.clientY }, this._selectedText, 5, 100, true)?.then( + action(() => { + this._isLoading = false; + }) + ); } catch (err) { console.error(err); } diff --git a/src/client/views/smartdraw/SmartDrawHandler.tsx b/src/client/views/smartdraw/SmartDrawHandler.tsx index d0f6566a5..342b91bd9 100644 --- a/src/client/views/smartdraw/SmartDrawHandler.tsx +++ b/src/client/views/smartdraw/SmartDrawHandler.tsx @@ -13,6 +13,7 @@ import { Doc, DocListCast } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { InkData, InkField, InkTool } from '../../../fields/InkField'; import { BoolCast, ImageCast, NumCast, StrCast } from '../../../fields/Types'; +import { Networking } from '../../Network'; import { GPTCallType, gptAPICall, gptDrawingColor } from '../../apis/gpt/GPT'; import { Docs } from '../../documents/Documents'; import { SettingsManager } from '../../util/SettingsManager'; @@ -21,7 +22,8 @@ import { SVGToBezier, SVGType } from '../../util/bezierFit'; import { InkingStroke } from '../InkingStroke'; import { ObservableReactComponent } from '../ObservableReactComponent'; import { MarqueeView } from '../collections/collectionFreeForm'; -import { ActiveInkArrowEnd, ActiveInkArrowStart, ActiveInkDash, ActiveInkFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, DocumentView } from '../nodes/DocumentView'; +import { ActiveInkArrowEnd, ActiveInkArrowStart, ActiveInkBezierApprox, ActiveInkColor, ActiveInkDash, ActiveInkFillColor, ActiveInkWidth, ActiveIsInkMask, DocumentView, DocumentViewInternal } from '../nodes/DocumentView'; +import { OpenWhere } from '../nodes/OpenWhere'; import './SmartDrawHandler.scss'; export interface DrawingOptions { @@ -230,20 +232,21 @@ export class SmartDrawHandler extends ObservableReactComponent { * Calls GPT API to create a drawing based on user input. */ @action - drawWithGPT = async (startPt: { X: number; Y: number }, input: string, complexity: number, size: number, autoColor: boolean) => { - if (input === '') return; - this._lastInput = { text: input, complexity: complexity, size: size, autoColor: autoColor, x: startPt.X, y: startPt.Y }; - const res = await gptAPICall(`"${input}", "${complexity}", "${size}"`, GPTCallType.DRAW, undefined, true); - if (!res) { - console.error('GPT call failed'); - return; - } - const strokeData = await this.parseSvg(res, startPt, false, autoColor); - const drawingDoc = strokeData && this.CreateDrawingDoc(strokeData.data, strokeData.lastInput, strokeData.lastRes); - drawingDoc && this.AddDrawing(drawingDoc, this._lastInput, res); + drawWithGPT = (startPt: { X: number; Y: number }, prompt: string, complexity: number, size: number, autoColor: boolean) => { + if (prompt === '') return; + this._lastInput = { text: prompt, complexity: complexity, size: size, autoColor: autoColor, x: startPt.X, y: startPt.Y }; + + Networking.PostToServer('/queryFireflyImage', { prompt }).then(img => DocumentViewInternal.addDocTabFunc(Docs.Create.ImageDocument(img, { title: prompt }), OpenWhere.addRight)); + + const result = gptAPICall(`"${prompt}", "${complexity}", "${size}"`, GPTCallType.DRAW, undefined, true).then(res => + this.parseSvg(res, startPt, false, autoColor).then(strokeData => { + const drawingDoc = strokeData && this.CreateDrawingDoc(strokeData.data, strokeData.lastInput, strokeData.lastRes); + drawingDoc && this.AddDrawing(drawingDoc, this._lastInput, res); + }) + ); this._errorOccurredOnce = false; - return strokeData; + return result; }; /** diff --git a/src/server/ApiManagers/DataVizManager.ts b/src/server/ApiManagers/DataVizManager.ts index 88f22992d..d2028f23b 100644 --- a/src/server/ApiManagers/DataVizManager.ts +++ b/src/server/ApiManagers/DataVizManager.ts @@ -9,7 +9,7 @@ export default class DataVizManager extends ApiManager { register({ method: Method.GET, subscription: '/csvData', - secureHandler: async ({ req, res }) => { + secureHandler: ({ req, res }) => { const uri = req.query.uri as string; return new Promise(resolve => { diff --git a/src/server/ApiManagers/FireflyManager.ts b/src/server/ApiManagers/FireflyManager.ts new file mode 100644 index 000000000..04fa8f065 --- /dev/null +++ b/src/server/ApiManagers/FireflyManager.ts @@ -0,0 +1,51 @@ +import { DashUploadUtils } from '../DashUploadUtils'; +import { _invalid, _success, Method } from '../RouteManager'; +import ApiManager, { Registration } from './ApiManager'; + +export default class FireflyManager extends ApiManager { + askFirefly = (prompt: string = 'a realistic illustration of a cat coding') => { + const fetched = fetch('https://ims-na1.adobelogin.com/ims/token/v3', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: `grant_type=client_credentials&client_id=${process.env._CLIENT_FIREFLY_CLIENT_ID}&client_secret=${process.env._CLIENT_FIREFLY_SECRET}&scope=openid,AdobeID,session,additional_info,read_organizations,firefly_api,ff_apis`, + }) + .then(response => response.json()) + .then((data: { access_token: string }) => + fetch('https://firefly-api.adobe.io/v3/images/generate', { + method: 'POST', + headers: [ + ['Content-Type', 'application/json'], + ['Accept', 'application/json'], + ['x-api-key', process.env._CLIENT_FIREFLY_CLIENT_ID ?? ''], + ['Authorization', `Bearer ${data.access_token}`], + ], + body: `{ "prompt": "${prompt}" }`, + }) + .then(response => response.json().then(json => JSON.stringify((json.outputs?.[0] as { image: { url: string } })?.image))) + .catch(error => { + console.error('Error:', error); + return ''; + }) + ) + .catch(error => { + console.error('Error:', error); + return ''; + }); + return fetched; + }; + protected initialize(register: Registration): void { + register({ + method: Method.POST, + subscription: '/queryFireflyImage', + secureHandler: ({ req, res }) => + this.askFirefly(req.body.prompt).then(fire => + DashUploadUtils.UploadImage(JSON.parse(fire).url).then(info => { + if (info instanceof Error) _invalid(res, info.message); + else _success(res, info.accessPaths.agnostic.client); + }) + ), + }); + } +} diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts index 1e55a885a..032d13d43 100644 --- a/src/server/DashUploadUtils.ts +++ b/src/server/DashUploadUtils.ts @@ -369,7 +369,8 @@ export namespace DashUploadUtils { */ export const UploadInspectedImage = async (metadata: Upload.InspectionResults, filename: string, prefix = '', cleanUp = true): Promise => { const { requestable, source, ...remaining } = metadata; - const resolved = filename || `${prefix}upload_${Utils.GenerateGuid()}.${remaining.contentType.split('/')[1].toLowerCase()}`; + const dfltSuffix = remaining.contentType.split('/')[1].toLowerCase(); + const resolved = filename || `${prefix}upload_${Utils.GenerateGuid()}.${dfltSuffix === 'xml' ? 'jpg' : dfltSuffix}`; const { images } = Directory; const information: Upload.ImageInformation = { accessPaths: { diff --git a/src/server/index.ts b/src/server/index.ts index 88dbd232d..1f9af9ee0 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -7,6 +7,7 @@ import AssistantManager from './ApiManagers/AssistantManager'; import DataVizManager from './ApiManagers/DataVizManager'; import DeleteManager from './ApiManagers/DeleteManager'; import DownloadManager from './ApiManagers/DownloadManager'; +import FireflyManager from './ApiManagers/FireflyManager'; import GeneralGoogleManager from './ApiManagers/GeneralGoogleManager'; import SessionManager from './ApiManagers/SessionManager'; import UploadManager from './ApiManagers/UploadManager'; @@ -71,6 +72,7 @@ function routeSetter({ addSupervisedRoute, logRegistrationOutcome }: RouteManage new GeneralGoogleManager(), /* new GooglePhotosManager(), */ new DataVizManager(), new AssistantManager(), + new FireflyManager(), ]; // initialize API Managers @@ -112,7 +114,6 @@ function routeSetter({ addSupervisedRoute, logRegistrationOutcome }: RouteManage }); const serve: PublicHandler = ({ req, res }) => { - // eslint-disable-next-line new-cap const detector = new mobileDetect(req.headers['user-agent'] || ''); const filename = detector.mobile() !== null ? 'mobile/image.html' : 'index.html'; res.sendFile(path.join(__dirname, '../../deploy/' + filename)); diff --git a/webpack.config.js b/webpack.config.js index e1afc64e5..67417fb02 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ +/* eslint-disable @typescript-eslint/no-require-imports */ const path = require('path'); const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); @@ -36,7 +36,6 @@ function transferEnvironmentVariables() { } const resolvedClientSide = Object.keys(parsed).reduce((mapping, envKey) => { if (envKey.startsWith(prefix)) { - // eslint-disable-next-line mapping[`process.env.${envKey.replace(prefix, '')}`] = JSON.stringify(parsed[envKey]); } return mapping; @@ -112,7 +111,7 @@ module.exports = { test: /\.scss|css$/, exclude: /\.module\.scss$/i, use: [ - { loader: 'style-loader' }, // eslint-disable-next-line prettier/prettier + { loader: 'style-loader' }, // { loader: 'css-loader' }, { loader: 'sass-loader' }, ], @@ -127,7 +126,7 @@ module.exports = { { test: /\.module\.scss$/i, use: [ - { loader: 'style-loader' }, // eslint-disable-next-line prettier/prettier + { loader: 'style-loader' }, // { loader: 'css-loader', options: { modules: true } }, { loader: 'sass-loader' }, ], -- cgit v1.2.3-70-g09d2