mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-05-13 17:22:37 +02:00
Clean up agent config UX
This commit is contained in:
parent
b52e4b952b
commit
99053c4360
3 changed files with 184 additions and 229 deletions
|
|
@ -15,11 +15,11 @@
|
||||||
}
|
}
|
||||||
.custom-scrollbar {
|
.custom-scrollbar {
|
||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
scrollbar-color: #E4E4E7 transparent;
|
scrollbar-color: rgba(156, 163, 175, 0.3) transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-scrollbar::-webkit-scrollbar {
|
.custom-scrollbar::-webkit-scrollbar {
|
||||||
width: 6px;
|
width: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-scrollbar::-webkit-scrollbar-track {
|
.custom-scrollbar::-webkit-scrollbar-track {
|
||||||
|
|
@ -27,17 +27,19 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||||
background-color: #E4E4E7;
|
background-color: rgba(156, 163, 175, 0.3);
|
||||||
border-radius: 3px;
|
border-radius: 4px;
|
||||||
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Dark mode */
|
/* Dark mode */
|
||||||
.dark .custom-scrollbar {
|
.dark .custom-scrollbar {
|
||||||
scrollbar-color: #3F3F46 transparent;
|
scrollbar-color: rgba(63, 63, 70, 0.4) transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .custom-scrollbar::-webkit-scrollbar-thumb {
|
.dark .custom-scrollbar::-webkit-scrollbar-thumb {
|
||||||
background-color: #3F3F46;
|
background-color: rgba(63, 63, 70, 0.4);
|
||||||
|
border: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,19 @@ export function AgentConfig({
|
||||||
setLocalName(agent.name);
|
setLocalName(agent.name);
|
||||||
}, [agent.name]);
|
}, [agent.name]);
|
||||||
|
|
||||||
|
// Store active tab in URL hash to persist across re-renders
|
||||||
|
useEffect(() => {
|
||||||
|
const hash = window.location.hash.slice(1);
|
||||||
|
if (hash === 'instructions' || hash === 'configurations') {
|
||||||
|
setActiveTab(hash as TabType);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleTabChange = (tab: TabType) => {
|
||||||
|
setActiveTab(tab);
|
||||||
|
window.location.hash = tab;
|
||||||
|
};
|
||||||
|
|
||||||
// Track changes in RAG datasources
|
// Track changes in RAG datasources
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const currentSources = agent.ragDataSources || [];
|
const currentSources = agent.ragDataSources || [];
|
||||||
|
|
@ -198,7 +211,7 @@ export function AgentConfig({
|
||||||
{(['instructions', 'configurations'] as TabType[]).map((tab) => (
|
{(['instructions', 'configurations'] as TabType[]).map((tab) => (
|
||||||
<button
|
<button
|
||||||
key={tab}
|
key={tab}
|
||||||
onClick={() => setActiveTab(tab)}
|
onClick={() => handleTabChange(tab)}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"px-4 py-2 text-sm font-medium transition-colors relative",
|
"px-4 py-2 text-sm font-medium transition-colors relative",
|
||||||
activeTab === tab
|
activeTab === tab
|
||||||
|
|
@ -387,67 +400,63 @@ export function AgentConfig({
|
||||||
|
|
||||||
|
|
||||||
{activeTab === 'configurations' && (
|
{activeTab === 'configurations' && (
|
||||||
<div className="space-y-6">
|
<div className="space-y-8">
|
||||||
{!agent.locked && (
|
{!agent.locked && (
|
||||||
<div className="space-y-4">
|
<div className="space-y-2">
|
||||||
<div className="space-y-2">
|
<label className={sectionHeaderStyles}>
|
||||||
<label className={sectionHeaderStyles}>
|
Name
|
||||||
Name
|
</label>
|
||||||
</label>
|
<div className={clsx(
|
||||||
<div className={clsx(
|
"border rounded-lg focus-within:ring-2",
|
||||||
"border rounded-lg focus-within:ring-2",
|
nameError
|
||||||
nameError
|
? "border-red-500 focus-within:ring-red-500/20"
|
||||||
? "border-red-500 focus-within:ring-red-500/20"
|
: "border-gray-200 dark:border-gray-700 focus-within:ring-indigo-500/20 dark:focus-within:ring-indigo-400/20"
|
||||||
: "border-gray-200 dark:border-gray-700 focus-within:ring-indigo-500/20 dark:focus-within:ring-indigo-400/20"
|
)}>
|
||||||
)}>
|
<Textarea
|
||||||
<Textarea
|
value={agent.name}
|
||||||
value={agent.name}
|
useValidation={true}
|
||||||
useValidation={true}
|
updateOnBlur={true}
|
||||||
updateOnBlur={true}
|
validate={(value) => {
|
||||||
validate={(value) => {
|
const error = validateAgentName(value, agent.name, usedAgentNames);
|
||||||
const error = validateAgentName(value, agent.name, usedAgentNames);
|
setNameError(error);
|
||||||
setNameError(error);
|
return { valid: !error, errorMessage: error || undefined };
|
||||||
return { valid: !error, errorMessage: error || undefined };
|
}}
|
||||||
}}
|
onValidatedChange={(value) => {
|
||||||
onValidatedChange={(value) => {
|
handleUpdate({
|
||||||
handleUpdate({
|
...agent,
|
||||||
...agent,
|
name: value
|
||||||
name: value
|
});
|
||||||
});
|
}}
|
||||||
}}
|
placeholder="Enter agent name..."
|
||||||
placeholder="Enter agent name..."
|
className="w-full text-sm bg-transparent focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 transition-colors px-4 py-3"
|
||||||
className="w-full text-sm bg-transparent focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 transition-colors px-4 py-3"
|
autoResize
|
||||||
autoResize
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{nameError && (
|
|
||||||
<p className="text-sm text-red-500">{nameError}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
{nameError && (
|
||||||
|
<p className="text-sm text-red-500">{nameError}</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-2">
|
||||||
<div className="space-y-2">
|
<label className={sectionHeaderStyles}>
|
||||||
<label className={sectionHeaderStyles}>
|
Description
|
||||||
Description
|
</label>
|
||||||
</label>
|
<Textarea
|
||||||
<Textarea
|
value={agent.description || ""}
|
||||||
value={agent.description || ""}
|
onChange={(e) => {
|
||||||
onChange={(e) => {
|
handleUpdate({
|
||||||
handleUpdate({
|
...agent,
|
||||||
...agent,
|
description: e.target.value
|
||||||
description: e.target.value
|
});
|
||||||
});
|
}}
|
||||||
}}
|
placeholder="Enter a description for this agent"
|
||||||
placeholder="Enter a description for this agent"
|
className={textareaStyles}
|
||||||
className={textareaStyles}
|
autoResize
|
||||||
autoResize
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<label className={sectionHeaderStyles}>
|
<label className={sectionHeaderStyles}>
|
||||||
Agent Type
|
Agent Type
|
||||||
|
|
@ -479,7 +488,7 @@ export function AgentConfig({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<label className={sectionHeaderStyles}>
|
<label className={sectionHeaderStyles}>
|
||||||
Model
|
Model
|
||||||
|
|
@ -564,7 +573,7 @@ export function AgentConfig({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{agent.outputVisibility === "internal" && (
|
{agent.outputVisibility === "internal" && (
|
||||||
<div className="space-y-4">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<label className={sectionHeaderStyles}>
|
<label className={sectionHeaderStyles}>
|
||||||
Max calls from parent agent per turn
|
Max calls from parent agent per turn
|
||||||
|
|
@ -596,7 +605,7 @@ export function AgentConfig({
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{USE_TRANSFER_CONTROL_OPTIONS && (
|
{USE_TRANSFER_CONTROL_OPTIONS && (
|
||||||
<div className="space-y-4">
|
<div className="space-y-2">
|
||||||
<label className={sectionHeaderStyles}>
|
<label className={sectionHeaderStyles}>
|
||||||
Conversation control after turn
|
Conversation control after turn
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -623,178 +632,105 @@ export function AgentConfig({
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{useRag && (
|
{useRag && (
|
||||||
<div className="space-y-6">
|
<div className="space-y-2">
|
||||||
<div className="space-y-4">
|
<div className="flex items-center">
|
||||||
<div className="flex items-center">
|
<label className={sectionHeaderStyles}>
|
||||||
<label className={sectionHeaderStyles}>
|
RAG DATA SOURCES
|
||||||
RAG DATA SOURCES
|
</label>
|
||||||
</label>
|
</div>
|
||||||
</div>
|
<div className="flex items-center gap-3">
|
||||||
<div className="flex items-center gap-3">
|
<Select
|
||||||
<Select
|
variant="bordered"
|
||||||
variant="bordered"
|
placeholder="Add data source"
|
||||||
placeholder="Add data source"
|
size="sm"
|
||||||
|
className="w-64"
|
||||||
|
onSelectionChange={(keys) => {
|
||||||
|
const key = keys.currentKey as string;
|
||||||
|
if (key) {
|
||||||
|
handleUpdate({
|
||||||
|
...agent,
|
||||||
|
ragDataSources: [...(agent.ragDataSources || []), key]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
startContent={<PlusIcon className="w-4 h-4 text-gray-500" />}
|
||||||
|
>
|
||||||
|
{dataSources
|
||||||
|
.filter((ds) => !(agent.ragDataSources || []).includes(ds._id))
|
||||||
|
.map((ds) => (
|
||||||
|
<SelectItem key={ds._id}>
|
||||||
|
{ds.name}
|
||||||
|
</SelectItem>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
{showRagCta && (
|
||||||
|
<CustomButton
|
||||||
|
variant="primary"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="w-64"
|
onClick={handleUpdateInstructions}
|
||||||
onSelectionChange={(keys) => {
|
className="whitespace-nowrap"
|
||||||
const key = keys.currentKey as string;
|
>
|
||||||
if (key) {
|
Update Instructions
|
||||||
|
</CustomButton>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{agent.ragDataSources !== undefined && agent.ragDataSources.length > 0 && (
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
{(agent.ragDataSources || []).map((source) => {
|
||||||
|
const ds = dataSources.find((ds) => ds._id === source);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={source}
|
||||||
|
className="flex items-center justify-between p-3 rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-750 transition-colors"
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="flex items-center justify-center w-8 h-8 rounded-md bg-indigo-50 dark:bg-indigo-900/20">
|
||||||
|
<svg
|
||||||
|
className="w-4 h-4 text-indigo-600 dark:text-indigo-400"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span className="text-sm font-medium text-gray-900 dark:text-gray-100">
|
||||||
|
{ds?.name || "Unknown"}
|
||||||
|
</span>
|
||||||
|
<span className="text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
Data Source
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<CustomButton
|
||||||
|
variant="tertiary"
|
||||||
|
size="sm"
|
||||||
|
className="text-gray-500 hover:text-red-500 hover:bg-red-50 dark:hover:bg-red-900/20"
|
||||||
|
onClick={() => {
|
||||||
|
const newSources = agent.ragDataSources?.filter((s) => s !== source);
|
||||||
handleUpdate({
|
handleUpdate({
|
||||||
...agent,
|
...agent,
|
||||||
ragDataSources: [...(agent.ragDataSources || []), key]
|
ragDataSources: newSources
|
||||||
});
|
});
|
||||||
}
|
}}
|
||||||
}}
|
startContent={<Trash2 className="w-4 h-4" />}
|
||||||
startContent={<PlusIcon className="w-4 h-4 text-gray-500" />}
|
|
||||||
>
|
|
||||||
{dataSources
|
|
||||||
.filter((ds) => !(agent.ragDataSources || []).includes(ds._id))
|
|
||||||
.map((ds) => (
|
|
||||||
<SelectItem key={ds._id}>
|
|
||||||
{ds.name}
|
|
||||||
</SelectItem>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</Select>
|
|
||||||
|
|
||||||
{showRagCta && (
|
|
||||||
<CustomButton
|
|
||||||
variant="primary"
|
|
||||||
size="sm"
|
|
||||||
onClick={handleUpdateInstructions}
|
|
||||||
className="whitespace-nowrap"
|
|
||||||
>
|
>
|
||||||
Update Instructions
|
Remove
|
||||||
</CustomButton>
|
</CustomButton>
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
{(agent.ragDataSources || []).map((source) => {
|
|
||||||
const ds = dataSources.find((ds) => ds._id === source);
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={source}
|
|
||||||
className="flex items-center justify-between p-3 rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-750 transition-colors"
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<div className="flex items-center justify-center w-8 h-8 rounded-md bg-indigo-50 dark:bg-indigo-900/20">
|
|
||||||
<svg
|
|
||||||
className="w-4 h-4 text-indigo-600 dark:text-indigo-400"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeWidth={2}
|
|
||||||
d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col">
|
|
||||||
<span className="text-sm font-medium text-gray-900 dark:text-gray-100">
|
|
||||||
{ds?.name || "Unknown"}
|
|
||||||
</span>
|
|
||||||
<span className="text-xs text-gray-500 dark:text-gray-400">
|
|
||||||
Data Source
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<CustomButton
|
|
||||||
variant="tertiary"
|
|
||||||
size="sm"
|
|
||||||
className="text-gray-500 hover:text-red-500 hover:bg-red-50 dark:hover:bg-red-900/20"
|
|
||||||
onClick={() => {
|
|
||||||
const newSources = agent.ragDataSources?.filter((s) => s !== source);
|
|
||||||
handleUpdate({
|
|
||||||
...agent,
|
|
||||||
ragDataSources: newSources
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
startContent={<Trash2 className="w-4 h-4" />}
|
|
||||||
>
|
|
||||||
Remove
|
|
||||||
</CustomButton>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{agent.ragDataSources !== undefined && agent.ragDataSources.length > 0 && (
|
|
||||||
<>
|
|
||||||
<div className="mt-4">
|
|
||||||
<button
|
|
||||||
onClick={() => setIsAdvancedConfigOpen(!isAdvancedConfigOpen)}
|
|
||||||
className="flex items-center gap-2 text-xs font-medium text-gray-500 dark:text-gray-400 uppercase hover:text-gray-700 dark:hover:text-gray-300 transition-colors"
|
|
||||||
>
|
|
||||||
{isAdvancedConfigOpen ?
|
|
||||||
<ChevronDown className="w-4 h-4 text-gray-400" /> :
|
|
||||||
<ChevronRight className="w-4 h-4 text-gray-400" />
|
|
||||||
}
|
|
||||||
Advanced RAG configuration
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{isAdvancedConfigOpen && (
|
|
||||||
<div className="mt-3 ml-4 p-4 rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800/50">
|
|
||||||
<div className="grid gap-6">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<label className={sectionHeaderStyles}>
|
|
||||||
Return type
|
|
||||||
</label>
|
|
||||||
<div className="flex gap-4">
|
|
||||||
{["chunks", "content"].map((type) => (
|
|
||||||
<button
|
|
||||||
key={type}
|
|
||||||
onClick={() => handleUpdate({
|
|
||||||
...agent,
|
|
||||||
ragReturnType: type as z.infer<typeof WorkflowAgent>['ragReturnType']
|
|
||||||
})}
|
|
||||||
className={clsx(
|
|
||||||
"px-4 py-2 rounded-lg text-sm font-medium transition-colors",
|
|
||||||
agent.ragReturnType === type
|
|
||||||
? "bg-indigo-50 dark:bg-indigo-900/20 text-indigo-600 dark:text-indigo-400 border-2 border-indigo-200 dark:border-indigo-800"
|
|
||||||
: "bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-400 border border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{type.charAt(0).toUpperCase() + type.slice(1)}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<label className={sectionHeaderStyles}>
|
|
||||||
Number of matches
|
|
||||||
</label>
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
min="1"
|
|
||||||
max="20"
|
|
||||||
className="w-24 px-3 py-2 rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 text-sm focus:ring-2 focus:ring-indigo-500/20 dark:focus:ring-indigo-400/20 focus:border-indigo-500 dark:focus:border-indigo-400"
|
|
||||||
value={agent.ragK}
|
|
||||||
onChange={(e) => handleUpdate({
|
|
||||||
...agent,
|
|
||||||
ragK: parseInt(e.target.value)
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
<span className="text-sm text-gray-500 dark:text-gray-400">
|
|
||||||
matches
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
|
||||||
Number of relevant chunks to retrieve (1-20)
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
);
|
||||||
)}
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -65,3 +65,20 @@
|
||||||
background-color: #f59e0b !important;
|
background-color: #f59e0b !important;
|
||||||
color: white !important;
|
color: white !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Quill editor font size in editing mode */
|
||||||
|
.ql-editor {
|
||||||
|
font-size: 1rem !important; /* Increase base font size */
|
||||||
|
line-height: 1.6 !important; /* Adjust line height for better readability */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Keep the rendered markdown view at its original size */
|
||||||
|
.ql-editor.ql-blank::before {
|
||||||
|
font-size: 1rem !important; /* Match placeholder text size */
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure mentions maintain proper size */
|
||||||
|
.ql-editor .mention {
|
||||||
|
font-size: inherit !important;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue