diff --git a/apps/rowboat/app/lib/components/input-field.tsx b/apps/rowboat/app/lib/components/input-field.tsx index 42ef48a6..a2b96dac 100644 --- a/apps/rowboat/app/lib/components/input-field.tsx +++ b/apps/rowboat/app/lib/components/input-field.tsx @@ -251,7 +251,7 @@ function TextInputField({
- {value ? ( + {(mentions ? localValue : value) ? ( <> {markdown ? (
@@ -355,7 +355,7 @@ function TextInputField({ "max-h-[420px] overflow-y-auto": multiline })}> diff --git a/apps/rowboat/app/lib/components/mentions_editor.tsx b/apps/rowboat/app/lib/components/mentions_editor.tsx index 0f0bd72e..35b0aef3 100644 --- a/apps/rowboat/app/lib/components/mentions_editor.tsx +++ b/apps/rowboat/app/lib/components/mentions_editor.tsx @@ -97,13 +97,20 @@ export default function MentionEditor({ }) { const containerRef = useRef(null); const quillRef = useRef(null); + const atValuesRef = useRef(atValues); + const onValueChangeRef = useRef(onValueChange); + const externalValueRef = useRef(value); + const isApplyingExternalRef = useRef(false); function getMarkdown(): string { if (!quillRef.current) { return ""; } // generate markdown representation of content - const markdown = quillRef.current.getContents().map((op) => { + const delta = quillRef.current.getContents() as unknown as Delta; + // Quill Delta has .ops + const ops: any[] = (delta as any).ops || []; + const markdown = ops.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)`; @@ -120,6 +127,20 @@ export default function MentionEditor({ navigator.clipboard.writeText(getMarkdown()); } + // Keep refs up to date without re-initializing Quill + useEffect(() => { + atValuesRef.current = atValues; + }, [atValues]); + + useEffect(() => { + onValueChangeRef.current = onValueChange; + }, [onValueChange]); + + useEffect(() => { + externalValueRef.current = value; + }, [value]); + + // Initialize Quill once useEffect(() => { if (!containerRef.current) { return; @@ -140,15 +161,14 @@ export default function MentionEditor({ mentionDenotationChars: ["@"], showDenotationChar: true, source: async function (searchTerm: string, renderList: (values: Match[], searchTerm: string) => void) { + const list = atValuesRef.current || []; if (searchTerm.length === 0) { - renderList(atValues, searchTerm); + renderList(list, 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]); + const matches: Match[] = []; + for (let i = 0; i < list.length; i++) { + if (list[i].value.toLowerCase().indexOf(searchTerm.toLowerCase()) !== -1) { + matches.push(list[i]); } } renderList(matches, searchTerm); @@ -165,15 +185,18 @@ export default function MentionEditor({ }); // clear the quill contents - quill.setContents([]); + quill.setText('', Quill.sources.SILENT); // convert the markdown to parts - const parts = markdownToParts(value, atValues); + const parts = markdownToParts(externalValueRef.current, atValuesRef.current); insertPartsIntoQuill(quill, parts); quill.on(Quill.events.TEXT_CHANGE, (delta: Delta, oldDelta: Delta, source: string) => { - if (onValueChange) { - onValueChange(getMarkdown()); + if (isApplyingExternalRef.current) { + return; + } + if (onValueChangeRef.current) { + onValueChangeRef.current(getMarkdown()); } }); quillRef.current = quill; @@ -193,7 +216,22 @@ export default function MentionEditor({ quillRef.current.off(Quill.events.TEXT_CHANGE); } } - }, [atValues, onValueChange, placeholder, value, autoFocus]); + // Mount once + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + // Sync external value into the editor without re-initializing + useEffect(() => { + if (!quillRef.current) return; + const current = getMarkdown(); + if (value === current) return; + const quill = quillRef.current; + isApplyingExternalRef.current = true; + quill.setText('', Quill.sources.SILENT); + const parts = markdownToParts(value, atValuesRef.current); + insertPartsIntoQuill(quill, parts); + isApplyingExternalRef.current = false; + }, [value]); return
@@ -181,9 +180,9 @@ export function CreateRecurringJobRuleForm({ projectId }: { projectId: string }) onClick={addMessage} variant="secondary" size="sm" - className="flex items-center gap-2" + startContent={} + className="whitespace-nowrap" > - Add Message
@@ -231,7 +230,8 @@ export function CreateRecurringJobRuleForm({ projectId }: { projectId: string }) diff --git a/apps/rowboat/app/projects/[projectId]/job-rules/components/recurring-job-rule-view.tsx b/apps/rowboat/app/projects/[projectId]/job-rules/components/recurring-job-rule-view.tsx index 18c672ff..890f13ff 100644 --- a/apps/rowboat/app/projects/[projectId]/job-rules/components/recurring-job-rule-view.tsx +++ b/apps/rowboat/app/projects/[projectId]/job-rules/components/recurring-job-rule-view.tsx @@ -134,8 +134,7 @@ export function RecurringJobRuleView({ projectId, ruleId }: { projectId: string; title={
- @@ -149,31 +148,21 @@ export function RecurringJobRuleView({ projectId, ruleId }: { projectId: string;
@@ -297,6 +286,7 @@ export function RecurringJobRuleView({ projectId, ruleId }: { projectId: string; variant="secondary" onClick={() => setShowDeleteConfirm(false)} disabled={deleting} + className="whitespace-nowrap" > Cancel @@ -304,19 +294,11 @@ export function RecurringJobRuleView({ projectId, ruleId }: { projectId: string; variant="secondary" onClick={handleDelete} disabled={deleting} - className="flex items-center gap-2 bg-red-50 hover:bg-red-100 text-red-700 dark:bg-red-950 dark:hover:bg-red-900 dark:text-red-400 border border-red-200 dark:border-red-800" + isLoading={deleting} + startContent={} + className="bg-red-50 hover:bg-red-100 text-red-700 dark:bg-red-950 dark:hover:bg-red-900 dark:text-red-400 border border-red-200 dark:border-red-800 whitespace-nowrap" > - {deleting ? ( - <> - - Deleting... - - ) : ( - <> - - Delete - - )} + {deleting ? 'Deleting...' : 'Delete'}
diff --git a/apps/rowboat/app/projects/[projectId]/job-rules/components/recurring-job-rules-list.tsx b/apps/rowboat/app/projects/[projectId]/job-rules/components/recurring-job-rules-list.tsx index 008ae461..99e30593 100644 --- a/apps/rowboat/app/projects/[projectId]/job-rules/components/recurring-job-rules-list.tsx +++ b/apps/rowboat/app/projects/[projectId]/job-rules/components/recurring-job-rules-list.tsx @@ -220,15 +220,10 @@ export function RecurringJobRulesList({ projectId }: { projectId: string }) { disabled={loadingMore} variant="secondary" size="sm" + isLoading={loadingMore} + className="whitespace-nowrap" > - {loadingMore ? ( - <> - - Loading... - - ) : ( - 'Load More' - )} + {loadingMore ? 'Loading...' : 'Load More'} )} diff --git a/apps/rowboat/app/projects/[projectId]/job-rules/components/triggers-tab.tsx b/apps/rowboat/app/projects/[projectId]/job-rules/components/triggers-tab.tsx index a68594db..3fab04b3 100644 --- a/apps/rowboat/app/projects/[projectId]/job-rules/components/triggers-tab.tsx +++ b/apps/rowboat/app/projects/[projectId]/job-rules/components/triggers-tab.tsx @@ -4,12 +4,12 @@ import React, { useState, useEffect, useCallback, useMemo } from 'react'; import { Spinner, Link } from '@heroui/react'; import { Button } from '@/components/ui/button'; import { Panel } from '@/components/common/panel-common'; -import { Plus, Trash2, ZapIcon, ChevronDown, ChevronUp } from 'lucide-react'; +import { Plus, Trash2, ZapIcon, ChevronDown, ChevronUp, ArrowLeftIcon } from 'lucide-react'; import { z } from 'zod'; import { ComposioTriggerDeployment } from '@/src/entities/models/composio-trigger-deployment'; import { ComposioTriggerType } from '@/src/entities/models/composio-trigger-type'; import { isToday, isThisWeek, isThisMonth } from '@/lib/utils/date'; -import { listComposioTriggerDeployments, deleteComposioTriggerDeployment, createComposioTriggerDeployment, listComposioTriggerTypes } from '@/app/actions/composio.actions'; +import { listComposioTriggerDeployments, deleteComposioTriggerDeployment, createComposioTriggerDeployment } from '@/app/actions/composio.actions'; import { SelectComposioToolkit } from '../../tools/components/SelectComposioToolkit'; import { ComposioTriggerTypesPanel } from '../../workflow/components/ComposioTriggerTypesPanel'; import { TriggerConfigForm } from '../../workflow/components/TriggerConfigForm'; @@ -20,6 +20,8 @@ import { fetchProject } from '@/app/actions/project.actions'; type TriggerDeployment = z.infer; +// Removed friendly name computation; backend now provides friendly trigger name + export function TriggersTab({ projectId }: { projectId: string }) { const [triggers, setTriggers] = useState([]); const [loading, setLoading] = useState(true); @@ -31,7 +33,6 @@ export function TriggersTab({ projectId }: { projectId: string }) { const [isSubmittingTrigger, setIsSubmittingTrigger] = useState(false); const [deletingTrigger, setDeletingTrigger] = useState(null); const [projectConfig, setProjectConfig] = useState | null>(null); - const [triggerTypeNames, setTriggerTypeNames] = useState>({}); const [expandedTrigger, setExpandedTrigger] = useState(null); const [cursor, setCursor] = useState(null); const [hasMore, setHasMore] = useState(false); @@ -46,31 +47,6 @@ export function TriggersTab({ projectId }: { projectId: string }) { } }, [projectId]); - const loadTriggerTypeNames = useCallback(async () => { - try { - const names: Record = {}; - - // Get unique toolkit slugs from existing triggers - const uniqueToolkits = [...new Set(triggers.map(t => t.toolkitSlug))]; - - // Fetch trigger types for each toolkit - for (const toolkitSlug of uniqueToolkits) { - try { - const response = await listComposioTriggerTypes(toolkitSlug); - response.items.forEach(triggerType => { - names[triggerType.slug] = triggerType.name; - }); - } catch (err) { - console.error(`Error fetching trigger types for ${toolkitSlug}:`, err); - } - } - - setTriggerTypeNames(names); - } catch (err: any) { - console.error('Error loading trigger type names:', err); - } - }, [triggers]); - const sections = useMemo(() => { const groups: Record = { Today: [], @@ -225,10 +201,8 @@ export function TriggersTab({ projectId }: { projectId: string }) { }, [showCreateFlow, loadTriggers]); useEffect(() => { - if (triggers.length > 0) { - loadTriggerTypeNames(); - } - }, [triggers, loadTriggerTypeNames]); + // No-op: trigger names are now derived from slug locally + }, [triggers]); const renderTriggerList = () => { if (loading) { @@ -261,7 +235,7 @@ export function TriggersTab({ projectId }: { projectId: string }) { } rightActions={ - } @@ -350,12 +324,25 @@ export function TriggersTab({ projectId }: { projectId: string }) {
-
- - Active - +
+ {trigger.logo && ( + {`${trigger.toolkitSlug} + )} + {trigger.toolkitSlug && ( + + {trigger.toolkitSlug} + + )} +
+
+
+ Active - {triggerTypeNames[trigger.triggerTypeSlug] || trigger.triggerTypeSlug} + {trigger.triggerTypeName}
@@ -422,15 +409,10 @@ export function TriggersTab({ projectId }: { projectId: string }) { disabled={loadingMore} variant="secondary" size="sm" + isLoading={loadingMore} + className="whitespace-nowrap" > - {loadingMore ? ( - <> - - Loading... - - ) : ( - 'Load More' - )} + {loadingMore ? 'Loading...' : 'Load More'}
)} @@ -471,8 +453,10 @@ export function TriggersTab({ projectId }: { projectId: string }) {
diff --git a/apps/rowboat/app/projects/[projectId]/job-rules/scheduled/components/create-scheduled-job-rule-form.tsx b/apps/rowboat/app/projects/[projectId]/job-rules/scheduled/components/create-scheduled-job-rule-form.tsx index 1fb4605e..f41aeb3c 100644 --- a/apps/rowboat/app/projects/[projectId]/job-rules/scheduled/components/create-scheduled-job-rule-form.tsx +++ b/apps/rowboat/app/projects/[projectId]/job-rules/scheduled/components/create-scheduled-job-rule-form.tsx @@ -106,8 +106,7 @@ export function CreateScheduledJobRuleForm({ projectId }: { projectId: string }) title={
- @@ -147,9 +146,9 @@ export function CreateScheduledJobRuleForm({ projectId }: { projectId: string }) onClick={addMessage} variant="secondary" size="sm" - className="flex items-center gap-2" + startContent={} + className="whitespace-nowrap" > - Add Message
@@ -197,7 +196,8 @@ export function CreateScheduledJobRuleForm({ projectId }: { projectId: string }) diff --git a/apps/rowboat/app/projects/[projectId]/job-rules/scheduled/components/scheduled-job-rule-view.tsx b/apps/rowboat/app/projects/[projectId]/job-rules/scheduled/components/scheduled-job-rule-view.tsx index 73c07ae3..445a39de 100644 --- a/apps/rowboat/app/projects/[projectId]/job-rules/scheduled/components/scheduled-job-rule-view.tsx +++ b/apps/rowboat/app/projects/[projectId]/job-rules/scheduled/components/scheduled-job-rule-view.tsx @@ -81,8 +81,7 @@ export function ScheduledJobRuleView({ projectId, ruleId }: { projectId: string; title={
- @@ -97,9 +96,9 @@ export function ScheduledJobRuleView({ projectId, ruleId }: { projectId: string; onClick={() => setShowDeleteConfirm(true)} variant="secondary" size="sm" - className="flex items-center gap-2 bg-red-50 hover:bg-red-100 text-red-700 dark:bg-red-950 dark:hover:bg-red-900 dark:text-red-400 border border-red-200 dark:border-red-800" + startContent={} + className="bg-red-50 hover:bg-red-100 text-red-700 dark:bg-red-950 dark:hover:bg-red-900 dark:text-red-400 border border-red-200 dark:border-red-800 whitespace-nowrap" > - Delete
@@ -204,6 +203,7 @@ export function ScheduledJobRuleView({ projectId, ruleId }: { projectId: string; variant="secondary" onClick={() => setShowDeleteConfirm(false)} disabled={deleting} + className="whitespace-nowrap" > Cancel @@ -211,19 +211,11 @@ export function ScheduledJobRuleView({ projectId, ruleId }: { projectId: string; variant="secondary" onClick={handleDelete} disabled={deleting} - className="flex items-center gap-2 bg-red-50 hover:bg-red-100 text-red-700 dark:bg-red-950 dark:hover:bg-red-900 dark:text-red-400 border border-red-200 dark:border-red-800" + isLoading={deleting} + startContent={} + className="bg-red-50 hover:bg-red-100 text-red-700 dark:bg-red-950 dark:hover:bg-red-900 dark:text-red-400 border border-red-200 dark:border-red-800 whitespace-nowrap" > - {deleting ? ( - <> - - Deleting... - - ) : ( - <> - - Delete - - )} + {deleting ? 'Deleting...' : 'Delete'}
diff --git a/apps/rowboat/app/projects/[projectId]/job-rules/scheduled/components/scheduled-job-rules-list.tsx b/apps/rowboat/app/projects/[projectId]/job-rules/scheduled/components/scheduled-job-rules-list.tsx index da980452..69c1d33f 100644 --- a/apps/rowboat/app/projects/[projectId]/job-rules/scheduled/components/scheduled-job-rules-list.tsx +++ b/apps/rowboat/app/projects/[projectId]/job-rules/scheduled/components/scheduled-job-rules-list.tsx @@ -190,15 +190,10 @@ export function ScheduledJobRulesList({ projectId }: { projectId: string }) { disabled={loadingMore} variant="secondary" size="sm" + isLoading={loadingMore} + className="whitespace-nowrap" > - {loadingMore ? ( - <> - - Loading... - - ) : ( - 'Load More' - )} + {loadingMore ? 'Loading...' : 'Load More'}
)} diff --git a/apps/rowboat/app/projects/[projectId]/workflow/components/ComposioTriggerTypesPanel.tsx b/apps/rowboat/app/projects/[projectId]/workflow/components/ComposioTriggerTypesPanel.tsx index 3e906be7..41426bf5 100644 --- a/apps/rowboat/app/projects/[projectId]/workflow/components/ComposioTriggerTypesPanel.tsx +++ b/apps/rowboat/app/projects/[projectId]/workflow/components/ComposioTriggerTypesPanel.tsx @@ -1,12 +1,13 @@ 'use client'; import React, { useState, useEffect, useCallback } from 'react'; -import { Button, Card, CardBody, CardHeader, Spinner } from '@heroui/react'; +import { Button, Card, CardBody, Spinner } from '@heroui/react'; import { ChevronLeft, ChevronRight, ZapIcon, ArrowLeft } from 'lucide-react'; import { z } from 'zod'; import { ComposioTriggerType } from '@/src/entities/models/composio-trigger-type'; import { listComposioTriggerTypes } from '@/app/actions/composio.actions'; import { ZToolkit } from "@/src/application/lib/composio/types"; +import { PictureImg } from '@/components/ui/picture-img'; interface ComposioTriggerTypesPanelProps { toolkit: z.infer; @@ -151,32 +152,42 @@ export function ComposioTriggerTypesPanel({ ) : (
-
+
{triggerTypes.map((triggerType) => ( - handleTriggerTypeSelect(triggerType)} > - -
- -
-
-

+

+ {toolkit.meta?.logo ? ( + + ) : ( +
+ +
+ )} +
+

{triggerType.name} +

+
+
+ +
+

+ {triggerType.description}

- - -

- {triggerType.description} -

-
-