diff --git a/apps/docs/docs/management/custom-widgets/authoring.mdx b/apps/docs/docs/management/custom-widgets/authoring.mdx index 429827c55..aa2a18805 100644 --- a/apps/docs/docs/management/custom-widgets/authoring.mdx +++ b/apps/docs/docs/management/custom-widgets/authoring.mdx @@ -25,10 +25,10 @@ Every option has a default. Supported controls include text, textarea, number, s ## ChatGPT or Claude -Enter the desired widget and optional API documentation URL, then select **Copy AI prompt**. The single self-contained prompt is below 12,000 characters, starts with your request, includes the lean contract and two polished examples, and never claims that an offline chat has MCP tools. +Open **Create or fix with any AI**, enter the desired widget and optional API documentation URL, then select the copy action beside the accordion title. The single self-contained prompt is below 12,000 characters, starts with your request, includes the lean contract and two polished examples, and never claims that an offline chat has MCP tools. The model must return one fenced `json` block containing the complete widget, including the JSX in its `template` -field. Copy that single block and select **Paste AI response**. Homarr fills the visual builders and the JSX editor +field. Copy that single block and select **Paste**. Homarr fills the visual builders and the JSX editor without any placeholder replacement or second paste. Homarr shows all actionable schema or JSX errors inline. Enter credentials separately. The prompt deliberately diff --git a/apps/nextjs/src/app/[locale]/manage/custom-widgets/_copy-ai-prompt-button.spec.tsx b/apps/nextjs/src/app/[locale]/manage/custom-widgets/_copy-ai-prompt-button.spec.tsx index 5e50b6127..3ad283ea4 100644 --- a/apps/nextjs/src/app/[locale]/manage/custom-widgets/_copy-ai-prompt-button.spec.tsx +++ b/apps/nextjs/src/app/[locale]/manage/custom-widgets/_copy-ai-prompt-button.spec.tsx @@ -30,7 +30,13 @@ describe("Copy AI prompt", () => { `${process.cwd()}/apps/nextjs/src/app/[locale]/manage/custom-widgets/_custom-widget-form.module.css`, "utf8", ); - expect(card).toContain(""); + const actions = readFileSync( + `${process.cwd()}/apps/nextjs/src/app/[locale]/manage/custom-widgets/_copy-ai-prompt-button.tsx`, + "utf8", + ); + expect(card).toContain(''); + expect(card).not.toContain("defaultValue"); + expect(actions).toContain(" a"); expect(styles).toContain("flex: 0 0 auto"); }); diff --git a/apps/nextjs/src/app/[locale]/manage/custom-widgets/_copy-ai-prompt-button.tsx b/apps/nextjs/src/app/[locale]/manage/custom-widgets/_copy-ai-prompt-button.tsx index dfbeff7d4..48d95f0bc 100644 --- a/apps/nextjs/src/app/[locale]/manage/custom-widgets/_copy-ai-prompt-button.tsx +++ b/apps/nextjs/src/app/[locale]/manage/custom-widgets/_copy-ai-prompt-button.tsx @@ -1,7 +1,7 @@ "use client"; -import { Button } from "@mantine/core"; -import { IconSparkles } from "@tabler/icons-react"; +import { ActionIcon, Tooltip } from "@mantine/core"; +import { IconCopy } from "@tabler/icons-react"; import { buildCustomWidgetAiPrompt } from "@homarr/custom-widgets/authoring-prompt"; import { showErrorNotification, showSuccessNotification } from "@homarr/notifications"; @@ -36,15 +36,16 @@ export const CopyAiPromptButton = ({ }; return ( - + + void handleCopy()} + > + + + ); }; diff --git a/apps/nextjs/src/app/[locale]/manage/custom-widgets/_custom-widget-ai-card.tsx b/apps/nextjs/src/app/[locale]/manage/custom-widgets/_custom-widget-ai-card.tsx index 86fa64951..3e817a595 100644 --- a/apps/nextjs/src/app/[locale]/manage/custom-widgets/_custom-widget-ai-card.tsx +++ b/apps/nextjs/src/app/[locale]/manage/custom-widgets/_custom-widget-ai-card.tsx @@ -1,7 +1,7 @@ "use client"; -import { Button, Card, Group, SimpleGrid, Stack, Text, TextInput, Textarea } from "@mantine/core"; -import { IconSparkles } from "@tabler/icons-react"; +import { Accordion, Button, Group, Stack, Text, TextInput, Textarea } from "@mantine/core"; +import { IconClipboard, IconSparkles } from "@tabler/icons-react"; import type { HomarrCustomWidgetV2 } from "@homarr/custom-widgets/core"; import { useScopedI18n } from "@homarr/translation/client"; @@ -20,38 +20,41 @@ interface AiCardProps { export function CustomWidgetAiCard(props: AiCardProps) { const t = useScopedI18n("customWidget.workbench.ai"); return ( - - - - - {t("title")} - - - {t("description")} - -