Compare commits

..

1 Commits

Author SHA1 Message Date
Zamil Majdy
11cfd8756c fix(backend): standardize microservice host/port configuration
- Change agentgenerator_host default from empty string to "localhost"
  - Consistent with other client services (rabbitmq, redis, clamav)
  - Fixes "service_not_configured" error when only port is set
- Change agentgenerator_port default from 8000 to 8009
  - Avoids conflict with Kong gateway (port 8000)
  - Follows sequential port allocation (8001-8008 already in use)
- Add AGENTGENERATOR_HOST to .env.default for clarity

This standardization ensures:
1. Consistent host naming across all microservice client configurations
2. No port conflicts with Kong or other services
3. Agent Generator service works out of the box when enabled
2026-02-21 16:48:51 +07:00
15 changed files with 284 additions and 428 deletions

View File

@@ -192,3 +192,8 @@ POSTHOG_HOST=https://eu.i.posthog.com
# Other Services # Other Services
AUTOMOD_API_KEY= AUTOMOD_API_KEY=
# Agent Generator Service
# The Agent Generator microservice handles AI-powered agent creation from natural language
AGENTGENERATOR_HOST=localhost
AGENTGENERATOR_PORT=8009

View File

@@ -364,11 +364,11 @@ class Config(UpdateTrackingModel["Config"], BaseSettings):
) )
agentgenerator_host: str = Field( agentgenerator_host: str = Field(
default="", default="localhost",
description="The host for the Agent Generator service (empty to use built-in)", description="The host for the Agent Generator service",
) )
agentgenerator_port: int = Field( agentgenerator_port: int = Field(
default=8000, default=8009,
description="The port for the Agent Generator service", description="The port for the Agent Generator service",
) )
agentgenerator_timeout: int = Field( agentgenerator_timeout: int = Field(

View File

@@ -11,11 +11,6 @@ import {
MessageResponse, MessageResponse,
} from "@/components/ai-elements/message"; } from "@/components/ai-elements/message";
import { Text } from "@/components/atoms/Text/Text"; import { Text } from "@/components/atoms/Text/Text";
import {
CredentialsProvidersContext,
type CredentialsProviderData,
type CredentialsProvidersContextType,
} from "@/providers/agent-credentials/credentials-provider";
import { CopilotChatActionsProvider } from "../components/CopilotChatActionsProvider/CopilotChatActionsProvider"; import { CopilotChatActionsProvider } from "../components/CopilotChatActionsProvider/CopilotChatActionsProvider";
import { CreateAgentTool } from "../tools/CreateAgent/CreateAgent"; import { CreateAgentTool } from "../tools/CreateAgent/CreateAgent";
import { EditAgentTool } from "../tools/EditAgent/EditAgent"; import { EditAgentTool } from "../tools/EditAgent/EditAgent";
@@ -102,65 +97,6 @@ function uid() {
return `sg-${++_id}`; return `sg-${++_id}`;
} }
// ---------------------------------------------------------------------------
// Mock credential providers for setup-requirements demos
// ---------------------------------------------------------------------------
const noop = () => Promise.reject(new Error("Styleguide mock"));
function makeMockProvider(
provider: string,
providerName: string,
savedCredentials: CredentialsProviderData["savedCredentials"] = [],
): CredentialsProviderData {
return {
provider,
providerName,
savedCredentials,
isSystemProvider: false,
oAuthCallback: noop as CredentialsProviderData["oAuthCallback"],
mcpOAuthCallback: noop as CredentialsProviderData["mcpOAuthCallback"],
createAPIKeyCredentials:
noop as CredentialsProviderData["createAPIKeyCredentials"],
createUserPasswordCredentials:
noop as CredentialsProviderData["createUserPasswordCredentials"],
createHostScopedCredentials:
noop as CredentialsProviderData["createHostScopedCredentials"],
deleteCredentials: noop as CredentialsProviderData["deleteCredentials"],
};
}
/**
* Provider context where the user already has saved credentials
* so the credential picker shows a selection list.
*/
const MOCK_PROVIDERS_WITH_CREDENTIALS: CredentialsProvidersContextType = {
google: makeMockProvider("google", "Google", [
{
id: "cred-google-1",
provider: "google",
type: "oauth2",
title: "work@company.com",
scopes: ["email", "calendar"],
},
{
id: "cred-google-2",
provider: "google",
type: "oauth2",
title: "personal@gmail.com",
scopes: ["email", "calendar"],
},
]),
};
/**
* Provider context where the user has NO saved credentials,
* so the credential picker shows an "add new" flow.
*/
const MOCK_PROVIDERS_WITHOUT_CREDENTIALS: CredentialsProvidersContextType = {
openweathermap: makeMockProvider("openweathermap", "OpenWeatherMap"),
};
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Page // Page
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -618,80 +554,45 @@ export default function StyleguidePage() {
/> />
</SubSection> </SubSection>
<SubSection label="Setup requirements — no credentials (add new)"> <SubSection label="Output available (setup requirements)">
<CredentialsProvidersContext.Provider <RunBlockTool
value={MOCK_PROVIDERS_WITHOUT_CREDENTIALS} part={{
> type: "tool-run_block",
<RunBlockTool toolCallId: uid(),
part={{ state: "output-available",
type: "tool-run_block", input: { block_id: "weather-block-123" },
toolCallId: uid(), output: {
state: "output-available", type: ResponseType.setup_requirements,
input: { block_id: "weather-block-123" }, message:
output: { "This block requires API credentials to run. Please configure them below.",
type: ResponseType.setup_requirements, setup_info: {
message: agent_name: "Weather Agent",
"This block requires API credentials to run. Please configure them below.", requirements: {
setup_info: { inputs: [
agent_id: "agent-weather-1", {
agent_name: "Weather Agent", name: "city",
requirements: { title: "City",
inputs: [ type: "string",
{ required: true,
name: "city", description: "The city to get weather for",
title: "City", },
type: "string", ],
required: true, },
description: "The city to get weather for", user_readiness: {
}, missing_credentials: {
], openweathermap: {
}, provider: "openweathermap",
user_readiness: { credentials_type: "api_key",
missing_credentials: { title: "OpenWeatherMap API Key",
openweathermap_key: { description:
provider: "openweathermap", "Required to access weather data. Get your key at openweathermap.org",
types: ["api_key"],
},
}, },
}, },
}, },
}, },
}} },
/> }}
</CredentialsProvidersContext.Provider> />
</SubSection>
<SubSection label="Setup requirements — has credentials (pick from list)">
<CredentialsProvidersContext.Provider
value={MOCK_PROVIDERS_WITH_CREDENTIALS}
>
<RunBlockTool
part={{
type: "tool-run_block",
toolCallId: uid(),
state: "output-available",
input: { block_id: "calendar-block-456" },
output: {
type: ResponseType.setup_requirements,
message:
"This block requires Google credentials. Pick an account below or connect a new one.",
setup_info: {
agent_id: "agent-calendar-1",
agent_name: "Calendar Agent",
user_readiness: {
missing_credentials: {
google_oauth: {
provider: "google",
types: ["oauth2"],
scopes: ["email", "calendar"],
},
},
},
},
},
}}
/>
</CredentialsProvidersContext.Provider>
</SubSection> </SubSection>
<SubSection label="Output available (error)"> <SubSection label="Output available (error)">
@@ -948,71 +849,34 @@ export default function StyleguidePage() {
/> />
</SubSection> </SubSection>
<SubSection label="Setup requirements — no credentials (add new)"> <SubSection label="Output available (setup requirements)">
<CredentialsProvidersContext.Provider <RunAgentTool
value={MOCK_PROVIDERS_WITHOUT_CREDENTIALS} part={{
> type: "tool-run_agent",
<RunAgentTool toolCallId: uid(),
part={{ state: "output-available",
type: "tool-run_agent", input: { username_agent_slug: "creator/my-agent" },
toolCallId: uid(), output: {
state: "output-available", type: ResponseType.setup_requirements,
input: { username_agent_slug: "creator/weather-agent" }, message: "This agent requires additional setup.",
output: { setup_info: {
type: ResponseType.setup_requirements, agent_name: "YouTube Summarizer",
message: requirements: {},
"This agent requires an API key. Add your credentials below.", user_readiness: {
setup_info: { missing_credentials: {
agent_id: "agent-weather-1", youtube_api: {
agent_name: "Weather Agent", provider: "youtube",
requirements: {}, credentials_type: "api_key",
user_readiness: { title: "YouTube Data API Key",
missing_credentials: { description:
openweathermap_key: { "Required to access YouTube video data.",
provider: "openweathermap",
types: ["api_key"],
},
}, },
}, },
}, },
}, },
}} },
/> }}
</CredentialsProvidersContext.Provider> />
</SubSection>
<SubSection label="Setup requirements — has credentials (pick from list)">
<CredentialsProvidersContext.Provider
value={MOCK_PROVIDERS_WITH_CREDENTIALS}
>
<RunAgentTool
part={{
type: "tool-run_agent",
toolCallId: uid(),
state: "output-available",
input: { username_agent_slug: "creator/calendar-agent" },
output: {
type: ResponseType.setup_requirements,
message:
"This agent needs Google credentials. Pick an account or connect a new one.",
setup_info: {
agent_id: "agent-calendar-1",
agent_name: "Google Calendar Agent",
requirements: {},
user_readiness: {
missing_credentials: {
google_oauth: {
provider: "google",
types: ["oauth2"],
scopes: ["email", "calendar"],
},
},
},
},
},
}}
/>
</CredentialsProvidersContext.Provider>
</SubSection> </SubSection>
<SubSection label="Output available (need login)"> <SubSection label="Output available (need login)">

View File

@@ -16,6 +16,7 @@ import {
ContentCardDescription, ContentCardDescription,
ContentCodeBlock, ContentCodeBlock,
ContentGrid, ContentGrid,
ContentHint,
ContentMessage, ContentMessage,
} from "../../components/ToolAccordion/AccordionContent"; } from "../../components/ToolAccordion/AccordionContent";
import { ToolAccordion } from "../../components/ToolAccordion/ToolAccordion"; import { ToolAccordion } from "../../components/ToolAccordion/ToolAccordion";
@@ -23,8 +24,8 @@ import {
ClarificationQuestionsCard, ClarificationQuestionsCard,
ClarifyingQuestion, ClarifyingQuestion,
} from "./components/ClarificationQuestionsCard"; } from "./components/ClarificationQuestionsCard";
import sparklesImg from "../../components/MiniGame/assets/sparkles.png"; import sparklesImg from "./components/MiniGame/assets/sparkles.png";
import { MiniGame } from "../../components/MiniGame/MiniGame"; import { MiniGame } from "./components/MiniGame/MiniGame";
import { SuggestedGoalCard } from "./components/SuggestedGoalCard"; import { SuggestedGoalCard } from "./components/SuggestedGoalCard";
import { import {
AccordionIcon, AccordionIcon,
@@ -92,7 +93,9 @@ function getAccordionMeta(output: CreateAgentToolOutput) {
) { ) {
return { return {
icon, icon,
title: output.message || "Agent creation started", title:
"Creating agent, this may take a few minutes. Play while you wait.",
expanded: true,
}; };
} }
return { return {
@@ -166,22 +169,15 @@ export function CreateAgentTool({ part }: Props) {
/> />
</div> </div>
{isStreaming && (
<ToolAccordion
icon={<AccordionIcon />}
title="Creating agent, this may take a few minutes. Play while you wait."
expanded
>
<ContentGrid>
<MiniGame />
</ContentGrid>
</ToolAccordion>
)}
{hasExpandableContent && output && ( {hasExpandableContent && output && (
<ToolAccordion {...getAccordionMeta(output)}> <ToolAccordion {...getAccordionMeta(output)}>
{isOperating && output.message && ( {isOperating && (
<ContentMessage>{output.message}</ContentMessage> <ContentGrid>
<MiniGame />
<ContentHint>
This could take a few minutes play while you wait!
</ContentHint>
</ContentGrid>
)} )}
{isAgentSavedOutput(output) && ( {isAgentSavedOutput(output) && (

View File

@@ -4,15 +4,17 @@ import { WarningDiamondIcon } from "@phosphor-icons/react";
import type { ToolUIPart } from "ai"; import type { ToolUIPart } from "ai";
import { useCopilotChatActions } from "../../components/CopilotChatActionsProvider/useCopilotChatActions"; import { useCopilotChatActions } from "../../components/CopilotChatActionsProvider/useCopilotChatActions";
import { MorphingTextAnimation } from "../../components/MorphingTextAnimation/MorphingTextAnimation"; import { MorphingTextAnimation } from "../../components/MorphingTextAnimation/MorphingTextAnimation";
import { OrbitLoader } from "../../components/OrbitLoader/OrbitLoader";
import { import {
ContentCardDescription, ContentCardDescription,
ContentCodeBlock, ContentCodeBlock,
ContentGrid, ContentGrid,
ContentHint,
ContentLink, ContentLink,
ContentMessage, ContentMessage,
} from "../../components/ToolAccordion/AccordionContent"; } from "../../components/ToolAccordion/AccordionContent";
import { ToolAccordion } from "../../components/ToolAccordion/ToolAccordion"; import { ToolAccordion } from "../../components/ToolAccordion/ToolAccordion";
import { MiniGame } from "../../components/MiniGame/MiniGame"; import { MiniGame } from "../CreateAgent/components/MiniGame/MiniGame";
import { import {
ClarificationQuestionsCard, ClarificationQuestionsCard,
ClarifyingQuestion, ClarifyingQuestion,
@@ -79,8 +81,9 @@ function getAccordionMeta(output: EditAgentToolOutput): {
isOperationInProgressOutput(output) isOperationInProgressOutput(output)
) { ) {
return { return {
icon, icon: <OrbitLoader size={32} />,
title: output.message || "Agent editing started", title: "Editing agent, this may take a few minutes. Play while you wait.",
expanded: true,
}; };
} }
return { return {
@@ -145,22 +148,15 @@ export function EditAgentTool({ part }: Props) {
/> />
</div> </div>
{isStreaming && (
<ToolAccordion
icon={<AccordionIcon />}
title="Editing agent, this may take a few minutes. Play while you wait."
expanded
>
<ContentGrid>
<MiniGame />
</ContentGrid>
</ToolAccordion>
)}
{hasExpandableContent && output && ( {hasExpandableContent && output && (
<ToolAccordion {...getAccordionMeta(output)}> <ToolAccordion {...getAccordionMeta(output)}>
{isOperating && output.message && ( {isOperating && (
<ContentMessage>{output.message}</ContentMessage> <ContentGrid>
<MiniGame />
<ContentHint>
This could take a few minutes play while you wait!
</ContentHint>
</ContentGrid>
)} )}
{isAgentSavedOutput(output) && ( {isAgentSavedOutput(output) && (

View File

@@ -9,7 +9,7 @@ import {
ContentHint, ContentHint,
ContentMessage, ContentMessage,
} from "../../components/ToolAccordion/AccordionContent"; } from "../../components/ToolAccordion/AccordionContent";
import { MiniGame } from "../../components/MiniGame/MiniGame"; import { MiniGame } from "../CreateAgent/components/MiniGame/MiniGame";
import { import {
getAccordionMeta, getAccordionMeta,
getAnimationText, getAnimationText,
@@ -47,25 +47,14 @@ export function RunAgentTool({ part }: Props) {
const isError = const isError =
part.state === "output-error" || part.state === "output-error" ||
(!!output && isRunAgentErrorOutput(output)); (!!output && isRunAgentErrorOutput(output));
const isOutputAvailable = part.state === "output-available" && !!output;
const setupRequirementsOutput =
isOutputAvailable && isRunAgentSetupRequirementsOutput(output)
? output
: null;
const agentDetailsOutput =
isOutputAvailable && isRunAgentAgentDetailsOutput(output) ? output : null;
const needLoginOutput =
isOutputAvailable && isRunAgentNeedLoginOutput(output) ? output : null;
const hasExpandableContent = const hasExpandableContent =
isOutputAvailable && part.state === "output-available" &&
!setupRequirementsOutput && !!output &&
!agentDetailsOutput && (isRunAgentExecutionStartedOutput(output) ||
!needLoginOutput && isRunAgentAgentDetailsOutput(output) ||
(isRunAgentExecutionStartedOutput(output) || isRunAgentErrorOutput(output)); isRunAgentSetupRequirementsOutput(output) ||
isRunAgentNeedLoginOutput(output) ||
isRunAgentErrorOutput(output));
return ( return (
<div className="py-2"> <div className="py-2">
@@ -92,30 +81,24 @@ export function RunAgentTool({ part }: Props) {
</ToolAccordion> </ToolAccordion>
)} )}
{setupRequirementsOutput && (
<div className="mt-2">
<SetupRequirementsCard output={setupRequirementsOutput} />
</div>
)}
{agentDetailsOutput && (
<div className="mt-2">
<AgentDetailsCard output={agentDetailsOutput} />
</div>
)}
{needLoginOutput && (
<div className="mt-2">
<ContentMessage>{needLoginOutput.message}</ContentMessage>
</div>
)}
{hasExpandableContent && output && ( {hasExpandableContent && output && (
<ToolAccordion {...getAccordionMeta(output)}> <ToolAccordion {...getAccordionMeta(output)}>
{isRunAgentExecutionStartedOutput(output) && ( {isRunAgentExecutionStartedOutput(output) && (
<ExecutionStartedCard output={output} /> <ExecutionStartedCard output={output} />
)} )}
{isRunAgentAgentDetailsOutput(output) && (
<AgentDetailsCard output={output} />
)}
{isRunAgentSetupRequirementsOutput(output) && (
<SetupRequirementsCard output={output} />
)}
{isRunAgentNeedLoginOutput(output) && (
<ContentMessage>{output.message}</ContentMessage>
)}
{isRunAgentErrorOutput(output) && <ErrorCard output={output} />} {isRunAgentErrorOutput(output) && <ErrorCard output={output} />}
</ToolAccordion> </ToolAccordion>
)} )}

View File

@@ -1,11 +1,10 @@
"use client"; "use client";
import type { SetupRequirementsResponse } from "@/app/api/__generated__/models/setupRequirementsResponse";
import { Button } from "@/components/atoms/Button/Button";
import { Text } from "@/components/atoms/Text/Text";
import { CredentialsGroupedView } from "@/components/contextual/CredentialsInput/components/CredentialsGroupedView/CredentialsGroupedView";
import type { CredentialsMetaInput } from "@/lib/autogpt-server-api/types";
import { useState } from "react"; import { useState } from "react";
import { CredentialsGroupedView } from "@/components/contextual/CredentialsInput/components/CredentialsGroupedView/CredentialsGroupedView";
import { Button } from "@/components/atoms/Button/Button";
import type { CredentialsMetaInput } from "@/lib/autogpt-server-api/types";
import type { SetupRequirementsResponse } from "@/app/api/__generated__/models/setupRequirementsResponse";
import { useCopilotChatActions } from "../../../../components/CopilotChatActionsProvider/useCopilotChatActions"; import { useCopilotChatActions } from "../../../../components/CopilotChatActionsProvider/useCopilotChatActions";
import { import {
ContentBadge, ContentBadge,
@@ -39,40 +38,40 @@ export function SetupRequirementsCard({ output }: Props) {
setInputCredentials((prev) => ({ ...prev, [key]: value })); setInputCredentials((prev) => ({ ...prev, [key]: value }));
} }
const needsCredentials = credentialFields.length > 0; const isAllComplete =
const isAllCredentialsComplete = credentialFields.length > 0 &&
needsCredentials &&
[...requiredCredentials].every((key) => !!inputCredentials[key]); [...requiredCredentials].every((key) => !!inputCredentials[key]);
const canProceed =
!hasSent && (!needsCredentials || isAllCredentialsComplete);
function handleProceed() { function handleProceed() {
setHasSent(true); setHasSent(true);
const message = needsCredentials onSend(
? "I've configured the required credentials. Please check if everything is ready and proceed with running the agent." "I've configured the required credentials. Please check if everything is ready and proceed with running the agent.",
: "Please proceed with running the agent."; );
onSend(message);
} }
return ( return (
<div className="grid gap-2"> <div className="grid gap-2">
<ContentMessage>{output.message}</ContentMessage> <ContentMessage>{output.message}</ContentMessage>
{needsCredentials && ( {credentialFields.length > 0 && (
<div className="rounded-2xl border bg-background p-3"> <div className="rounded-2xl border bg-background p-3">
<Text variant="small" className="w-fit border-b text-zinc-500"> <CredentialsGroupedView
Agent credentials credentialFields={credentialFields}
</Text> requiredCredentials={requiredCredentials}
<div className="mt-6"> inputCredentials={inputCredentials}
<CredentialsGroupedView inputValues={{}}
credentialFields={credentialFields} onCredentialChange={handleCredentialChange}
requiredCredentials={requiredCredentials} />
inputCredentials={inputCredentials} {isAllComplete && !hasSent && (
inputValues={{}} <Button
onCredentialChange={handleCredentialChange} variant="primary"
/> size="small"
</div> className="mt-3 w-full"
onClick={handleProceed}
>
Proceed
</Button>
)}
</div> </div>
)} )}
@@ -101,18 +100,6 @@ export function SetupRequirementsCard({ output }: Props) {
</div> </div>
</div> </div>
)} )}
{(needsCredentials || expectedInputs.length > 0) && (
<Button
variant="primary"
size="small"
className="mt-4 w-fit"
disabled={!canProceed}
onClick={handleProceed}
>
Proceed
</Button>
)}
</div> </div>
); );
} }

View File

@@ -39,19 +39,12 @@ export function RunBlockTool({ part }: Props) {
const isError = const isError =
part.state === "output-error" || part.state === "output-error" ||
(!!output && isRunBlockErrorOutput(output)); (!!output && isRunBlockErrorOutput(output));
const setupRequirementsOutput =
part.state === "output-available" &&
output &&
isRunBlockSetupRequirementsOutput(output)
? output
: null;
const hasExpandableContent = const hasExpandableContent =
part.state === "output-available" && part.state === "output-available" &&
!!output && !!output &&
!setupRequirementsOutput &&
(isRunBlockBlockOutput(output) || (isRunBlockBlockOutput(output) ||
isRunBlockDetailsOutput(output) || isRunBlockDetailsOutput(output) ||
isRunBlockSetupRequirementsOutput(output) ||
isRunBlockErrorOutput(output)); isRunBlockErrorOutput(output));
return ( return (
@@ -64,12 +57,6 @@ export function RunBlockTool({ part }: Props) {
/> />
</div> </div>
{setupRequirementsOutput && (
<div className="mt-2">
<SetupRequirementsCard output={setupRequirementsOutput} />
</div>
)}
{hasExpandableContent && output && ( {hasExpandableContent && output && (
<ToolAccordion {...getAccordionMeta(output)}> <ToolAccordion {...getAccordionMeta(output)}>
{isRunBlockBlockOutput(output) && <BlockOutputCard output={output} />} {isRunBlockBlockOutput(output) && <BlockOutputCard output={output} />}
@@ -78,6 +65,10 @@ export function RunBlockTool({ part }: Props) {
<BlockDetailsCard output={output} /> <BlockDetailsCard output={output} />
)} )}
{isRunBlockSetupRequirementsOutput(output) && (
<SetupRequirementsCard output={output} />
)}
{isRunBlockErrorOutput(output) && <ErrorCard output={output} />} {isRunBlockErrorOutput(output) && <ErrorCard output={output} />}
</ToolAccordion> </ToolAccordion>
)} )}

View File

@@ -6,9 +6,15 @@ import { Text } from "@/components/atoms/Text/Text";
import { CredentialsGroupedView } from "@/components/contextual/CredentialsInput/components/CredentialsGroupedView/CredentialsGroupedView"; import { CredentialsGroupedView } from "@/components/contextual/CredentialsInput/components/CredentialsGroupedView/CredentialsGroupedView";
import { FormRenderer } from "@/components/renderers/InputRenderer/FormRenderer"; import { FormRenderer } from "@/components/renderers/InputRenderer/FormRenderer";
import type { CredentialsMetaInput } from "@/lib/autogpt-server-api/types"; import type { CredentialsMetaInput } from "@/lib/autogpt-server-api/types";
import { AnimatePresence, motion } from "framer-motion";
import { useState } from "react"; import { useState } from "react";
import { useCopilotChatActions } from "../../../../components/CopilotChatActionsProvider/useCopilotChatActions"; import { useCopilotChatActions } from "../../../../components/CopilotChatActionsProvider/useCopilotChatActions";
import { ContentMessage } from "../../../../components/ToolAccordion/AccordionContent"; import {
ContentBadge,
ContentCardDescription,
ContentCardTitle,
ContentMessage,
} from "../../../../components/ToolAccordion/AccordionContent";
import { import {
buildExpectedInputsSchema, buildExpectedInputsSchema,
coerceCredentialFields, coerceCredentialFields,
@@ -25,8 +31,10 @@ export function SetupRequirementsCard({ output }: Props) {
const [inputCredentials, setInputCredentials] = useState< const [inputCredentials, setInputCredentials] = useState<
Record<string, CredentialsMetaInput | undefined> Record<string, CredentialsMetaInput | undefined>
>({}); >({});
const [hasSentCredentials, setHasSentCredentials] = useState(false);
const [showInputForm, setShowInputForm] = useState(false);
const [inputValues, setInputValues] = useState<Record<string, unknown>>({}); const [inputValues, setInputValues] = useState<Record<string, unknown>>({});
const [hasSent, setHasSent] = useState(false);
const { credentialFields, requiredCredentials } = coerceCredentialFields( const { credentialFields, requiredCredentials } = coerceCredentialFields(
output.setup_info.user_readiness?.missing_credentials, output.setup_info.user_readiness?.missing_credentials,
@@ -42,49 +50,27 @@ export function SetupRequirementsCard({ output }: Props) {
setInputCredentials((prev) => ({ ...prev, [key]: value })); setInputCredentials((prev) => ({ ...prev, [key]: value }));
} }
const needsCredentials = credentialFields.length > 0;
const isAllCredentialsComplete = const isAllCredentialsComplete =
needsCredentials && credentialFields.length > 0 &&
[...requiredCredentials].every((key) => !!inputCredentials[key]); [...requiredCredentials].every((key) => !!inputCredentials[key]);
const needsInputs = inputSchema !== null; function handleProceedCredentials() {
const requiredInputNames = expectedInputs setHasSentCredentials(true);
.filter((i) => i.required) onSend(
.map((i) => i.name); "I've configured the required credentials. Please re-run the block now.",
const isAllInputsComplete = );
needsInputs && }
requiredInputNames.every((name) => {
const v = inputValues[name];
return v !== undefined && v !== null && v !== "";
});
const canRun = function handleRunWithInputs() {
!hasSent && const nonEmpty = Object.fromEntries(
(!needsCredentials || isAllCredentialsComplete) && Object.entries(inputValues).filter(
(!needsInputs || isAllInputsComplete); ([, v]) => v !== undefined && v !== null && v !== "",
),
function handleRun() { );
setHasSent(true); onSend(
`Run the block with these inputs: ${JSON.stringify(nonEmpty, null, 2)}`,
const parts: string[] = []; );
if (needsCredentials) { setShowInputForm(false);
parts.push("I've configured the required credentials.");
}
if (needsInputs) {
const nonEmpty = Object.fromEntries(
Object.entries(inputValues).filter(
([, v]) => v !== undefined && v !== null && v !== "",
),
);
parts.push(
`Run the block with these inputs: ${JSON.stringify(nonEmpty, null, 2)}`,
);
} else {
parts.push("Please re-run the block now.");
}
onSend(parts.join(" "));
setInputValues({}); setInputValues({});
} }
@@ -92,54 +78,119 @@ export function SetupRequirementsCard({ output }: Props) {
<div className="grid gap-2"> <div className="grid gap-2">
<ContentMessage>{output.message}</ContentMessage> <ContentMessage>{output.message}</ContentMessage>
{needsCredentials && ( {credentialFields.length > 0 && (
<div className="rounded-2xl border bg-background p-3"> <div className="rounded-2xl border bg-background p-3">
<Text variant="small" className="w-fit border-b text-zinc-500"> <CredentialsGroupedView
Block credentials credentialFields={credentialFields}
</Text> requiredCredentials={requiredCredentials}
<div className="mt-6"> inputCredentials={inputCredentials}
<CredentialsGroupedView inputValues={{}}
credentialFields={credentialFields} onCredentialChange={handleCredentialChange}
requiredCredentials={requiredCredentials} />
inputCredentials={inputCredentials} {isAllCredentialsComplete && !hasSentCredentials && (
inputValues={{}} <Button
onCredentialChange={handleCredentialChange} variant="primary"
/> size="small"
</div> className="mt-3 w-full"
onClick={handleProceedCredentials}
>
Proceed
</Button>
)}
</div> </div>
)} )}
{inputSchema && ( {inputSchema && (
<div className="rounded-2xl border bg-background p-3 pt-4"> <div className="flex gap-2 pt-2">
<Text variant="small" className="w-fit border-b text-zinc-500"> <Button
Block inputs variant="outline"
</Text> size="small"
<FormRenderer className="w-fit"
jsonSchema={inputSchema} onClick={() => setShowInputForm((prev) => !prev)}
className="mb-3 mt-3" >
handleChange={(v) => setInputValues(v.formData ?? {})} {showInputForm ? "Hide inputs" : "Fill in inputs"}
uiSchema={{ </Button>
"ui:submitButtonOptions": { norender: true },
}}
initialValues={inputValues}
formContext={{
showHandles: false,
size: "small",
}}
/>
</div> </div>
)} )}
{(needsCredentials || needsInputs) && ( <AnimatePresence initial={false}>
<Button {showInputForm && inputSchema && (
variant="primary" <motion.div
size="small" initial={{ height: 0, opacity: 0, filter: "blur(6px)" }}
className="w-fit" animate={{ height: "auto", opacity: 1, filter: "blur(0px)" }}
disabled={!canRun} exit={{ height: 0, opacity: 0, filter: "blur(6px)" }}
onClick={handleRun} transition={{
> height: { type: "spring", bounce: 0.15, duration: 0.5 },
Proceed opacity: { duration: 0.25 },
</Button> filter: { duration: 0.2 },
}}
className="overflow-hidden"
style={{ willChange: "height, opacity, filter" }}
>
<div className="rounded-2xl border bg-background p-3 pt-4">
<Text variant="body-medium">Block inputs</Text>
<FormRenderer
jsonSchema={inputSchema}
handleChange={(v) => setInputValues(v.formData ?? {})}
uiSchema={{
"ui:submitButtonOptions": { norender: true },
}}
initialValues={inputValues}
formContext={{
showHandles: false,
size: "small",
}}
/>
<div className="-mt-8 flex gap-2">
<Button
variant="primary"
size="small"
className="w-fit"
onClick={handleRunWithInputs}
>
Run
</Button>
<Button
variant="secondary"
size="small"
className="w-fit"
onClick={() => {
setShowInputForm(false);
setInputValues({});
}}
>
Cancel
</Button>
</div>
</div>
</motion.div>
)}
</AnimatePresence>
{expectedInputs.length > 0 && !inputSchema && (
<div className="rounded-2xl border bg-background p-3">
<ContentCardTitle className="text-xs">
Expected inputs
</ContentCardTitle>
<div className="mt-2 grid gap-2">
{expectedInputs.map((input) => (
<div key={input.name} className="rounded-xl border p-2">
<div className="flex items-center justify-between gap-2">
<ContentCardTitle className="text-xs">
{input.title}
</ContentCardTitle>
<ContentBadge>
{input.required ? "Required" : "Optional"}
</ContentBadge>
</div>
<ContentCardDescription className="mt-1">
{input.name} &bull; {input.type}
{input.description ? ` \u2022 ${input.description}` : ""}
</ContentCardDescription>
</div>
))}
</div>
</div>
)} )}
</div> </div>
); );

View File

@@ -119,7 +119,7 @@ export function CredentialsFlatView({
) : ( ) : (
!readOnly && ( !readOnly && (
<Button <Button
variant="primary" variant="secondary"
size="small" size="small"
onClick={onAddCredential} onClick={onAddCredential}
className="w-fit" className="w-fit"

View File

@@ -1,11 +1,10 @@
import { cn } from "@/lib/utils";
import { RJSFSchema } from "@rjsf/utils"; import { RJSFSchema } from "@rjsf/utils";
import { preprocessInputSchema } from "./utils/input-schema-pre-processor";
import { useMemo } from "react"; import { useMemo } from "react";
import { customValidator } from "./utils/custom-validator";
import Form from "./registry"; import Form from "./registry";
import { ExtendedFormContextType } from "./types"; import { ExtendedFormContextType } from "./types";
import { customValidator } from "./utils/custom-validator";
import { generateUiSchemaForCustomFields } from "./utils/generate-ui-schema"; import { generateUiSchemaForCustomFields } from "./utils/generate-ui-schema";
import { preprocessInputSchema } from "./utils/input-schema-pre-processor";
type FormRendererProps = { type FormRendererProps = {
jsonSchema: RJSFSchema; jsonSchema: RJSFSchema;
@@ -13,17 +12,15 @@ type FormRendererProps = {
uiSchema: any; uiSchema: any;
initialValues: any; initialValues: any;
formContext: ExtendedFormContextType; formContext: ExtendedFormContextType;
className?: string;
}; };
export function FormRenderer({ export const FormRenderer = ({
jsonSchema, jsonSchema,
handleChange, handleChange,
uiSchema, uiSchema,
initialValues, initialValues,
formContext, formContext,
className, }: FormRendererProps) => {
}: FormRendererProps) {
const preprocessedSchema = useMemo(() => { const preprocessedSchema = useMemo(() => {
return preprocessInputSchema(jsonSchema); return preprocessInputSchema(jsonSchema);
}, [jsonSchema]); }, [jsonSchema]);
@@ -34,10 +31,7 @@ export function FormRenderer({
}, [preprocessedSchema, uiSchema]); }, [preprocessedSchema, uiSchema]);
return ( return (
<div <div className={"mb-6 mt-4"} data-tutorial-id="input-handles">
className={cn("mb-6 mt-4", className)}
data-tutorial-id="input-handles"
>
<Form <Form
formContext={formContext} formContext={formContext}
idPrefix="agpt" idPrefix="agpt"
@@ -51,4 +45,4 @@ export function FormRenderer({
/> />
</div> </div>
); );
} };

View File

@@ -218,17 +218,6 @@ If you initially installed Docker with Hyper-V, you **dont need to reinstall*
For more details, refer to [Docker's official documentation](https://docs.docker.com/desktop/windows/wsl/). For more details, refer to [Docker's official documentation](https://docs.docker.com/desktop/windows/wsl/).
### ⚠️ Podman Not Supported
AutoGPT requires **Docker** (Docker Desktop or Docker Engine). **Podman and podman-compose are not supported** and may cause path resolution issues, particularly on Windows.
If you see errors like:
```text
Error: the specified Containerfile or Dockerfile does not exist, ..\..\autogpt_platform\backend\Dockerfile
```
This indicates you're using Podman instead of Docker. Please install [Docker Desktop](https://docs.docker.com/desktop/) and use `docker compose` instead of `podman-compose`.
## Development ## Development