Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/integrations/ai-image/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,5 +74,5 @@
"metadata": {
"modelCapabilities": "Configured model: {{model}}\nModel capabilities:\n- gpt-image-1: sizes [1024x1024, 1024x1536, 1536x1024, auto], supports edit\n- dall-e-3: sizes [1024x1024, 1024x1792, 1792x1024], does NOT support edit\n- dall-e-2: sizes [256x256, 512x512, 1024x1024], supports edit\nIMPORTANT: Only use sizes supported by the configured model. Invalid sizes will cause an error."
},
"setupGuide": "### Configuration Guide\n\n1. **domain** — Your API provider's base URL:\n - OpenAI: `https://api.openai.com`\n - Together AI: `https://api.together.xyz`\n\n2. **accessToken** — Your API key from the provider\n\n3. **model** — The image generation model ID:\n - `gpt-image-1` — OpenAI's latest image model (sizes: 1024x1024, 1024x1536, 1536x1024, auto)\n - `dall-e-3` — DALL-E 3 (sizes: 1024x1024, 1024x1792, 1792x1024, no edit support)\n - `dall-e-2` — DALL-E 2 (sizes: 256x256, 512x512, 1024x1024)"
"setupGuide": "1. **domain** — Your API provider's base URL:\n - OpenAI: `https://api.openai.com`\n - Together AI: `https://api.together.xyz`\n\n2. **accessToken** — Your API key from the provider\n\n3. **model** — The image generation model ID:\n - `gpt-image-1` — OpenAI's latest image model (sizes: 1024x1024, 1024x1536, 1536x1024, auto)\n - `dall-e-3` — DALL-E 3 (sizes: 1024x1024, 1024x1792, 1792x1024, no edit support)\n - `dall-e-2` — DALL-E 2 (sizes: 256x256, 512x512, 1024x1024)"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
'use client';

import { Info } from 'lucide-react';
import { useState } from 'react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';

import { markdownWrapperStyles } from '@/app/features/chat/components/message-bubble/markdown-renderer';
import { cn } from '@/lib/utils/cn';

interface CollapsibleGuideProps {
label: string;
content: string;
defaultOpen?: boolean;
}

export function CollapsibleGuide({
label,
content,
defaultOpen,
}: CollapsibleGuideProps) {
const [isOpen, setIsOpen] = useState(Boolean(defaultOpen));

return (
<details
className="bg-muted/30 border-border rounded-lg border"
open={isOpen}
onToggle={(event) => setIsOpen(event.currentTarget.open)}
>
Comment on lines +25 to +29
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n services/platform/app/components/ui/data-display/collapsible-guide.tsx

Repository: tale-project/tale

Length of output: 1407


🏁 Script executed:

rg -l "CollapsibleGuide" --type tsx --type ts

Repository: tale-project/tale

Length of output: 88


🏁 Script executed:

rg "CollapsibleGuide" --type ts --type js

Repository: tale-project/tale

Length of output: 1000


🏁 Script executed:

rg -A 5 "CollapsibleGuide" services/platform/app/features/settings/providers/components/provider-add-panel.tsx | head -20

Repository: tale-project/tale

Length of output: 640


🏁 Script executed:

rg -A 5 "CollapsibleGuide" services/platform/app/features/settings/integrations/components/integration-manage/integration-credentials-form.tsx | head -20

Repository: tale-project/tale

Length of output: 606


defaultOpen is wired as a controlled open prop, causing user-toggled state to reset on parent rerender.

When a parent component rerenders, React will update the <details> element's open attribute to the defaultOpen value, overriding any user interactions (like closing the disclosure). This breaks the expected "default-only" behavior.

Proposed fix
 import { Info } from 'lucide-react';
+import { useState } from 'react';
 import ReactMarkdown from 'react-markdown';
 import remarkGfm from 'remark-gfm';
@@
 export function CollapsibleGuide({
   label,
   content,
   defaultOpen,
 }: CollapsibleGuideProps) {
+  const [isOpen, setIsOpen] = useState(Boolean(defaultOpen));
+
   return (
     <details
       className="bg-muted/30 border-border rounded-lg border"
-      open={defaultOpen}
+      open={isOpen}
+      onToggle={(event) => setIsOpen(event.currentTarget.open)}
     >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/app/components/ui/data-display/collapsible-guide.tsx`
around lines 22 - 25, The <details> is currently controlled via
open={defaultOpen}, which forces its state on every parent render; remove the
controlled prop and instead set the initial open state only on mount (e.g., use
a ref for the <details> element and in useEffect set detailsRef.current.open =
defaultOpen once). Update the component in collapsible-guide.tsx to stop passing
open={defaultOpen} and implement the useRef/useEffect initialization so user
toggles remain uncontrolled after mount.

<summary className="flex cursor-pointer items-center gap-2 px-3 py-2 text-sm font-medium">
<Info className="text-muted-foreground size-3.5 shrink-0" />
{label}
</summary>
<div
className={cn(
markdownWrapperStyles,
'max-w-none border-t border-border px-3 py-2 text-xs leading-relaxed',
)}
>
<ReactMarkdown remarkPlugins={[remarkGfm]}>{content}</ReactMarkdown>
</div>
</details>
);
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
'use client';

import { Info, Loader2, Pencil, Save } from 'lucide-react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import { Loader2, Pencil, Save } from 'lucide-react';

import { CollapsibleGuide } from '@/app/components/ui/data-display/collapsible-guide';
import { Badge } from '@/app/components/ui/feedback/badge';
import { Input } from '@/app/components/ui/forms/input';
import { Select } from '@/app/components/ui/forms/select';
Expand All @@ -12,9 +11,7 @@ import { HStack, Stack } from '@/app/components/ui/layout/layout';
import { Button } from '@/app/components/ui/primitives/button';
import { IconButton } from '@/app/components/ui/primitives/icon-button';
import { Text } from '@/app/components/ui/typography/text';
import { markdownWrapperStyles } from '@/app/features/chat/components/message-bubble/markdown-renderer';
import { useT } from '@/lib/i18n/client';
import { cn } from '@/lib/utils/cn';
import { startCase } from '@/lib/utils/string';

import type { Integration } from '../../hooks/use-integration-manage';
Expand Down Expand Up @@ -105,24 +102,11 @@ export function IntegrationCredentialsForm({
/>
)}

{/* Setup Guide */}
{typeof integration.setupGuide === 'string' && (
<details className="bg-muted/30 border-border rounded-lg border">
<summary className="flex cursor-pointer items-center gap-2 px-3 py-2 text-sm font-medium">
<Info className="text-muted-foreground size-3.5 shrink-0" />
{t('integrations.manageDialog.setupGuide')}
</summary>
<div
className={cn(
markdownWrapperStyles,
'max-w-none border-t border-border px-3 py-2 text-xs leading-relaxed',
)}
>
<ReactMarkdown remarkPlugins={[remarkGfm]}>
{integration.setupGuide}
</ReactMarkdown>
</div>
</details>
<CollapsibleGuide
label={t('integrations.manageDialog.setupGuide')}
content={integration.setupGuide}
/>
)}

<Stack gap={3}>
Expand Down
Loading
Loading