mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-06-06 19:35:44 +02:00
Add support for @mentions
This commit is contained in:
parent
00bd476b76
commit
49500d845e
12 changed files with 671 additions and 291 deletions
|
|
@ -76,3 +76,36 @@ html, body {
|
|||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
|
||||
/* Add these styles alongside your other global styles */
|
||||
.ql-mention-list-container {
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 0.375rem;
|
||||
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
|
||||
background-color: white;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.ql-mention-list {
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
/* Styles for mentions in the editor */
|
||||
.ql-editor .mention {
|
||||
background-color: #e0f2fe; /* light blue bg */
|
||||
color: #1e40af; /* darker blue text */
|
||||
padding: 0.125rem 0.375rem;
|
||||
border-radius: 0.25rem;
|
||||
white-space: nowrap;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Style for the @ symbol */
|
||||
.ql-editor .mention .ql-mention-denotation-char {
|
||||
color: #1e40af; /* blue color for @ symbol */
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.ql-editor .mention .invalid {
|
||||
color: red;
|
||||
}
|
||||
|
|
@ -4,6 +4,9 @@ import { useClickAway } from "../../../hooks/use-click-away";
|
|||
import MarkdownContent from "./markdown-content";
|
||||
import clsx from "clsx";
|
||||
import { Label } from "./label";
|
||||
import dynamic from "next/dynamic";
|
||||
import { Match } from "./mentions_editor";
|
||||
const MentionsEditor = dynamic(() => import('./mentions_editor'), { ssr: false });
|
||||
|
||||
interface EditableFieldProps {
|
||||
value: string;
|
||||
|
|
@ -16,6 +19,8 @@ interface EditableFieldProps {
|
|||
className?: string;
|
||||
validate?: (value: string) => { valid: boolean; errorMessage?: string };
|
||||
light?: boolean;
|
||||
mentions?: boolean;
|
||||
mentionsAtValues?: Match[];
|
||||
}
|
||||
|
||||
export function EditableField({
|
||||
|
|
@ -29,6 +34,8 @@ export function EditableField({
|
|||
className = "flex flex-col gap-1",
|
||||
validate,
|
||||
light = false,
|
||||
mentions = false,
|
||||
mentionsAtValues = [],
|
||||
}: EditableFieldProps) {
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [localValue, setLocalValue] = useState(value);
|
||||
|
|
@ -115,38 +122,40 @@ export function EditableField({
|
|||
</Button>
|
||||
</div>}
|
||||
</div>}
|
||||
{isEditing ? (
|
||||
multiline ? (
|
||||
<Textarea
|
||||
{...commonProps}
|
||||
minRows={3}
|
||||
maxRows={20}
|
||||
/>
|
||||
) : (
|
||||
<Input {...commonProps} />
|
||||
)
|
||||
) : (
|
||||
{isEditing ? <>
|
||||
{mentions && <MentionsEditor
|
||||
atValues={mentionsAtValues}
|
||||
value={value}
|
||||
placeholder={placeholder}
|
||||
onValueChange={setLocalValue}
|
||||
/>}
|
||||
{multiline && !mentions && <Textarea
|
||||
{...commonProps}
|
||||
minRows={3}
|
||||
maxRows={20}
|
||||
/>}
|
||||
{!multiline && <Input {...commonProps} />}
|
||||
</> : (
|
||||
<div
|
||||
onClick={() => !locked && setIsEditing(true)}
|
||||
className={clsx("text-sm px-2 py-1 rounded-md", {
|
||||
"bg-blue-50": markdown && !locked,
|
||||
"bg-gray-50": light,
|
||||
"bg-gray-50": (markdown && !locked) || light,
|
||||
"hover:bg-blue-50 cursor-pointer": light && !locked,
|
||||
"hover:bg-gray-50 cursor-pointer": !light && !locked,
|
||||
"hover:bg-gray-100 cursor-pointer": !light && !locked,
|
||||
"cursor-default": locked,
|
||||
})}
|
||||
>
|
||||
{value ? (<>
|
||||
{markdown && <div className="max-h-[420px] overflow-y-auto">
|
||||
<MarkdownContent content={value} />
|
||||
<MarkdownContent content={value} atValues={mentionsAtValues} />
|
||||
</div>}
|
||||
{!markdown && <div className={`${multiline ? 'whitespace-pre-wrap max-h-[420px] overflow-y-auto' : 'flex items-center'}`}>
|
||||
{value}
|
||||
<MarkdownContent content={value} atValues={mentionsAtValues} />
|
||||
</div>}
|
||||
</>) : (
|
||||
<>
|
||||
{markdown && <div className="max-h-[420px] overflow-y-auto text-gray-400 italic">
|
||||
<MarkdownContent content={placeholder} />
|
||||
<MarkdownContent content={placeholder} atValues={mentionsAtValues} />
|
||||
</div>}
|
||||
{!markdown && <span className="text-gray-400 italic">{placeholder}</span>}
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,14 @@
|
|||
import Markdown from 'react-markdown'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
import { Match } from './mentions_editor';
|
||||
|
||||
export default function MarkdownContent({ content }: { content: string }) {
|
||||
export default function MarkdownContent({
|
||||
content,
|
||||
atValues = []
|
||||
}: {
|
||||
content: string;
|
||||
atValues?: Match[];
|
||||
}) {
|
||||
return <Markdown
|
||||
className="overflow-auto break-words"
|
||||
remarkPlugins={[remarkGfm]}
|
||||
|
|
@ -49,8 +56,46 @@ export default function MarkdownContent({ content }: { content: string }) {
|
|||
return <blockquote className='py-2 bg-gray-200 px-1'>{children}</blockquote>;
|
||||
},
|
||||
a(props) {
|
||||
const { children, className, node, ...rest } = props
|
||||
return <a className="inline-flex items-center gap-1" target="_blank" {...rest} >
|
||||
const { children, href, className, node, ...rest } = props;
|
||||
|
||||
// If this is a mention link, render it with mention styling
|
||||
if (href === '#mention') {
|
||||
let label: string = '';
|
||||
// Check if children is an array and get the first text element
|
||||
if (Array.isArray(children) && children.length > 0) {
|
||||
const text = children[0];
|
||||
if (typeof text === 'string') {
|
||||
const parts = text.split('@');
|
||||
if (parts.length === 2) {
|
||||
label = parts[1];
|
||||
}
|
||||
}
|
||||
} else if (typeof children === 'string') {
|
||||
// Fallback for direct string children
|
||||
const parts = children.split('@');
|
||||
if (parts.length === 2) {
|
||||
label = parts[1];
|
||||
}
|
||||
}
|
||||
|
||||
// check if the the mention is valid
|
||||
const invalid = !atValues.some(atValue => atValue.id === label);
|
||||
if (atValues.length > 0 && invalid) {
|
||||
return (
|
||||
<span className="inline-block bg-[#e0f2fe] text-[red] px-1.5 py-0.5 rounded whitespace-nowrap">
|
||||
@{label} (!)
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<span className="inline-block bg-[#e0f2fe] text-[#1e40af] px-1.5 py-0.5 rounded whitespace-nowrap">
|
||||
@{label}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
// Otherwise render normal link (your existing link component)
|
||||
return <a className="inline-flex items-center gap-1" target="_blank" href={href} {...rest} >
|
||||
<span className='underline'>
|
||||
{children}
|
||||
</span>
|
||||
|
|
|
|||
197
apps/rowboat/app/lib/components/mentions_editor.tsx
Normal file
197
apps/rowboat/app/lib/components/mentions_editor.tsx
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
"use client"
|
||||
import { useEffect, useRef } from 'react';
|
||||
import Quill, { Delta, Op } from 'quill';
|
||||
import { Mention, MentionBlot, MentionBlotData } from "quill-mention";
|
||||
import "quill/dist/quill.snow.css";
|
||||
import { CopyIcon } from 'lucide-react';
|
||||
import { CopyButton } from './copy-button';
|
||||
|
||||
export type Match = {
|
||||
id: string;
|
||||
value: string;
|
||||
invalid?: boolean;
|
||||
[key: string]: string | boolean | undefined;
|
||||
};
|
||||
|
||||
class CustomMentionBlot extends MentionBlot {
|
||||
static render(data: any) {
|
||||
const element = document.createElement('span');
|
||||
element.className = data.invalid ? 'invalid' : '';
|
||||
element.textContent = data.invalid ? `${data.value} (!)` : data.value;
|
||||
return element;
|
||||
}
|
||||
}
|
||||
|
||||
Quill.register('blots/mention', CustomMentionBlot);
|
||||
Quill.register('modules/mention', Mention);
|
||||
|
||||
function markdownToParts(markdown: string, atValues: Match[]): (string | Match)[] {
|
||||
// Regex match for pattern [@type:name](#type:something) where type is tool/prompt/agent
|
||||
const mentionRegex = /\[@(tool|prompt|agent):([^\]]+)\]\(#mention\)/g;
|
||||
const parts: (string | Match)[] = [];
|
||||
|
||||
let lastIndex = 0;
|
||||
let match;
|
||||
|
||||
// Find all matches and build the parts array
|
||||
while ((match = mentionRegex.exec(markdown)) !== null) {
|
||||
// Add text before the match if there is any
|
||||
if (match.index > lastIndex) {
|
||||
parts.push(markdown.slice(lastIndex, match.index));
|
||||
}
|
||||
|
||||
// check if the match is valid
|
||||
const matchValue = `${match[1]}:${match[2]}`;
|
||||
const isInvalid = !atValues.some(atValue => atValue.id === matchValue);
|
||||
|
||||
// parse the match into a mention
|
||||
parts.push({
|
||||
id: `${match[1]}:${match[2]}`,
|
||||
value: `${match[1]}:${match[2]}`,
|
||||
invalid: isInvalid,
|
||||
});
|
||||
|
||||
lastIndex = match.index + match[0].length;
|
||||
}
|
||||
|
||||
// Add any remaining text after the last match
|
||||
if (lastIndex < markdown.length) {
|
||||
parts.push(markdown.slice(lastIndex));
|
||||
}
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
function insertPartsIntoQuill(quill: Quill, parts: (string | Match)[]) {
|
||||
let index = 0;
|
||||
for (const part of parts) {
|
||||
if (typeof part === 'string') {
|
||||
quill.insertText(index, part, Quill.sources.SILENT);
|
||||
index += part.length;
|
||||
} else {
|
||||
quill.insertEmbed(index, 'mention', {
|
||||
id: part.id,
|
||||
value: part.value,
|
||||
denotationChar: '@',
|
||||
invalid: part.invalid,
|
||||
}, Quill.sources.SILENT);
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default function MentionEditor({
|
||||
atValues,
|
||||
value,
|
||||
placeholder,
|
||||
onValueChange,
|
||||
}: {
|
||||
atValues: Match[];
|
||||
value: string;
|
||||
placeholder?: string;
|
||||
onValueChange?: (value: string) => void;
|
||||
}) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const quillRef = useRef<Quill | null>(null);
|
||||
|
||||
function getMarkdown(): string {
|
||||
if (!quillRef.current) {
|
||||
return "";
|
||||
}
|
||||
// generate markdown representation of content
|
||||
const markdown = quillRef.current.getContents().map((op) => {
|
||||
if (op.insert && typeof op.insert === 'object' && 'mention' in op.insert) {
|
||||
const mentionOp = op.insert as { mention: Match };
|
||||
return `[@${mentionOp.mention.id}](#mention)`;
|
||||
}
|
||||
return op.insert;
|
||||
}).join('');
|
||||
return markdown;
|
||||
}
|
||||
|
||||
function copyHandler() {
|
||||
if (!quillRef.current) {
|
||||
return;
|
||||
}
|
||||
navigator.clipboard.writeText(getMarkdown());
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!containerRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
function load() {
|
||||
if (!containerRef.current) {
|
||||
return;
|
||||
}
|
||||
const quill = new Quill(containerRef.current, {
|
||||
theme: 'snow',
|
||||
formats: ["mention"],
|
||||
placeholder,
|
||||
modules: {
|
||||
toolbar: false,
|
||||
mention: {
|
||||
allowedChars: /^[A-Za-z0-9_]*$/,
|
||||
mentionDenotationChars: ["@"],
|
||||
showDenotationChar: true,
|
||||
source: async function (searchTerm: string, renderList: (values: Match[], searchTerm: string) => void) {
|
||||
if (searchTerm.length === 0) {
|
||||
renderList(atValues, searchTerm);
|
||||
} else {
|
||||
const matches = [];
|
||||
for (let i = 0; i < atValues.length; i++) {
|
||||
if (
|
||||
atValues[i].value.toLowerCase().indexOf(searchTerm.toLowerCase()) !== -1
|
||||
) {
|
||||
matches.push(atValues[i]);
|
||||
}
|
||||
}
|
||||
renderList(matches, searchTerm);
|
||||
}
|
||||
},
|
||||
renderItem: (item: Match) => {
|
||||
const div = document.createElement('div');
|
||||
div.className = "px-2 py-1 bg-white text-blue-800 hover:bg-blue-100 cursor-pointer";
|
||||
div.textContent = item.id;
|
||||
return div;
|
||||
},
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// clear the quill contents
|
||||
quill.setContents([]);
|
||||
|
||||
// convert the markdown to parts
|
||||
const parts = markdownToParts(value, atValues);
|
||||
insertPartsIntoQuill(quill, parts);
|
||||
|
||||
quill.on(Quill.events.TEXT_CHANGE, (delta: Delta, oldDelta: Delta, source: string) => {
|
||||
if (onValueChange) {
|
||||
onValueChange(getMarkdown());
|
||||
}
|
||||
});
|
||||
quillRef.current = quill;
|
||||
}
|
||||
|
||||
load();
|
||||
|
||||
return () => {
|
||||
if (quillRef.current) {
|
||||
quillRef.current.off(Quill.events.TEXT_CHANGE);
|
||||
}
|
||||
}
|
||||
}, [atValues, onValueChange, placeholder, value]);
|
||||
|
||||
return <div className="relative">
|
||||
<button className="absolute top-2 right-2 z-10">
|
||||
<CopyButton
|
||||
onCopy={copyHandler}
|
||||
label="Copy"
|
||||
successLabel="Copied!"
|
||||
/>
|
||||
</button>
|
||||
<div ref={containerRef} />
|
||||
</div>;
|
||||
}
|
||||
|
|
@ -37,13 +37,10 @@ You are an helpful customer support assistant
|
|||
|
||||
❌ Don'ts:
|
||||
- don't ask user any other detail than email`,
|
||||
prompts: [],
|
||||
tools: [],
|
||||
model: "gpt-4o-mini",
|
||||
toggleAble: true,
|
||||
ragReturnType: "chunks",
|
||||
ragK: 3,
|
||||
connectedAgents: [],
|
||||
controlType: "retain",
|
||||
},
|
||||
{
|
||||
|
|
@ -51,14 +48,11 @@ You are an helpful customer support assistant
|
|||
type: "post_process",
|
||||
description: "",
|
||||
instructions: "Ensure that the agent response is terse and to the point.",
|
||||
prompts: [],
|
||||
tools: [],
|
||||
model: "gpt-4o-mini",
|
||||
locked: true,
|
||||
global: true,
|
||||
ragReturnType: "chunks",
|
||||
ragK: 3,
|
||||
connectedAgents: [],
|
||||
controlType: "retain",
|
||||
},
|
||||
{
|
||||
|
|
@ -66,14 +60,11 @@ You are an helpful customer support assistant
|
|||
type: "escalation",
|
||||
description: "",
|
||||
instructions: "Get the user's contact information and let them know that their request has been escalated.",
|
||||
prompts: [],
|
||||
tools: [],
|
||||
model: "gpt-4o-mini",
|
||||
locked: true,
|
||||
toggleAble: false,
|
||||
ragReturnType: "chunks",
|
||||
ragK: 3,
|
||||
connectedAgents: [],
|
||||
controlType: "retain",
|
||||
},
|
||||
],
|
||||
|
|
@ -98,31 +89,23 @@ You are an helpful customer support assistant
|
|||
"type": "post_process",
|
||||
"description": "Minimal post processing",
|
||||
"instructions": "- Avoid adding any additional phrases such as 'Let me know if you need anything else!' or similar.",
|
||||
"prompts": [],
|
||||
"tools": [],
|
||||
"model": "gpt-4o",
|
||||
"locked": true,
|
||||
"global": true,
|
||||
"ragReturnType": "chunks",
|
||||
"ragK": 3,
|
||||
"connectedAgents": [],
|
||||
"controlType": "relinquish_to_parent"
|
||||
},
|
||||
{
|
||||
"name": "Escalation",
|
||||
"type": "escalation",
|
||||
"description": "Escalation agent",
|
||||
"instructions": "## 🧑💼 Role:\nHandle scenarios where the system needs to escalate a request to a human representative.\n\n---\n## ⚙️ Steps to Follow:\n1. Inform the user that their details are being escalated to a human agent.\n2. Call the 'close_chat' tool to close the chat session.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Escalating issues to human agents\n- Closing chat sessions\n\n❌ Out of Scope:\n- Handling queries that do not require escalation\n- Providing solutions without escalation\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Clearly inform the user about the escalation.\n- Ensure the chat is closed after escalation.\n\n🚫 Don'ts:\n- Attempt to resolve issues without escalation.\n- Leave the chat open after informing the user about escalation.",
|
||||
"prompts": [],
|
||||
"tools": [
|
||||
"close_chat"
|
||||
],
|
||||
"instructions": "## 🧑💼 Role:\nHandle scenarios where the system needs to escalate a request to a human representative.\n\n---\n## ⚙️ Steps to Follow:\n1. Inform the user that their details are being escalated to a human agent.\n2. Call {tool:close_chat} to close the chat session.\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Escalating issues to human agents\n- Closing chat sessions\n\n❌ Out of Scope:\n- Handling queries that do not require escalation\n- Providing solutions without escalation\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Clearly inform the user about the escalation.\n- Ensure the chat is closed after escalation.\n\n🚫 Don'ts:\n- Attempt to resolve issues without escalation.\n- Leave the chat open after informing the user about escalation.",
|
||||
"model": "gpt-4o",
|
||||
"locked": true,
|
||||
"toggleAble": false,
|
||||
"ragReturnType": "chunks",
|
||||
"ragK": 3,
|
||||
"connectedAgents": [],
|
||||
"controlType": "retain",
|
||||
"examples": "- **User** : I need help with something urgent.\n - **Agent response**: Your request is being escalated to a human agent.\n - **Agent actions**: Call close_chat\n\n- **User** : Can you escalate this issue?\n - **Agent response**: Your details are being escalated to a human agent.\n - **Agent actions**: Call close_chat"
|
||||
},
|
||||
|
|
@ -131,19 +114,12 @@ You are an helpful customer support assistant
|
|||
"type": "conversation",
|
||||
"description": "Agent to check the user's account balance.",
|
||||
"disabled": false,
|
||||
"instructions": "## 🧑💼 Role:\nAssist users in checking their account balance.\n\n---\n## ⚙️ Steps to Follow:\n1. Greet them with 'Hello, welcome to RowBoat Bank.'\n2. If the user hasn't provided their request yet, ask 'How may I help you today?'\n3. If the request is related to checking account balance, proceed with the following steps:\n - Ask the user to confirm the last 4 digits of their debit card.\n - Use the 'get_account_balance' tool to fetch the account balance.\n - Inform the user of their account balance based on the output of 'get_account_balance'\n4. If the user requests to talk to a human, call the 'Escalation' agent.\n5. If the request is not related to checking account balance, inform the user: 'Sorry, I can only help you with account balance.'\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Fetching and providing account balance\n- Escalating to human agents upon request\n\n❌ Out of Scope:\n- Handling queries unrelated to account balance\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Always call get_account_balance to fetch the user's account balance\n- Be clear and concise in communication.\n- Call the Escalation agent if the user requests to speak with a human.\n\n🚫 Don'ts:\n- Extend the conversation beyond account balance checking.",
|
||||
"prompts": [],
|
||||
"tools": [
|
||||
"get_account_balance"
|
||||
],
|
||||
"instructions": "## 🧑💼 Role:\nAssist users in checking their account balance.\n\n---\n## ⚙️ Steps to Follow:\n1. Greet them with 'Hello, welcome to RowBoat Bank.'\n2. If the user hasn't provided their request yet, ask 'How may I help you today?'\n3. If the request is related to checking account balance, proceed with the following steps:\n - Ask the user to confirm the last 4 digits of their debit card.\n - Use {tool:get_account_balance} to fetch the account balance.\n - Inform the user of their account balance based on the output of 'get_account_balance'\n4. If the user requests to talk to a human, call {agent:Escalation}.\n5. If the request is not related to checking account balance, inform the user: 'Sorry, I can only help you with account balance.'\n\n---\n## 🎯 Scope:\n✅ In Scope:\n- Fetching and providing account balance\n- Escalating to human agents upon request\n\n❌ Out of Scope:\n- Handling queries unrelated to account balance\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Always call get_account_balance to fetch the user's account balance\n- Be clear and concise in communication.\n- Call the Escalation agent if the user requests to speak with a human.\n\n🚫 Don'ts:\n- Extend the conversation beyond account balance checking.",
|
||||
"model": "gpt-4o",
|
||||
"locked": false,
|
||||
"toggleAble": true,
|
||||
"ragReturnType": "chunks",
|
||||
"ragK": 3,
|
||||
"connectedAgents": [
|
||||
"Escalation"
|
||||
],
|
||||
"controlType": "retain",
|
||||
"examples": "- **User** : Can you tell me my account balance?\n - **Agent response**: Sure, can you confirm the last 4 digits of your debit card?\n - **Agent actions**: Call get_account_balance\n\n- **User** : The last 4 digits are 1234.\n - **Agent actions**: Call get_account_balance\n - **Agent response**: Your account balance is $500.\n\n- **User** : What's my balance?\n - **Agent actions**: Call get_account_balance\n - **Agent response**: Your account balance is $500.\n\n- **User** : I need help with my credit card.\n - **Agent response**: Sorry, I can only help you with account balance.\n\n- **User** : I need to know my balance, please.\n - **Agent response**: Of course, please provide the last 4 digits of your debit card.\n - **Agent actions**: Call get_account_balance\n\n- **User** : How much money do I have in my account?\n - **Agent response**: Please confirm the last 4 digits of your debit card to proceed.\n - **Agent actions**: Call get_account_balance\n\n- **User** : My card ends in 5678.\n - **Agent actions**: Call get_account_balance\n - **Agent response**: Your account balance is $500.\n\n- **User** : Can you check my balance?\n - **Agent response**: Certainly, could you provide the last 4 digits of your debit card?\n - **Agent actions**: Call get_account_balance\n\n- **User** : I want to know my account balance.\n - **Agent response**: Sure, please confirm the last 4 digits of your debit card.\n - **Agent actions**: Call get_account_balance\n\n- **User** : The last 4 digits are 9876.\n - **Agent actions**: Call get_account_balance\n - **Agent response**: Your account balance is $500.\n\n- **User** : Hi\n - **Agent response**: Hello, welcome to RowBoat Bank. How may I help you today?\n\n- **User** : Hi, I want to check my balance\n - **Agent response**: Hello, welcome to RowBoat Bank. Sure, can you confirm the last 4 digits of your debit card?\n - **Agent actions**: Call get_account_balance\n\n- **User** : Who are you?\n - **Agent response**: I'm a friendly RowBoat Bank assistant, how can I help you today?\n\n- **User** : Who built you?\n - **Agent response**: I was created by the RowBoat Bank team to assist you with your banking needs. How may I help you today?\n\n- **User** : How are you?\n - **Agent response**: I'm here to help you with your banking queries. How can I assist you today?\n\n- **User** : I want to talk to a human.\n - **Agent actions**: Call Escalation\n - **Agent response**: Your request is being escalated to a human agent."
|
||||
}
|
||||
|
|
@ -205,21 +181,45 @@ You are an helpful customer support assistant
|
|||
"name": "Main agent",
|
||||
"type": "conversation",
|
||||
"disabled": false,
|
||||
"instructions": "## 🧑💼 Role:\nYou are a customer support agent for ScootUp scooters. Your main responsibility is to orchestrate conversations and delegate them to specialized worker agents for efficient query handling.\n\n---\n## ⚙️ Steps to Follow:\n1. Engage in basic small talk to build rapport. Stick to the specified examples for such interactions.\n2. When a specific query arises, pass control to the relevant worker agent immediately.\n3. For follow-up questions on the same topic, direct them back to the same worker agent who handled the initial query.\n4. If the query is out-of-scope, call the Escalation agent.\n\n---\n## 🎯 Scope:\n\n✅ In Scope:\n- Initial query handling and passing control to specific agents\n\n❌ Out of Scope:\n- Detailed product or service resolutions\n- Technical troubleshooting or detailed assistance beyond initial query reading\n- General knowledge related questions\n\n---\n## 📋 Guidelines:\n✔️ Dos:\n- Ensure smooth conversational flow while transferring queries to respective agents.\n- Engage only in light rapport-building or disambiguating discussions.\n\n🚫 Don'ts:\n- Avoid engaging in detailed support discussions or troubleshooting at length.\n- Do not address queries beyond initial understanding beyond relaying them to appropriate agents.\n- Do not answer out-of-scope questions; instead, direct them to the Escalation agent.\n- Do not talk about other agents or about transferring to them.",
|
||||
"prompts": [
|
||||
"self_support_prompt",
|
||||
"Style prompt"
|
||||
],
|
||||
"tools": [],
|
||||
"instructions": `## 🧑💼 Role:
|
||||
You are a customer support agent for ScootUp scooters. Your main responsibility is to orchestrate conversations and delegate them to specialized worker agents for efficient query handling.
|
||||
|
||||
---
|
||||
## ⚙️ Steps to Follow:
|
||||
1. Engage in basic small talk to build rapport. Stick to the specified examples for such interactions.
|
||||
2. When a specific query arises, pass control to the relevant worker agent immediately, such as {agent:Product info agent} or {agent:Delivery info agent}.
|
||||
3. For follow-up questions on the same topic, direct them back to the same worker agent who handled the initial query.
|
||||
4. If the query is out-of-scope, call {agent:Escalation}.
|
||||
|
||||
---
|
||||
## 🎯 Scope:
|
||||
|
||||
✅ In Scope:
|
||||
- Initial query handling and passing control to specific agents
|
||||
|
||||
❌ Out of Scope:
|
||||
- Detailed product or service resolutions
|
||||
- Technical troubleshooting or detailed assistance beyond initial query reading
|
||||
- General knowledge related questions
|
||||
|
||||
---
|
||||
## 📋 Guidelines:
|
||||
✔️ Dos:
|
||||
- Ensure smooth conversational flow while transferring queries to respective agents.
|
||||
- Engage only in light rapport-building or disambiguating discussions.
|
||||
|
||||
🚫 Don'ts:
|
||||
- Avoid engaging in detailed support discussions or troubleshooting at length.
|
||||
- Do not address queries beyond initial understanding beyond relaying them to appropriate agents.
|
||||
- Do not answer out-of-scope questions; instead, direct them to the {agent:Escalation}.
|
||||
- Do not talk about other agents or about transferring to them.
|
||||
|
||||
Follow {prompt:Style prompt}. Also keep in mind {prompt:self_support_prompt}.`,
|
||||
"model": "gpt-4o",
|
||||
"locked": false,
|
||||
"toggleAble": true,
|
||||
"ragReturnType": "chunks",
|
||||
"ragK": 3,
|
||||
"connectedAgents": [
|
||||
"Product info agent",
|
||||
"Delivery info agent"
|
||||
],
|
||||
"ragDataSources": [],
|
||||
"description": "The Main agent orchestrates interactions between various specialized worker agents to ensure efficient handling of user queries and support needs.",
|
||||
"controlType": "retain",
|
||||
|
|
@ -230,16 +230,11 @@ You are an helpful customer support assistant
|
|||
"type": "post_process",
|
||||
"disabled": false,
|
||||
"instructions": "- Extract the response_to_user field from the provided structured JSON and ensure that this is the only content you use for the final output.\n- Ensure that the agent response covers all the details the user asked for.\n- Use bullet points only when providing lengthy or detailed information that benefits from such formatting.\n- Generally, aim to keep responses concise and focused on key details. You can summarize the info to around 5 sentences.\n- Focus specifically on the response_to_user field in its input.",
|
||||
"prompts": [
|
||||
"self_support_prompt"
|
||||
],
|
||||
"tools": [],
|
||||
"model": "gpt-4o",
|
||||
"locked": true,
|
||||
"toggleAble": true,
|
||||
"ragReturnType": "chunks",
|
||||
"ragK": 3,
|
||||
"connectedAgents": [],
|
||||
"ragDataSources": [],
|
||||
"description": "",
|
||||
"controlType": "retain",
|
||||
|
|
@ -249,21 +244,11 @@ You are an helpful customer support assistant
|
|||
"type": "conversation",
|
||||
"disabled": false,
|
||||
"instructions": "🧑💼 Role:\nYou are a product information agent for ScootUp scooters. Your job is to search for the right article and answer questions strictly based on the article about ScootUp products. Feel free to ask the user clarification questions if needed.\n\n---\n\n📜 Instructions:\n- Call retrieve_snippet to get the relevant information and answer questions strictly based on that\n\n✅ In Scope:\n- Answer questions strictly about ScootUp product information.\n\n❌ Out of Scope:\n- Questions about delivery, returns, and subscriptions.\n- Any topic unrelated to ScootUp products.\n- If a question is out of scope, call give_up_control and do not attempt to answer it.\n\n---\n## 📋 Guidelines:\n\n✔️ Dos:\n- Stick to the facts provided in the articles.\n- Provide complete and direct answers to the user's questions.\n\n---\n\n🚫 Don'ts:\n- Do not partially answer questions or direct users to a URL for more information.\n- Do not provide information outside of the given context.\n",
|
||||
"prompts": [
|
||||
"structured_output",
|
||||
"rag_article_prompt",
|
||||
"self_support_prompt",
|
||||
"Style prompt"
|
||||
],
|
||||
"tools": [
|
||||
"retrieve_snippet"
|
||||
],
|
||||
"model": "gpt-4o",
|
||||
"locked": false,
|
||||
"toggleAble": true,
|
||||
"ragReturnType": "content",
|
||||
"ragK": 3,
|
||||
"connectedAgents": [],
|
||||
"ragDataSources": [],
|
||||
"description": "You assist with product-related questions by retrieving relevant articles and information.",
|
||||
"controlType": "relinquish_to_parent",
|
||||
|
|
@ -274,23 +259,11 @@ You are an helpful customer support assistant
|
|||
"type": "conversation",
|
||||
"disabled": false,
|
||||
"instructions": "## 🧑💼 Role:\n\nYou are responsible for providing delivery information to the user.\n\n---\n\n## ⚙️ Steps to Follow:\n\n1. Check if the orderId is available:\n - If not available, politely ask the user for their orderId.\n - Once the user provides the orderId, call the 'validations' tool to check if it's valid.\n - If 'validated', proceed to Step 2.\n - If 'not validated', ask the user to re-check or provide a corrected orderId. Provide a reason on why it is invalid only if the validations tool returns that information.\n2. Fetch the delivery details using the function: get_shipping_details once the valid orderId is available.\n3. Answer the user's question based on the fetched delivery details.\n4. If the user asks a general delivery question, call retrieve_snippet and provide an answer only based on it.\n5. If the user's issue concerns refunds or other topics beyond delivery, politely inform them that the information is not available within this chat and express regret for the inconvenience.\n\n---\n## 🎯 Scope\n\n✅ In Scope:\n- Questions about delivery status, shipping timelines, and delivery processes.\n- Generic delivery/shipping-related questions where answers can be sourced from articles.\n\n❌ Out of Scope:\n- Questions unrelated to delivery or shipping.\n- Questions about product features, returns, subscriptions, or promotions.\n- If a question is out of scope, politely inform the user and avoid providing an answer.\n\n---\n\n## 📋 Guidelines\n\n✔️ Dos:\n- Use validations to verify orderId.\n- Use get_shipping_details to fetch accurate delivery information.\n- Promptly ask for orderId if not available.\n- Provide complete and clear answers based on the delivery details.\n- For generic delivery questions, strictly refer to retrieved snippets. Stick to factual information when answering.\n\n🚫 Don'ts:\n- Do not mention or describe how you are fetching the information behind the scenes.\n- Do not provide answers without fetching delivery details when required.\n- Do not leave the user with partial information.\n- Refrain from phrases like 'please contact support'; instead, relay information limitations gracefully.",
|
||||
"prompts": [
|
||||
"structured_output",
|
||||
"rag_article_prompt",
|
||||
"self_support_prompt",
|
||||
"Style prompt"
|
||||
],
|
||||
"tools": [
|
||||
"get_delivery_details",
|
||||
"retrieve_snippet",
|
||||
"validate_entity"
|
||||
],
|
||||
"model": "gpt-4o",
|
||||
"locked": false,
|
||||
"toggleAble": true,
|
||||
"ragReturnType": "chunks",
|
||||
"ragK": 3,
|
||||
"connectedAgents": [],
|
||||
"ragDataSources": [],
|
||||
"description": "You are responsible for providing accurate delivery status and shipping details for orders.",
|
||||
"controlType": "retain",
|
||||
|
|
@ -302,17 +275,11 @@ You are an helpful customer support assistant
|
|||
"description": "Handles situations where user queries cannot be addressed by existing agents and require escalation.",
|
||||
"disabled": false,
|
||||
"instructions": "\n## 🧑💼 Role:\nYou handle situations where escalation is necessary because the current agents cannot fulfill the user's request.\n\n---\n\n## ⚙️ Steps to Follow:\n1. Tell the user you will setup a callback with the team. \n\n---\n\n## 🎯 Scope:\n✅ In Scope:\n- Escalating unresolvable queries, notifying users of escalation, and logging escalation activities.\n\n❌ Out of Scope:\n- Providing responses to general or specialized topics already handled by other agents.\n\n---\n\n## 📋 Guidelines:\n✔️ Dos: \n- Respond empathetically to the user, inform them about the escalation, and ensure necessary actions are taken.\n\n🚫 Don'ts: \n- Do not attempt to resolve issues not within your scope.\n",
|
||||
"prompts": [
|
||||
"self_support_prompt",
|
||||
"Style prompt"
|
||||
],
|
||||
"tools": [],
|
||||
"model": "gpt-4o",
|
||||
"locked": false,
|
||||
"toggleAble": true,
|
||||
"ragReturnType": "chunks",
|
||||
"ragK": 3,
|
||||
"connectedAgents": [],
|
||||
"controlType": "retain",
|
||||
"examples": "- **User**: \"I've tried everything, but no one can resolve my issue. I demand further assistance!\"\n - **Agent actions**: N/A\n - **Agent response**: \"I'm sorry about your experience. I'll set up a callback with our support team so we can thoroughly resolve your issue. We appreciate your patience, and we'll be in touch soon.\"\n\n- **User**: \"Could you escalate this? I've been waiting for days without a resolution.\"\n - **Agent actions**: N/A\n - **Agent response**: \"I'm sorry about your experience. I'll set up a callback with our support team so we can thoroughly resolve your issue. We appreciate your patience, and we'll be in touch soon.\"\n\n- **User**: \"I want a manager to handle my case personally. This is unacceptable.\"\n - **Agent actions**: N/A\n - **Agent response**: \"I'm sorry about your experience. I'll set up a callback with our support team so we can thoroughly resolve your issue. We appreciate your patience, and we'll be in touch soon.\"\n\n- **User**: \"None of the agents so far have fixed my problem. How do I escalate this?\"\n - **Agent actions**: N/A\n - **Agent response**: \"I'm sorry about your experience. I'll set up a callback with our support team so we can thoroughly resolve your issue. We appreciate your patience, and we'll be in touch soon.\"\n\n- **User**: \"I'm tired of repeating myself. I need upper management involved now.\"\n - **Agent actions**: N/A\n - **Agent response**: \"I'm sorry about your experience. I'll set up a callback with our support team so we can thoroughly resolve your issue. We appreciate your patience, and we'll be in touch soon.\""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { z } from "zod";
|
||||
import { Workflow, WorkflowAgent, WorkflowPrompt, WorkflowTool } from "./workflow_types";
|
||||
import { ConnectedEntity, sanitizeTextWithMentions, Workflow, WorkflowAgent, WorkflowPrompt, WorkflowTool } from "./workflow_types";
|
||||
import { apiV1 } from "rowboat-shared";
|
||||
import { ApiMessage } from "./types";
|
||||
|
||||
|
|
@ -21,11 +21,12 @@ export const AgenticAPIChatMessage = z.object({
|
|||
z.literal('internal'),
|
||||
z.literal('external'),
|
||||
]).optional(),
|
||||
});export const AgenticAPIAgent = WorkflowAgent
|
||||
});
|
||||
|
||||
export const AgenticAPIAgent = WorkflowAgent
|
||||
.omit({
|
||||
disabled: true,
|
||||
examples: true,
|
||||
prompts: true,
|
||||
locked: true,
|
||||
toggleAble: true,
|
||||
global: true,
|
||||
|
|
@ -35,12 +36,18 @@ export const AgenticAPIChatMessage = z.object({
|
|||
})
|
||||
.extend({
|
||||
hasRagSources: z.boolean().default(false).optional(),
|
||||
tools: z.array(z.string()),
|
||||
prompts: z.array(z.string()),
|
||||
connectedAgents: z.array(z.string()),
|
||||
});
|
||||
|
||||
export const AgenticAPIPrompt = WorkflowPrompt;
|
||||
|
||||
export const AgenticAPITool = WorkflowTool.omit({
|
||||
mockInPlayground: true,
|
||||
autoSubmitMockedResponse: true,
|
||||
});
|
||||
|
||||
export const AgenticAPIChatRequest = z.object({
|
||||
messages: z.array(AgenticAPIChatMessage),
|
||||
state: z.unknown(),
|
||||
|
|
@ -49,10 +56,12 @@ export const AgenticAPIChatRequest = z.object({
|
|||
prompts: z.array(WorkflowPrompt),
|
||||
startAgent: z.string(),
|
||||
});
|
||||
|
||||
export const AgenticAPIChatResponse = z.object({
|
||||
messages: z.array(AgenticAPIChatMessage),
|
||||
state: z.unknown(),
|
||||
});
|
||||
|
||||
export function convertWorkflowToAgenticAPI(workflow: z.infer<typeof Workflow>): {
|
||||
agents: z.infer<typeof AgenticAPIAgent>[];
|
||||
tools: z.infer<typeof AgenticAPITool>[];
|
||||
|
|
@ -62,30 +71,41 @@ export function convertWorkflowToAgenticAPI(workflow: z.infer<typeof Workflow>):
|
|||
return {
|
||||
agents: workflow.agents
|
||||
.filter(agent => !agent.disabled)
|
||||
.map(agent => ({
|
||||
name: agent.name,
|
||||
type: agent.type,
|
||||
description: agent.description,
|
||||
instructions: agent.instructions +
|
||||
'\n\n' + agent.prompts.map(prompt => workflow.prompts.find(p => p.name === prompt)?.prompt
|
||||
).join('\n\n') +
|
||||
(agent.examples ? '\n\n# Examples\n' + agent.examples : ''),
|
||||
tools: agent.tools,
|
||||
model: agent.model,
|
||||
hasRagSources: agent.ragDataSources ? agent.ragDataSources.length > 0 : false,
|
||||
connectedAgents: agent.connectedAgents,
|
||||
controlType: agent.controlType,
|
||||
})),
|
||||
.map(agent => {
|
||||
const { sanitized, entities } = sanitizeTextWithMentions(agent.instructions, workflow);
|
||||
|
||||
const agenticAgent: z.infer<typeof AgenticAPIAgent> = {
|
||||
name: agent.name,
|
||||
type: agent.type,
|
||||
description: agent.description,
|
||||
instructions: sanitized,
|
||||
model: agent.model,
|
||||
hasRagSources: agent.ragDataSources ? agent.ragDataSources.length > 0 : false,
|
||||
controlType: agent.controlType,
|
||||
tools: entities.filter(e => e.type == 'tool').map(e => e.name),
|
||||
prompts: entities.filter(e => e.type == 'prompt').map(e => e.name),
|
||||
connectedAgents: entities.filter(e => e.type === 'agent').map(e => e.name),
|
||||
};
|
||||
return agenticAgent;
|
||||
}),
|
||||
tools: workflow.tools.map(tool => {
|
||||
const { mockInPlayground, autoSubmitMockedResponse, ...rest } = tool;
|
||||
return {
|
||||
...rest,
|
||||
};
|
||||
}),
|
||||
prompts: workflow.prompts,
|
||||
prompts: workflow.prompts
|
||||
.map(p => {
|
||||
const { sanitized } = sanitizeTextWithMentions(p.prompt, workflow);
|
||||
return {
|
||||
...p,
|
||||
prompt: sanitized,
|
||||
};
|
||||
}),
|
||||
startAgent: workflow.startAgent,
|
||||
};
|
||||
}
|
||||
|
||||
export function convertToAgenticAPIChatMessages(messages: z.infer<typeof apiV1.ChatMessage>[]): z.infer<typeof AgenticAPIChatMessage>[] {
|
||||
const converted: z.infer<typeof AgenticAPIChatMessage>[] = [];
|
||||
|
||||
|
|
@ -144,6 +164,7 @@ export function convertToAgenticAPIChatMessages(messages: z.infer<typeof apiV1.C
|
|||
|
||||
return converted;
|
||||
}
|
||||
|
||||
export function convertFromAgenticAPIChatMessages(messages: z.infer<typeof AgenticAPIChatMessage>[]): z.infer<typeof apiV1.ChatMessage>[] {
|
||||
const converted: z.infer<typeof apiV1.ChatMessage>[] = [];
|
||||
|
||||
|
|
@ -194,6 +215,7 @@ export function convertFromAgenticAPIChatMessages(messages: z.infer<typeof Agent
|
|||
}
|
||||
return converted;
|
||||
}
|
||||
|
||||
export function convertFromApiToAgenticApiMessages(messages: z.infer<typeof ApiMessage>[]): z.infer<typeof AgenticAPIChatMessage>[] {
|
||||
return messages.map(m => {
|
||||
switch (m.role) {
|
||||
|
|
@ -259,6 +281,7 @@ export function convertFromApiToAgenticApiMessages(messages: z.infer<typeof ApiM
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function convertFromAgenticApiToApiMessages(messages: z.infer<typeof AgenticAPIChatMessage>[]): z.infer<typeof ApiMessage>[] {
|
||||
const converted: z.infer<typeof ApiMessage>[] = [];
|
||||
|
||||
|
|
@ -299,4 +322,3 @@ export function convertFromAgenticApiToApiMessages(messages: z.infer<typeof Agen
|
|||
}
|
||||
return converted;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,8 +10,6 @@ export const WorkflowAgent = z.object({
|
|||
disabled: z.boolean().default(false).optional(),
|
||||
instructions: z.string(),
|
||||
examples: z.string().optional(),
|
||||
prompts: z.array(z.string()),
|
||||
tools: z.array(z.string()),
|
||||
model: z.union([
|
||||
z.literal('gpt-4o'),
|
||||
z.literal('gpt-4o-mini'),
|
||||
|
|
@ -22,7 +20,6 @@ export const WorkflowAgent = z.object({
|
|||
ragDataSources: z.array(z.string()).optional(),
|
||||
ragReturnType: z.union([z.literal('chunks'), z.literal('content')]).default('chunks'),
|
||||
ragK: z.number().default(3),
|
||||
connectedAgents: z.array(z.string()),
|
||||
controlType: z.union([z.literal('retain'), z.literal('relinquish_to_parent'), z.literal('relinquish_to_start')]).default('retain').describe('Whether this agent retains control after a turn, relinquishes to the parent agent, or relinquishes to the start agent'),
|
||||
});
|
||||
export const WorkflowPrompt = z.object({
|
||||
|
|
@ -68,3 +65,63 @@ export const WorkflowTemplate = Workflow
|
|||
description: z.string(),
|
||||
});
|
||||
|
||||
export const ConnectedEntity = z.object({
|
||||
type: z.union([z.literal('tool'), z.literal('prompt'), z.literal('agent')]),
|
||||
name: z.string(),
|
||||
});
|
||||
|
||||
export function sanitizeTextWithMentions(
|
||||
text: string,
|
||||
workflow: {
|
||||
agents: z.infer<typeof WorkflowAgent>[],
|
||||
tools: z.infer<typeof WorkflowTool>[],
|
||||
prompts: z.infer<typeof WorkflowPrompt>[],
|
||||
},
|
||||
): {
|
||||
sanitized: string;
|
||||
entities: z.infer<typeof ConnectedEntity>[];
|
||||
} {
|
||||
// Regex to match [@type:name](#type:something) pattern where type is tool/prompt/agent
|
||||
const mentionRegex = /\[@(tool|prompt|agent):([^\]]+)\]\(#mention\)/g;
|
||||
const seen = new Set<string>();
|
||||
|
||||
// collect entities
|
||||
const entities = Array
|
||||
.from(text.matchAll(mentionRegex))
|
||||
.filter(match => {
|
||||
if (seen.has(match[0])) {
|
||||
return false;
|
||||
}
|
||||
seen.add(match[0]);
|
||||
return true;
|
||||
})
|
||||
.map(match => {
|
||||
return {
|
||||
type: match[1] as 'tool' | 'prompt' | 'agent',
|
||||
name: match[2],
|
||||
};
|
||||
})
|
||||
.filter(entity => {
|
||||
seen.add(entity.name);
|
||||
if (entity.type === 'agent') {
|
||||
return workflow.agents.some(a => a.name === entity.name);
|
||||
} else if (entity.type === 'tool') {
|
||||
return workflow.tools.some(t => t.name === entity.name);
|
||||
} else if (entity.type === 'prompt') {
|
||||
return workflow.prompts.some(p => p.name === entity.name);
|
||||
}
|
||||
return false;
|
||||
})
|
||||
|
||||
// sanitize text
|
||||
for (const entity of entities) {
|
||||
const id = `${entity.type}:${entity.name}`;
|
||||
const textToReplace = `[@${id}](#mention)`;
|
||||
text = text.replace(textToReplace, `[@${id}]`);
|
||||
}
|
||||
|
||||
return {
|
||||
sanitized: text,
|
||||
entities,
|
||||
};
|
||||
}
|
||||
|
|
@ -1,8 +1,7 @@
|
|||
"use client";
|
||||
import { WithStringId } from "../../../lib/types/types";
|
||||
import { AgenticAPITool } from "../../../lib/types/agents_api_types";
|
||||
import { WorkflowPrompt } from "../../../lib/types/workflow_types";
|
||||
import { WorkflowAgent } from "../../../lib/types/workflow_types";
|
||||
import { WorkflowPrompt, WorkflowAgent } from "../../../lib/types/workflow_types";
|
||||
import { DataSource } from "../../../lib/types/datasource_types";
|
||||
import { Button, Divider, Dropdown, DropdownItem, DropdownMenu, DropdownTrigger, Input, Radio, RadioGroup, Select, SelectItem } from "@nextui-org/react";
|
||||
import { z } from "zod";
|
||||
|
|
@ -10,7 +9,7 @@ import { DataSourceIcon } from "../../../lib/components/datasource-icon";
|
|||
import { ActionButton, Pane } from "./pane";
|
||||
import { EditableField } from "../../../lib/components/editable-field";
|
||||
import { Label } from "../../../lib/components/label";
|
||||
import { PlusIcon, XIcon } from "lucide-react";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import { List } from "./config_list";
|
||||
|
||||
export function AgentConfig({
|
||||
|
|
@ -32,6 +31,32 @@ export function AgentConfig({
|
|||
handleUpdate: (agent: z.infer<typeof WorkflowAgent>) => void,
|
||||
handleClose: () => void,
|
||||
}) {
|
||||
const atMentions = [];
|
||||
for (const a of agents) {
|
||||
if (a.disabled || a.name === agent.name) {
|
||||
continue;
|
||||
}
|
||||
const id = `agent:${a.name}`;
|
||||
atMentions.push({
|
||||
id,
|
||||
value: id,
|
||||
});
|
||||
}
|
||||
for (const prompt of prompts) {
|
||||
const id = `prompt:${prompt.name}`;
|
||||
atMentions.push({
|
||||
id,
|
||||
value: id,
|
||||
});
|
||||
}
|
||||
for (const tool of tools) {
|
||||
const id = `tool:${tool.name}`;
|
||||
atMentions.push({
|
||||
id,
|
||||
value: id,
|
||||
});
|
||||
}
|
||||
|
||||
return <Pane title={agent.name} actions={[
|
||||
<ActionButton
|
||||
key="close"
|
||||
|
|
@ -101,6 +126,8 @@ export function AgentConfig({
|
|||
markdown
|
||||
label="Instructions"
|
||||
multiline
|
||||
mentions
|
||||
mentionsAtValues={atMentions}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -125,46 +152,6 @@ export function AgentConfig({
|
|||
|
||||
<Divider />
|
||||
|
||||
<div className="flex flex-col gap-4 items-start">
|
||||
<Label label="Prompts" />
|
||||
<List
|
||||
items={agent.prompts.map((prompt) => ({
|
||||
id: prompt,
|
||||
node: <div>{prompt}</div>
|
||||
}))}
|
||||
onRemove={(id) => {
|
||||
const newPrompts = agent.prompts.filter((p) => p !== id);
|
||||
handleUpdate({
|
||||
...agent,
|
||||
prompts: newPrompts
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Dropdown size="sm">
|
||||
<DropdownTrigger>
|
||||
<Button
|
||||
variant="light"
|
||||
size="sm"
|
||||
startContent={<PlusIcon size={16} />}
|
||||
>
|
||||
Add prompt
|
||||
</Button>
|
||||
</DropdownTrigger>
|
||||
<DropdownMenu onAction={(key) => handleUpdate({
|
||||
...agent,
|
||||
prompts: [...agent.prompts, key as string]
|
||||
})}>
|
||||
{prompts.filter((prompt) => !agent.prompts.includes(prompt.name)).map((prompt) => (
|
||||
<DropdownItem key={prompt.name}>
|
||||
{prompt.name}
|
||||
</DropdownItem>
|
||||
))}
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
<div className="flex flex-col gap-4 items-start">
|
||||
<Label label="RAG (beta)" />
|
||||
<List
|
||||
|
|
@ -243,88 +230,6 @@ export function AgentConfig({
|
|||
<Divider />
|
||||
</>}
|
||||
|
||||
|
||||
<div className="flex flex-col gap-4 items-start">
|
||||
<Label label="Tools" />
|
||||
<List
|
||||
items={agent.tools.map((tool) => ({
|
||||
id: tool,
|
||||
node: <div>{tool}</div>
|
||||
}))}
|
||||
onRemove={(id) => {
|
||||
const newTools = agent.tools.filter((t) => t !== id);
|
||||
handleUpdate({
|
||||
...agent,
|
||||
tools: newTools
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Dropdown>
|
||||
<DropdownTrigger>
|
||||
<Button
|
||||
variant="light"
|
||||
size="sm"
|
||||
startContent={<PlusIcon size={16} />}
|
||||
>
|
||||
Add tool
|
||||
</Button>
|
||||
</DropdownTrigger>
|
||||
<DropdownMenu onAction={(key) => handleUpdate({
|
||||
...agent,
|
||||
tools: [...(agent.tools || []), key as string]
|
||||
})}>
|
||||
{tools.filter((tool) => !(agent.tools || []).includes(tool.name)).map((tool) => (
|
||||
<DropdownItem key={tool.name}>
|
||||
<div className="font-mono">{tool.name}</div>
|
||||
</DropdownItem>
|
||||
))}
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
<div className="flex flex-col gap-4 items-start">
|
||||
<Label label="Connected agents" />
|
||||
<List
|
||||
items={agent.connectedAgents?.map((connectedAgentName) => ({
|
||||
id: connectedAgentName,
|
||||
node: <div>{connectedAgentName}</div>
|
||||
})) || []}
|
||||
onRemove={(id) => {
|
||||
const newAgents = (agent.connectedAgents || []).filter((a) => a !== id);
|
||||
handleUpdate({
|
||||
...agent,
|
||||
connectedAgents: newAgents
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Dropdown>
|
||||
<DropdownTrigger>
|
||||
<Button
|
||||
variant="light"
|
||||
size="sm"
|
||||
startContent={<PlusIcon size={16} />}
|
||||
>
|
||||
Connect agent
|
||||
</Button>
|
||||
</DropdownTrigger>
|
||||
<DropdownMenu onAction={(key) => handleUpdate({
|
||||
...agent,
|
||||
connectedAgents: [...(agent.connectedAgents || []), key as string]
|
||||
})}>
|
||||
{agents.filter((a) =>
|
||||
a.name !== agent.name &&
|
||||
!(agent.connectedAgents || []).includes(a.name) &&
|
||||
!a.global
|
||||
).map((a) => (
|
||||
<DropdownItem key={a.name}>
|
||||
<div>{a.name}</div>
|
||||
</DropdownItem>
|
||||
))}
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
<div className="flex flex-col gap-2 items-start">
|
||||
<Label label="Model" />
|
||||
|
|
|
|||
|
|
@ -1,21 +1,53 @@
|
|||
"use client";
|
||||
import { WorkflowPrompt } from "../../../lib/types/workflow_types";
|
||||
import { Divider, Input, Textarea } from "@nextui-org/react";
|
||||
import { WorkflowAgent, WorkflowPrompt, WorkflowTool } from "../../../lib/types/workflow_types";
|
||||
import { Divider } from "@nextui-org/react";
|
||||
import { z } from "zod";
|
||||
import { ActionButton, Pane } from "./pane";
|
||||
import { EditableField } from "../../../lib/components/editable-field";
|
||||
|
||||
export function PromptConfig({
|
||||
prompt,
|
||||
agents,
|
||||
tools,
|
||||
prompts,
|
||||
usedPromptNames,
|
||||
handleUpdate,
|
||||
handleClose,
|
||||
}: {
|
||||
prompt: z.infer<typeof WorkflowPrompt>,
|
||||
agents: z.infer<typeof WorkflowAgent>[],
|
||||
tools: z.infer<typeof WorkflowTool>[],
|
||||
prompts: z.infer<typeof WorkflowPrompt>[],
|
||||
usedPromptNames: Set<string>,
|
||||
handleUpdate: (prompt: z.infer<typeof WorkflowPrompt>) => void,
|
||||
handleClose: () => void,
|
||||
}) {
|
||||
const atMentions = [];
|
||||
for (const a of agents) {
|
||||
const id = `agent:${a.name}`;
|
||||
atMentions.push({
|
||||
id,
|
||||
value: id,
|
||||
});
|
||||
}
|
||||
for (const p of prompts) {
|
||||
if (p.name === prompt.name) {
|
||||
continue;
|
||||
}
|
||||
const id = `prompt:${p.name}`;
|
||||
atMentions.push({
|
||||
id,
|
||||
value: id,
|
||||
});
|
||||
}
|
||||
for (const tool of tools) {
|
||||
const id = `tool:${tool.name}`;
|
||||
atMentions.push({
|
||||
id,
|
||||
value: id,
|
||||
});
|
||||
}
|
||||
|
||||
return <Pane title={prompt.name} actions={[
|
||||
<ActionButton
|
||||
key="close"
|
||||
|
|
@ -67,6 +99,8 @@ export function PromptConfig({
|
|||
markdown
|
||||
label="Prompt"
|
||||
multiline
|
||||
mentions
|
||||
mentionsAtValues={atMentions}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -29,8 +29,6 @@ import { BackIcon, HamburgerIcon, WorkflowIcon } from "../../../lib/components/i
|
|||
import { CopyIcon, Layers2Icon, RadioIcon, RedoIcon, Sparkles, UndoIcon } from "lucide-react";
|
||||
import { EntityList } from "./entity_list";
|
||||
import { CopilotMessage } from "../../../lib/types/copilot_types";
|
||||
import { InfoIcon } from "lucide-react";
|
||||
import { clsx } from "clsx";
|
||||
|
||||
enablePatches();
|
||||
|
||||
|
|
@ -170,7 +168,7 @@ function reducer(state: State, action: Action): State {
|
|||
draft.currentIndex++;
|
||||
draft.present.pendingChanges = true;
|
||||
draft.present.chatKey++;
|
||||
});
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "update_workflow_name": {
|
||||
|
|
@ -254,14 +252,11 @@ function reducer(state: State, action: Action): State {
|
|||
description: "",
|
||||
disabled: false,
|
||||
instructions: "",
|
||||
prompts: [],
|
||||
tools: [],
|
||||
model: "gpt-4o",
|
||||
locked: false,
|
||||
toggleAble: true,
|
||||
ragReturnType: "chunks",
|
||||
ragK: 3,
|
||||
connectedAgents: [],
|
||||
controlType: "retain",
|
||||
...action.agent
|
||||
});
|
||||
|
|
@ -331,10 +326,6 @@ function reducer(state: State, action: Action): State {
|
|||
draft.workflow.agents = draft.workflow.agents.filter(
|
||||
(agent) => agent.name !== action.name
|
||||
);
|
||||
draft.workflow.agents = draft.workflow.agents.map(agent => ({
|
||||
...agent,
|
||||
connectedAgents: agent.connectedAgents.filter(connectedAgent => connectedAgent !== action.name)
|
||||
}));
|
||||
draft.selection = null;
|
||||
draft.pendingChanges = true;
|
||||
draft.chatKey++;
|
||||
|
|
@ -346,10 +337,6 @@ function reducer(state: State, action: Action): State {
|
|||
draft.workflow.tools = draft.workflow.tools.filter(
|
||||
(tool) => tool.name !== action.name
|
||||
);
|
||||
draft.workflow.agents = draft.workflow.agents.map(agent => ({
|
||||
...agent,
|
||||
tools: agent.tools.filter(toolName => toolName !== action.name)
|
||||
}));
|
||||
draft.selection = null;
|
||||
draft.pendingChanges = true;
|
||||
draft.chatKey++;
|
||||
|
|
@ -361,10 +348,6 @@ function reducer(state: State, action: Action): State {
|
|||
draft.workflow.prompts = draft.workflow.prompts.filter(
|
||||
(prompt) => prompt.name !== action.name
|
||||
);
|
||||
draft.workflow.agents = draft.workflow.agents.map(agent => ({
|
||||
...agent,
|
||||
prompts: agent.prompts.filter(promptName => promptName !== action.name)
|
||||
}));
|
||||
draft.selection = null;
|
||||
draft.pendingChanges = true;
|
||||
draft.chatKey++;
|
||||
|
|
@ -373,26 +356,45 @@ function reducer(state: State, action: Action): State {
|
|||
if (isLive) {
|
||||
break;
|
||||
}
|
||||
|
||||
// update agent data
|
||||
draft.workflow.agents = draft.workflow.agents.map((agent) =>
|
||||
agent.name === action.name ? { ...agent, ...action.agent } : agent
|
||||
);
|
||||
if (action.agent.name && draft.workflow.startAgent === action.name) {
|
||||
draft.workflow.startAgent = action.agent.name;
|
||||
}
|
||||
|
||||
// if the agent is renamed
|
||||
if (action.agent.name && action.agent.name !== action.name) {
|
||||
// update start agent pointer if this is the start agent
|
||||
if (action.agent.name && draft.workflow.startAgent === action.name) {
|
||||
draft.workflow.startAgent = action.agent.name;
|
||||
}
|
||||
|
||||
// update this agents references in other agents / prompts
|
||||
draft.workflow.agents = draft.workflow.agents.map(agent => ({
|
||||
...agent,
|
||||
connectedAgents: agent.connectedAgents.map(connectedAgent =>
|
||||
connectedAgent === action.name ? action.agent.name! : connectedAgent
|
||||
instructions: agent.instructions.replace(
|
||||
`[@agent:${action.name}](#mention)`,
|
||||
`[@agent:${action.agent.name}](#mention)`
|
||||
)
|
||||
}));
|
||||
draft.workflow.prompts = draft.workflow.prompts.map(prompt => ({
|
||||
...prompt,
|
||||
prompt: prompt.prompt.replace(
|
||||
`[@agent:${action.name}](#mention)`,
|
||||
`[@agent:${action.agent.name}](#mention)`
|
||||
)
|
||||
}));
|
||||
|
||||
// update the selection pointer if this is the selected agent
|
||||
if (draft.selection?.type === "agent" && draft.selection.name === action.name) {
|
||||
draft.selection = {
|
||||
type: "agent",
|
||||
name: action.agent.name
|
||||
};
|
||||
}
|
||||
}
|
||||
if (action.agent.name && draft.selection?.type === "agent" && draft.selection.name === action.name) {
|
||||
draft.selection = {
|
||||
type: "agent",
|
||||
name: action.agent.name
|
||||
};
|
||||
}
|
||||
|
||||
// select this agent
|
||||
draft.selection = {
|
||||
type: "agent",
|
||||
name: action.agent.name || action.name,
|
||||
|
|
@ -404,23 +406,40 @@ function reducer(state: State, action: Action): State {
|
|||
if (isLive) {
|
||||
break;
|
||||
}
|
||||
|
||||
// update tool data
|
||||
draft.workflow.tools = draft.workflow.tools.map((tool) =>
|
||||
tool.name === action.name ? { ...tool, ...action.tool } : tool
|
||||
);
|
||||
|
||||
// if the tool is renamed
|
||||
if (action.tool.name && action.tool.name !== action.name) {
|
||||
// update this tools references in other agents / prompts
|
||||
draft.workflow.agents = draft.workflow.agents.map(agent => ({
|
||||
...agent,
|
||||
tools: agent.tools.map(toolName =>
|
||||
toolName === action.name ? action.tool.name! : toolName
|
||||
instructions: agent.instructions.replace(
|
||||
`[@tool:${action.name}](#mention)`,
|
||||
`[@tool:${action.tool.name}](#mention)`
|
||||
)
|
||||
}));
|
||||
draft.workflow.prompts = draft.workflow.prompts.map(prompt => ({
|
||||
...prompt,
|
||||
prompt: prompt.prompt.replace(
|
||||
`[@tool:${action.name}](#mention)`,
|
||||
`[@tool:${action.tool.name}](#mention)`
|
||||
)
|
||||
}));
|
||||
|
||||
// if this is the selected tool, update the selection
|
||||
if (draft.selection?.type === "tool" && draft.selection.name === action.name) {
|
||||
draft.selection = {
|
||||
type: "tool",
|
||||
name: action.tool.name
|
||||
};
|
||||
}
|
||||
}
|
||||
if (action.tool.name && draft.selection?.type === "tool" && draft.selection.name === action.name) {
|
||||
draft.selection = {
|
||||
type: "tool",
|
||||
name: action.tool.name
|
||||
};
|
||||
}
|
||||
|
||||
// select this tool
|
||||
draft.selection = {
|
||||
type: "tool",
|
||||
name: action.tool.name || action.name,
|
||||
|
|
@ -432,21 +451,40 @@ function reducer(state: State, action: Action): State {
|
|||
if (isLive) {
|
||||
break;
|
||||
}
|
||||
|
||||
// update prompt data
|
||||
draft.workflow.prompts = draft.workflow.prompts.map((prompt) =>
|
||||
prompt.name === action.name ? { ...prompt, ...action.prompt } : prompt
|
||||
);
|
||||
draft.workflow.agents = draft.workflow.agents.map(agent => ({
|
||||
...agent,
|
||||
prompts: agent.prompts.map(promptName =>
|
||||
promptName === action.name ? action.prompt.name! : promptName
|
||||
)
|
||||
}));
|
||||
if (action.prompt.name && draft.selection?.type === "prompt" && draft.selection.name === action.name) {
|
||||
draft.selection = {
|
||||
type: "prompt",
|
||||
name: action.prompt.name
|
||||
};
|
||||
|
||||
// if the prompt is renamed
|
||||
if (action.prompt.name && action.prompt.name !== action.name) {
|
||||
// update this prompts references in other agents / prompts
|
||||
draft.workflow.agents = draft.workflow.agents.map(agent => ({
|
||||
...agent,
|
||||
instructions: agent.instructions.replace(
|
||||
`[@prompt:${action.name}](#mention)`,
|
||||
`[@prompt:${action.prompt.name}](#mention)`
|
||||
)
|
||||
}));
|
||||
draft.workflow.prompts = draft.workflow.prompts.map(prompt => ({
|
||||
...prompt,
|
||||
prompt: prompt.prompt.replace(
|
||||
`[@prompt:${action.name}](#mention)`,
|
||||
`[@prompt:${action.prompt.name}](#mention)`
|
||||
)
|
||||
}));
|
||||
|
||||
// if this is the selected prompt, update the selection
|
||||
if (draft.selection?.type === "prompt" && draft.selection.name === action.name) {
|
||||
draft.selection = {
|
||||
type: "prompt",
|
||||
name: action.prompt.name
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// select this prompt
|
||||
draft.selection = {
|
||||
type: "prompt",
|
||||
name: action.prompt.name || action.name,
|
||||
|
|
@ -809,9 +847,9 @@ export function WorkflowEditor({
|
|||
/>
|
||||
</ResizablePanel>
|
||||
<ResizableHandle />
|
||||
<ResizablePanel
|
||||
minSize={20}
|
||||
defaultSize={showCopilot ? 85 - copilotWidth : 85}
|
||||
<ResizablePanel
|
||||
minSize={20}
|
||||
defaultSize={showCopilot ? 85 - copilotWidth : 85}
|
||||
className="overflow-auto"
|
||||
>
|
||||
<ChatApp
|
||||
|
|
@ -842,6 +880,9 @@ export function WorkflowEditor({
|
|||
{state.present.selection?.type === "prompt" && <PromptConfig
|
||||
key={state.present.selection.name}
|
||||
prompt={state.present.workflow.prompts.find((prompt) => prompt.name === state.present.selection!.name)!}
|
||||
agents={state.present.workflow.agents}
|
||||
tools={state.present.workflow.tools}
|
||||
prompts={state.present.workflow.prompts}
|
||||
usedPromptNames={new Set(state.present.workflow.prompts.filter((prompt) => prompt.name !== state.present.selection!.name).map((prompt) => prompt.name))}
|
||||
handleUpdate={handleUpdatePrompt.bind(null, state.present.selection.name)}
|
||||
handleClose={handleUnselectPrompt}
|
||||
|
|
@ -849,8 +890,8 @@ export function WorkflowEditor({
|
|||
</ResizablePanel>
|
||||
{showCopilot && <>
|
||||
<ResizableHandle />
|
||||
<ResizablePanel
|
||||
minSize={10}
|
||||
<ResizablePanel
|
||||
minSize={10}
|
||||
defaultSize={copilotWidth}
|
||||
onResize={(size) => setCopilotWidth(size)}
|
||||
>
|
||||
|
|
|
|||
68
apps/rowboat/package-lock.json
generated
68
apps/rowboat/package-lock.json
generated
|
|
@ -34,6 +34,8 @@
|
|||
"mongodb": "^6.8.0",
|
||||
"next": "^14.2.13",
|
||||
"openai": "^4.67.2",
|
||||
"quill": "^2.0.3",
|
||||
"quill-mention": "^6.0.2",
|
||||
"react": "^18.3.1",
|
||||
"react-diff-viewer-continued": "^3.4.0",
|
||||
"react-dom": "^18.3.1",
|
||||
|
|
@ -10803,6 +10805,11 @@
|
|||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fast-diff": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
|
||||
"integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw=="
|
||||
},
|
||||
"node_modules/fast-glob": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
|
||||
|
|
@ -12388,6 +12395,16 @@
|
|||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"node_modules/lodash-es": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
|
||||
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
|
||||
},
|
||||
"node_modules/lodash.clonedeep": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
|
||||
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ=="
|
||||
},
|
||||
"node_modules/lodash.debounce": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||
|
|
@ -12408,6 +12425,12 @@
|
|||
"resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz",
|
||||
"integrity": "sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg=="
|
||||
},
|
||||
"node_modules/lodash.isequal": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
|
||||
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
|
||||
"deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead."
|
||||
},
|
||||
"node_modules/lodash.isobject": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz",
|
||||
|
|
@ -14032,6 +14055,11 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/parchment": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/parchment/-/parchment-3.0.0.tgz",
|
||||
"integrity": "sha512-HUrJFQ/StvgmXRcQ1ftY6VEZUq3jA2t9ncFN4F84J/vN0/FPpQF+8FKXb3l6fLces6q0uOHj6NJn+2xvZnxO6A=="
|
||||
},
|
||||
"node_modules/parent-module": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||
|
|
@ -14430,6 +14458,46 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"node_modules/quill": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/quill/-/quill-2.0.3.tgz",
|
||||
"integrity": "sha512-xEYQBqfYx/sfb33VJiKnSJp8ehloavImQ2A6564GAbqG55PGw1dAWUn1MUbQB62t0azawUS2CZZhWCjO8gRvTw==",
|
||||
"dependencies": {
|
||||
"eventemitter3": "^5.0.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"parchment": "^3.0.0",
|
||||
"quill-delta": "^5.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"npm": ">=8.2.3"
|
||||
}
|
||||
},
|
||||
"node_modules/quill-delta": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-5.1.0.tgz",
|
||||
"integrity": "sha512-X74oCeRI4/p0ucjb5Ma8adTXd9Scumz367kkMK5V/IatcX6A0vlgLgKbzXWy5nZmCGeNJm2oQX0d2Eqj+ZIlCA==",
|
||||
"dependencies": {
|
||||
"fast-diff": "^1.3.0",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"lodash.isequal": "^4.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/quill-mention": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/quill-mention/-/quill-mention-6.0.2.tgz",
|
||||
"integrity": "sha512-ZyiEzLxtoNJ/hAjMyfVsugpXAcOdD2fbHmJT3yKuwpUxiDHdmutVJqOzpItqiVbcjUecnjAF+/Yo1IN3/W6iAg==",
|
||||
"dependencies": {
|
||||
"quill": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/quill/node_modules/eventemitter3": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
|
||||
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="
|
||||
},
|
||||
"node_modules/raf": {
|
||||
"version": "3.4.1",
|
||||
"resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@
|
|||
"mongodb": "^6.8.0",
|
||||
"next": "^14.2.13",
|
||||
"openai": "^4.67.2",
|
||||
"quill": "^2.0.3",
|
||||
"quill-mention": "^6.0.2",
|
||||
"react": "^18.3.1",
|
||||
"react-diff-viewer-continued": "^3.4.0",
|
||||
"react-dom": "^18.3.1",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue