> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sampleapp.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Agent Framework (CrewAI)

> Build an AI-powered marketing strategy crew with multiple specialized agents working together

export const DownloadAndOpenButtons = ({downloadUrl, mainAppUrl = "https://sampleapp.ai", className = "", gitUrl}) => {
  const [hasCopied, setHasCopied] = React.useState(false);
  const [showCommand, setShowCommand] = React.useState(false);
  const npxCommand = React.useMemo(() => {
    let apiPath = downloadUrl;
    if (apiPath.startsWith("http://") || apiPath.startsWith("https://")) {
      try {
        const url = new URL(apiPath);
        apiPath = url.pathname;
      } catch (e) {
        console.error("Invalid URL:", apiPath);
      }
    }
    if (apiPath.startsWith("/public/")) {
      apiPath = apiPath.slice(("/public/").length);
    }
    if (apiPath.startsWith("/")) {
      apiPath = apiPath.slice(1);
    }
    if (apiPath.endsWith(".zip")) {
      apiPath = apiPath.slice(0, -4);
    }
    return `npx sampleappai add "${mainAppUrl}/api/code/static/${apiPath}"`;
  }, [downloadUrl, mainAppUrl]);
  const handleDownload = () => {
    window.open(downloadUrl, "_blank");
  };
  const handleCopyCommand = async () => {
    try {
      await navigator.clipboard.writeText(npxCommand);
      setHasCopied(true);
      setTimeout(() => setHasCopied(false), 2000);
    } catch (err) {
      const textArea = document.createElement("textarea");
      textArea.value = npxCommand;
      document.body.appendChild(textArea);
      textArea.select();
      try {
        document.execCommand("copy");
        setHasCopied(true);
        setTimeout(() => setHasCopied(false), 2000);
      } catch (err) {
        console.error("Failed to copy:", err);
      }
      document.body.removeChild(textArea);
    }
  };
  const handleOpenVSCode = () => {
    try {
      window.location.href = `vscode://vscode.git/clone?url=${gitUrl}`;
    } catch {}
  };
  return <div className={`flex flex-col gap-3 ${className}`}>
      <div className="flex items-center gap-2">
        {}
        <button type="button" className="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md text-white transition-all hover:opacity-90 active:scale-[0.98] bg-primary dark:bg-primary-light" onClick={handleDownload}>
          <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
          </svg>
          Download example
        </button>

        {}
        <button type="button" className="inline-flex items-center h-8 gap-1.5 px-3 py-0.5 text-xs font-medium rounded-md border transition-all hover:bg-zinc-100 dark:hover:bg-zinc-800 active:scale-[0.98] bg-white dark:bg-zinc-900 border-zinc-300 dark:border-zinc-700 text-zinc-900 dark:text-zinc-100" onClick={() => handleOpenVSCode()}>
          <img src="/assets/vscode.png" alt="VS Code" className="w-3.5 h-3.5" />
          Integrate in VS Code
        </button>
      </div>

      {}
      {showCommand && <div className="p-4 border border-zinc-300 dark:border-zinc-700 rounded-lg bg-zinc-50 dark:bg-zinc-900/50">
          <div className="flex flex-col gap-3">
            {}
            <div className="flex flex-col gap-2">
              <div className="flex items-center gap-2">
                <div className="flex-shrink-0 w-5 h-5 rounded-full flex items-center justify-center text-[10px] font-bold text-white" style={{
    backgroundColor: themeColor
  }}>
                  1
                </div>
                <p className="text-sm font-medium text-zinc-900 dark:text-zinc-100">
                  Copy this command
                </p>
              </div>
              <div className="flex items-center gap-2 overflow-hidden">
                <code className="flex-1 bg-white dark:bg-zinc-800 px-3 py-2.5 rounded-md text-xs font-mono truncate block max-w-[calc(100%-3rem)] border border-zinc-200 dark:border-zinc-700 text-zinc-900 dark:text-zinc-100">
                  {npxCommand}
                </code>
                <button type="button" onClick={handleCopyCommand} className="flex-shrink-0 p-2 rounded-md hover:bg-zinc-200 dark:hover:bg-zinc-700 transition-colors border border-zinc-300 dark:border-zinc-600" title="Copy command">
                  {hasCopied ? <svg className="w-4 h-4 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                      <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
                    </svg> : <svg className="w-4 h-4 text-zinc-600 dark:text-zinc-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                      <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
                    </svg>}
                </button>
              </div>
            </div>

            {}
            <div className="flex flex-col gap-2">
              <div className="flex items-center gap-2">
                <div className="flex-shrink-0 w-5 h-5 rounded-full flex items-center justify-center text-[10px] font-bold text-white" style={{
    backgroundColor: themeColor
  }}>
                  2
                </div>
                <p className="text-sm font-medium text-zinc-900 dark:text-zinc-100">
                  Open VSCode and paste in terminal
                </p>
              </div>
              <button type="button" onClick={handleOpenVSCode} className="w-full inline-flex items-center justify-center gap-2 px-4 py-2.5 text-sm font-medium rounded-md text-white transition-all hover:opacity-90 active:scale-[0.99]" style={{
    backgroundColor: themeColor
  }}>
                <svg className="w-4 h-4" width="20" height="20" viewBox="0 0 20 20" fill="none">
                  <path d="M13.3333 0L5 3.33333V16.6667L13.3333 20V0Z" fill="currentColor" />
                  <path d="M13.3333 0L18.3333 3.75V16.25L13.3333 20V0Z" fill="currentColor" opacity="0.8" />
                </svg>
                Open VSCode
              </button>
            </div>
          </div>
        </div>}
    </div>;
};

export const FrameworkGuide = ({framework, label, children}) => {
  return children;
};

export const Architecture = ({value, label, children}) => {
  return children;
};

export const iframeState = {
  listeners: [],
  params: {},
  subscribe: fn => {
    iframeState.listeners.push(fn);
    return () => {
      iframeState.listeners = iframeState.listeners.filter(l => l !== fn);
    };
  },
  setParams: params => {
    iframeState.params = params;
    iframeState.listeners.forEach(fn => fn(params));
  }
};

export const CodeFocusSection = ({docs = "mintlify", themeColor, linesStart, linesEnd, activeFilePath, title, description, children}) => {
  const sectionRef = React.useRef(null);
  const hasTriggeredRef = React.useRef(false);
  const [isActive, setIsActive] = React.useState(false);
  const useProse = docs === "mintlify";
  const getParams = React.useCallback(() => {
    const params = {};
    if (docs) params.docs = docs;
    if (docs === "custom" && themeColor) params.theme = themeColor;
    if (linesStart !== undefined) params.linesStart = linesStart;
    if (linesEnd !== undefined) params.linesEnd = linesEnd;
    if (activeFilePath) params.activeFilePath = activeFilePath;
    return params;
  }, [docs, themeColor, linesStart, linesEnd, activeFilePath]);
  const handleFocus = React.useCallback(() => {
    iframeState.setParams(getParams());
    setIsActive(true);
  }, [getParams]);
  React.useEffect(() => {
    const checkActive = params => {
      const myParams = getParams();
      const isMatch = params.activeFilePath === myParams.activeFilePath && params.linesStart === myParams.linesStart && params.linesEnd === myParams.linesEnd;
      setIsActive(isMatch);
      if (params.platform !== undefined || params.framework !== undefined || params.activeFilePath !== myParams.activeFilePath) {
        hasTriggeredRef.current = false;
      }
    };
    checkActive(iframeState.params);
    return iframeState.subscribe(checkActive);
  }, [getParams]);
  React.useEffect(() => {
    const element = sectionRef.current;
    if (!element) return;
    const observer = new IntersectionObserver(entries => {
      entries.forEach(entry => {
        if (entry.isIntersecting && !hasTriggeredRef.current) {
          hasTriggeredRef.current = true;
          handleFocus();
        } else if (!entry.isIntersecting) {
          hasTriggeredRef.current = false;
        }
      });
    }, {
      threshold: 0,
      rootMargin: "-40% 0px -40% 0px"
    });
    observer.observe(element);
    return () => observer.disconnect();
  }, [handleFocus]);
  return <div ref={sectionRef} onClick={handleFocus} className="relative py-6 transition-colors cursor-pointer" style={{
    marginLeft: "-1.5rem",
    marginRight: "-1.5rem",
    paddingLeft: "1.5rem",
    paddingRight: "1.5rem"
  }}>
      <div className="absolute top-0 h-px" style={{
    backgroundColor: "rgba(128,128,128,0.2)",
    left: "-2rem",
    right: "-2rem"
  }} />
      {isActive && <div className={`absolute -left-2 top-0 bottom-0 w-1 ${useProse ? "bg-primary dark:bg-primary-light" : ""}`} style={!useProse && themeColor ? {
    backgroundColor: themeColor
  } : undefined} />}
      {title && <div className="flex items-center gap-2 mb-2">
          <h4 className={`text-base font-semibold m-0 ${useProse && isActive ? "text-primary dark:text-primary-light" : ""}`} style={!useProse && isActive && themeColor ? {
    color: themeColor
  } : undefined}>
            {title}
          </h4>
        </div>}
      {description && <p className="text-sm text-gray-500 dark:text-gray-400 mt-1 mb-0">
          {description}
        </p>}
      {children && <div className="mt-3">{children}</div>}
    </div>;
};

export const SideBySideLayout = ({previewSrc, children}) => {
  const iframeRef = React.useRef(null);
  const pendingParams = React.useRef(null);
  const iframeReady = React.useRef(false);
  const baseUrlRef = React.useRef(previewSrc);
  const architectures = React.useMemo(() => {
    const derived = [];
    React.Children.forEach(children, child => {
      if (child?.type === Architecture) {
        const archValue = child.props.value;
        const archLabel = child.props.label;
        const frameworks = [];
        React.Children.forEach(child.props.children, frameworkChild => {
          if (frameworkChild?.type === FrameworkGuide) {
            frameworks.push({
              value: frameworkChild.props.framework,
              label: frameworkChild.props.label || frameworkChild.props.framework
            });
          }
        });
        derived.push({
          value: archValue,
          label: archLabel,
          frameworks
        });
      }
    });
    return derived;
  }, [children]);
  const SettingsNavInternal = () => {
    const parseFrameworkValue = (archValue, fwValue, fwLabel) => {
      const archPrefix = archValue.toLowerCase().split(" ")[0];
      const archPrefixMap = {
        javascript: "js",
        ios: "ios",
        android: "ad"
      };
      const prefix = archPrefixMap[archPrefix] || archPrefix;
      let remaining = fwValue;
      if (fwValue.startsWith(prefix + "-")) {
        remaining = fwValue.slice(prefix.length + 1);
      }
      const labelParts = fwLabel.split(" + ");
      if (!fwLabel.includes(" + ")) {
        return {
          frontend: remaining,
          frontendLabel: fwLabel,
          backend: null,
          backendLabel: null,
          isFullStack: true
        };
      }
      const parts = remaining.split("-");
      if (parts.length >= 2) {
        return {
          frontend: parts[0],
          frontendLabel: labelParts[0] || parts[0],
          backend: parts.slice(1).join("-"),
          backendLabel: labelParts[1] || parts.slice(1).join("-"),
          isFullStack: false
        };
      }
      return {
        frontend: remaining,
        frontendLabel: fwLabel,
        backend: null,
        backendLabel: null,
        isFullStack: true
      };
    };
    const [selections, setSelections] = React.useState(() => {
      const defaults = {};
      if (architectures && architectures.length > 0) {
        const firstArch = architectures[0];
        defaults.platform = firstArch.value;
        if (firstArch.frameworks && firstArch.frameworks.length > 0) {
          const firstFw = firstArch.frameworks[0];
          const parsed = parseFrameworkValue(firstArch.value, firstFw.value, firstFw.label);
          defaults.client = parsed.frontend;
          defaults.server = parsed.backend;
          defaults.framework = firstFw.value;
        }
      }
      return defaults;
    });
    const getAvailableOptions = React.useMemo(() => {
      const selectedArch = architectures?.find(arch => arch.value === selections.platform);
      const frameworks = selectedArch?.frameworks || [];
      const frontends = new Map();
      const backends = new Map();
      const fullStackFrameworks = [];
      frameworks.forEach(fw => {
        const parsed = parseFrameworkValue(selectedArch?.value || "", fw.value, fw.label);
        if (parsed.isFullStack) {
          fullStackFrameworks.push({
            value: parsed.frontend,
            label: parsed.frontendLabel,
            fullFrameworkValue: fw.value
          });
        } else {
          if (!frontends.has(parsed.frontend)) {
            frontends.set(parsed.frontend, {
              value: parsed.frontend,
              label: parsed.frontendLabel
            });
          }
          if (parsed.backend && !backends.has(parsed.backend)) {
            backends.set(parsed.backend, {
              value: parsed.backend,
              label: parsed.backendLabel
            });
          }
        }
      });
      fullStackFrameworks.forEach(fs => {
        if (!frontends.has(fs.value)) {
          frontends.set(fs.value, {
            value: fs.value,
            label: fs.label,
            isFullStack: true,
            fullFrameworkValue: fs.fullFrameworkValue
          });
        }
      });
      return {
        frontends: Array.from(frontends.values()),
        backends: Array.from(backends.values()),
        fullStackFrameworks
      };
    }, [architectures, selections.platform]);
    const isFullStackSelected = React.useMemo(() => {
      const frontend = getAvailableOptions.frontends.find(f => f.value === selections.client);
      return frontend?.isFullStack || false;
    }, [getAvailableOptions.frontends, selections.client]);
    const sections = [{
      id: "platform",
      label: "Architecture:",
      options: architectures || []
    }, {
      id: "client",
      label: "Frontend:",
      options: getAvailableOptions.frontends
    }, ...isFullStackSelected ? [] : [{
      id: "server",
      label: "Backend:",
      options: getAvailableOptions.backends
    }]];
    React.useEffect(() => {
      if (selections.platform || selections.framework) {
        iframeState.setParams({
          ...iframeState.params,
          ...selections
        });
      }
    }, []);
    const handleSelect = (sectionId, value) => {
      let newSelections = {
        ...selections,
        [sectionId]: value
      };
      const getArchPrefix = archValue => {
        const archPrefix = archValue.toLowerCase().split(" ")[0];
        const archPrefixMap = {
          javascript: "js",
          ios: "ios",
          android: "ad"
        };
        return archPrefixMap[archPrefix] || archPrefix;
      };
      const getBackendsForArch = archValue => {
        const arch = architectures?.find(a => a.value === archValue);
        const backends = new Map();
        arch?.frameworks?.forEach(fw => {
          const parsed = parseFrameworkValue(arch.value, fw.value, fw.label);
          if (!parsed.isFullStack && parsed.backend) {
            backends.set(parsed.backend, parsed.backendLabel);
          }
        });
        return Array.from(backends.keys());
      };
      if (sectionId === "platform") {
        const newArch = architectures?.find(arch => arch.value === value);
        if (newArch?.frameworks && newArch.frameworks.length > 0) {
          const firstFw = newArch.frameworks[0];
          const parsed = parseFrameworkValue(newArch.value, firstFw.value, firstFw.label);
          newSelections.client = parsed.frontend;
          newSelections.server = parsed.backend;
          newSelections.framework = firstFw.value;
        }
      }
      if (sectionId === "client") {
        const selectedArch = architectures?.find(arch => arch.value === newSelections.platform);
        const frameworks = selectedArch?.frameworks || [];
        const fullStackFw = frameworks.find(fw => {
          const parsed = parseFrameworkValue(selectedArch?.value || "", fw.value, fw.label);
          return parsed.isFullStack && parsed.frontend === value;
        });
        if (fullStackFw) {
          newSelections.server = null;
          newSelections.framework = fullStackFw.value;
        } else {
          const prefix = getArchPrefix(newSelections.platform);
          let server = newSelections.server;
          if (!server) {
            const availableBackends = getBackendsForArch(newSelections.platform);
            server = availableBackends[0] || null;
            newSelections.server = server;
          }
          if (server) {
            newSelections.framework = `${prefix}-${value}-${server}`;
          }
        }
      }
      if (sectionId === "server") {
        const prefix = getArchPrefix(newSelections.platform);
        const client = newSelections.client;
        newSelections.framework = `${prefix}-${client}-${value}`;
      }
      setSelections(newSelections);
      iframeState.setParams({
        ...iframeState.params,
        ...newSelections
      });
    };
    return <div className="w-full sticky top-0 z-50 lg:-mx-8 px-4 lg:px-8 py-6 backdrop-blur-md">
        <div className="flex flex-wrap items-center justify-between">
          <div className="flex flex-wrap items-center" style={{
      marginLeft: "-24px",
      marginTop: "-24px"
    }}>
            {sections.map(section => <div key={section.id} className="flex items-center flex-wrap" style={{
      marginTop: "24px",
      marginLeft: "24px"
    }}>
                <span className="text-sm font-semibold text-gray-700 dark:text-gray-300 whitespace-nowrap mr-3">
                  {section.label}
                </span>

                {}
                <fieldset className="hidden 2xl:block border-0 p-0 m-0">
                  <legend className="sr-only">{section.label}</legend>
                  <div className="flex items-center gap-2">
                    {section.options.map(option => {
      const isSelected = selections[section.id] === option.value;
      return <div key={option.value} className="relative">
                          <input type="radio" id={`${section.id}-${option.value}`} name={section.id} value={option.value} checked={isSelected} onChange={() => handleSelect(section.id, option.value)} className="sr-only" />
                          <label htmlFor={`${section.id}-${option.value}`} className={`inline-flex items-center px-3 py-1.5 text-xs font-medium rounded-md border transition-all cursor-pointer active:scale-[0.98] ${isSelected ? "bg-white dark:bg-zinc-900 border-primary dark:border-primary-light text-primary dark:text-primary-light hover:bg-zinc-100 dark:hover:bg-zinc-800" : "bg-white dark:bg-zinc-900 border-zinc-300 dark:border-zinc-700 text-zinc-900 dark:text-zinc-100 hover:bg-zinc-100 dark:hover:bg-zinc-800"}`}>
                            {option.label}
                          </label>
                        </div>;
    })}
                  </div>
                </fieldset>

                {}
                <div className="block 2xl:hidden relative">
                  <select value={selections[section.id]} onChange={e => handleSelect(section.id, e.target.value)} className="appearance-none bg-white dark:bg-zinc-900 border border-zinc-300 dark:border-zinc-700 rounded-md px-3 py-1.5 pr-8 text-xs font-medium text-zinc-900 dark:text-zinc-100 focus:outline-none focus:ring-2 focus:ring-primary dark:focus:ring-primary-light focus:border-transparent transition-all hover:bg-zinc-100 dark:hover:bg-zinc-800">
                    {section.options.map(option => <option key={option.value} value={option.value}>
                        {option.label}
                      </option>)}
                  </select>
                  <svg aria-hidden="true" width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" className="absolute right-2 top-1/2 -translate-y-1/2 pointer-events-none text-zinc-400">
                    <path fillRule="evenodd" clipRule="evenodd" d="M2.35 9.915a.875.875 0 0 1 1.235-.065L8 13.823l4.415-3.973a.875.875 0 0 1 1.17 1.3l-5 4.5a.873.873 0 0 1-1.17 0l-5-4.5a.875.875 0 0 1-.065-1.235ZM7.415.35a.873.873 0 0 1 1.17 0l5 4.5a.875.875 0 1 1-1.17 1.3L8 2.177 3.585 6.15a.875.875 0 0 1-1.17-1.3l5-4.5Z" fill="currentColor" />
                  </svg>
                </div>
              </div>)}
          </div>
        </div>
      </div>;
  };
  const getCurrentColor = () => {
    const temp = document.createElement("div");
    temp.className = document.documentElement.classList.contains("dark") ? "dark:bg-primary-light" : "bg-primary";
    document.body.appendChild(temp);
    const bgColor = getComputedStyle(temp).backgroundColor;
    document.body.removeChild(temp);
    return bgColor;
  };
  function rgbToHex(rgb) {
    const match = rgb.match(/\d+/g);
    if (!match) return null;
    const [r, g, b] = match.map(Number);
    return "#" + [r, g, b].map(x => x.toString(16).padStart(2, "0")).join("");
  }
  const getCurrentColorInHexadecimal = () => {
    return "%23" + rgbToHex(getCurrentColor()).split("#")[1];
  };
  const updateIframeTheme = React.useCallback(() => {
    if (iframeRef.current) {
      const currentParams = iframeState.params;
      let newSrc = baseUrlRef.current + "&theme=" + getCurrentColorInHexadecimal();
      if (currentParams.iframeUrl) {
        newSrc += "&iframeUrl=" + encodeURIComponent(currentParams.iframeUrl);
      }
      iframeRef.current.src = newSrc;
    }
  }, []);
  React.useEffect(() => {
    const observer = new MutationObserver(mutations => {
      mutations.forEach(mutation => {
        if (mutation.attributeName === "class") {
          updateIframeTheme();
        }
      });
    });
    observer.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ["class"]
    });
    return () => observer.disconnect();
  }, [updateIframeTheme]);
  React.useEffect(() => {
    let lastFramework = iframeState.params.framework;
    let lastFilePath = iframeState.params.activeFilePath;
    let debounceTimer = null;
    const handleParamsChange = params => {
      if (params.framework && params.framework !== lastFramework) {
        lastFramework = params.framework;
        if (iframeRef.current) {
          const url = new URL(iframeRef.current.src);
          url.searchParams.set("framework", params.framework);
          iframeRef.current.src = url.toString();
          return;
        }
      }
      if (debounceTimer) {
        clearTimeout(debounceTimer);
      }
      const fileChanged = params.activeFilePath && params.activeFilePath !== lastFilePath;
      if (fileChanged) {
        lastFilePath = params.activeFilePath;
      }
      const delay = fileChanged ? 150 : 0;
      debounceTimer = setTimeout(() => {
        const message = {
          type: "UPDATE_VIEW",
          ...params
        };
        if (iframeRef.current?.contentWindow) {
          iframeRef.current.contentWindow.postMessage(message, "*");
        }
      }, delay);
    };
    const unsubscribe = iframeState.subscribe(handleParamsChange);
    return () => {
      if (debounceTimer) clearTimeout(debounceTimer);
      unsubscribe();
    };
  }, []);
  const ActiveContent = () => {
    const [currentState, setCurrentState] = React.useState(() => {
      const params = iframeState.params;
      return {
        platform: params.platform || architectures[0]?.value,
        framework: params.framework || architectures[0]?.frameworks[0]?.value
      };
    });
    const [currentPageId, setCurrentPageId] = React.useState("1");
    React.useEffect(() => {
      const handleUpdate = params => {
        if (params.platform !== undefined || params.framework !== undefined) {
          setCurrentState(prev => ({
            platform: params.platform ?? prev.platform,
            framework: params.framework ?? prev.framework
          }));
        }
      };
      return iframeState.subscribe(handleUpdate);
    }, []);
    let matchedContent = null;
    React.Children.forEach(children, child => {
      if (child?.type === Architecture && child.props.value === currentState.platform) {
        React.Children.forEach(child.props.children, frameworkChild => {
          if (frameworkChild?.type === FrameworkGuide && frameworkChild.props.framework === currentState.framework) {
            matchedContent = frameworkChild.props.children;
          }
        });
      }
    });
    const isPageComponent = child => {
      if (child?.props?.["data-page-id"]) return true;
      if (child?.type?.name === "Page") return true;
      if (typeof child?.type === "function" && child?.props?.id && child?.props?.title) {
        return true;
      }
      return false;
    };
    const pages = React.useMemo(() => {
      const extracted = [];
      React.Children.forEach(matchedContent, child => {
        if (isPageComponent(child)) {
          let verificationSteps = null;
          const stepsData = child.props["data-page-verification-steps"] || child.props.verificationSteps;
          if (stepsData) {
            try {
              verificationSteps = typeof stepsData === "string" ? JSON.parse(stepsData) : stepsData;
            } catch (e) {
              console.warn("Failed to parse verification steps:", e);
            }
          }
          extracted.push({
            id: child.props["data-page-id"] || child.props.id,
            title: child.props["data-page-title"] || child.props.title,
            content: child.props.children,
            verificationSteps
          });
        }
      });
      return extracted;
    }, [matchedContent]);
    React.useEffect(() => {
      if (pages.length > 0 && !pages.find(p => p.id === currentPageId)) {
        setCurrentPageId(pages[0].id);
      }
    }, [pages, currentPageId]);
    if (pages.length === 0) {
      return <>{matchedContent}</>;
    }
    if (pages.length === 1) {
      const currentPage = pages[0];
      return <>{currentPage?.content}</>;
    }
    const currentPageIndex = pages.findIndex(p => p.id === currentPageId);
    const currentPage = pages[currentPageIndex];
    const nextPage = currentPageIndex < pages.length - 1 ? pages[currentPageIndex + 1] : null;
    const [showStepsPopup, setShowStepsPopup] = React.useState(false);
    const [showVerificationPopup, setShowVerificationPopup] = React.useState(false);
    const [verificationSteps, setVerificationSteps] = React.useState([]);
    const [currentVerificationIndex, setCurrentVerificationIndex] = React.useState(0);
    const [verificationComplete, setVerificationComplete] = React.useState(false);
    const getVerificationStepsForPage = pageId => {
      const page = pages.find(p => p.id === pageId);
      if (page?.verificationSteps && page.verificationSteps.length > 0) {
        return page.verificationSteps;
      }
      const defaultSteps = [{
        id: 1,
        label: "Checking configuration"
      }, {
        id: 2,
        label: "Validating setup"
      }, {
        id: 3,
        label: "Verifying connection"
      }, {
        id: 4,
        label: "Ready to continue"
      }];
      return defaultSteps;
    };
    const goToPage = pageId => {
      setCurrentPageId(pageId);
      setShowStepsPopup(false);
      window.scrollTo({
        top: 0,
        behavior: "smooth"
      });
    };
    const handleNextClick = () => {
      if (!nextPage) return;
      const steps = getVerificationStepsForPage(currentPage.id);
      setVerificationSteps(steps);
      setCurrentVerificationIndex(0);
      setVerificationComplete(false);
      setShowVerificationPopup(true);
      steps.forEach((_, index) => {
        setTimeout(() => {
          setCurrentVerificationIndex(index);
          if (index === steps.length - 1) {
            setTimeout(() => {
              setVerificationComplete(true);
            }, 600);
          }
        }, (index + 1) * 800);
      });
    };
    const handleVerificationComplete = () => {
      setShowVerificationPopup(false);
      if (nextPage) {
        goToPage(nextPage.id);
      }
    };
    const barContainerRef = React.useRef(null);
    React.useEffect(() => {
      const handleClickOutside = event => {
        if (barContainerRef.current && !barContainerRef.current.contains(event.target) && (showStepsPopup || showVerificationPopup)) {
          if (showVerificationPopup && !verificationComplete) {
            return;
          }
          setShowStepsPopup(false);
          if (verificationComplete) {
            setShowVerificationPopup(false);
          }
        }
      };
      if (showStepsPopup || showVerificationPopup) {
        document.addEventListener("mousedown", handleClickOutside);
        return () => {
          document.removeEventListener("mousedown", handleClickOutside);
        };
      }
    }, [showStepsPopup, showVerificationPopup, verificationComplete]);
    return <div className="page-container relative">
        {}
        <div className="page-content pb-24">{currentPage?.content}</div>

        {}
        <div className="sticky bottom-4 z-40" ref={barContainerRef}>
          <div className="relative">
            {}
            <div className="bg-white dark:bg-zinc-900 border border-gray-200 dark:border-zinc-700 shadow-lg rounded-xl px-4 py-3">
              <div className="flex items-center justify-between gap-4">
                {}
                <button onClick={() => setShowStepsPopup(!showStepsPopup)} className="flex items-center gap-3 px-3 py-2 rounded-lg bg-gray-100 dark:bg-zinc-800 hover:bg-gray-200 dark:hover:bg-zinc-700 transition-all">
                  <div className="flex items-center gap-2">
                    <div className="w-6 h-6 rounded-full bg-primary dark:bg-primary-light text-white text-xs font-bold flex items-center justify-center">
                      {currentPageIndex + 1}
                    </div>
                    <span className="text-sm font-medium text-gray-700 dark:text-gray-300">
                      {currentPage?.title}
                    </span>
                  </div>
                  <svg className={`w-4 h-4 text-gray-400 transition-transform ${showStepsPopup ? "rotate-180" : ""}`} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
                    <path strokeLinecap="round" strokeLinejoin="round" d="M19 9l-7 7-7-7" />
                  </svg>
                </button>

                {}
                {nextPage ? <button onClick={handleNextClick} className="flex items-center gap-2 px-5 py-2 rounded-lg bg-primary dark:bg-primary-light text-white font-medium hover:opacity-90 transition-all">
                    <span>Verify</span>
                    <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
                      <path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
                    </svg>
                  </button> : <div className="flex items-center gap-2 px-5 py-2 rounded-lg bg-green-500 text-white font-medium">
                    <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
                      <path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
                    </svg>
                    <span>Complete</span>
                  </div>}
              </div>
            </div>

            {}
            {showStepsPopup && <div className="absolute bottom-full left-0 right-0 mb-2 z-50">
                <div className="bg-white dark:bg-zinc-900 border border-gray-200 dark:border-zinc-700 shadow-2xl rounded-xl overflow-hidden animate-expand-up">
                  {}
                  <div className="flex items-center justify-between px-3 py-2 border-b border-gray-200 dark:border-zinc-700 bg-gray-50 dark:bg-zinc-800/50">
                    <div className="flex items-center gap-2">
                      <div className="w-5 h-5 rounded-full bg-primary dark:bg-primary-light text-white text-xs font-bold flex items-center justify-center leading-none">
                        {pages.length}
                      </div>
                      <span className="text-sm font-semibold text-gray-900 dark:text-white leading-none">
                        Steps
                      </span>
                    </div>
                    <button onClick={() => setShowStepsPopup(false)} className="p-1 rounded-md hover:bg-gray-200 dark:hover:bg-zinc-700 transition-colors flex-shrink-0">
                      <svg className="w-4 h-4 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
                        <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
                      </svg>
                    </button>
                  </div>

                  {}
                  <div className="p-3 space-y-1 max-h-80 overflow-y-auto">
                    {pages.map((page, index) => {
      const isCompleted = index < currentPageIndex;
      const isCurrent = page.id === currentPageId;
      const isDisabled = index > currentPageIndex;
      return <div key={page.id} className="relative group">
                          <button onClick={() => !isDisabled && goToPage(page.id)} disabled={isDisabled} className={`w-full flex items-center gap-3 p-2.5 rounded-lg text-left transition-all ${isCurrent ? "bg-primary/10 dark:bg-primary-light/10 border border-primary/30 dark:border-primary-light/30" : isDisabled ? "opacity-50 cursor-not-allowed" : "hover:bg-gray-100 dark:hover:bg-zinc-800"}`}>
                            {}
                            <div className={`flex-shrink-0 w-7 h-7 rounded-full flex items-center justify-center text-xs font-bold transition-all ${isCompleted ? "bg-primary dark:bg-primary-light text-white" : isCurrent ? "bg-primary dark:bg-primary-light text-white" : isDisabled ? "bg-gray-100 dark:bg-zinc-800 text-gray-400 dark:text-gray-600" : "bg-gray-200 dark:bg-zinc-700 text-gray-500 dark:text-gray-400"}`}>
                              {isCompleted ? <svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={3}>
                                  <path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
                                </svg> : index + 1}
                            </div>
                            {}
                            <span className={`text-sm font-medium ${isCurrent ? "text-primary dark:text-primary-light" : isCompleted ? "text-gray-500 dark:text-gray-400" : isDisabled ? "text-gray-400 dark:text-gray-600" : "text-gray-700 dark:text-gray-300"}`}>
                              {page.title}
                            </span>
                          </button>
                          {}
                          {isDisabled && <div className="absolute left-0 bottom-full mb-2 w-64 px-3 py-2 bg-gray-900 dark:bg-gray-700 text-white text-xs rounded-lg shadow-lg opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-50">
                              Please complete all steps in this guide and press
                              verify
                              <div className="absolute top-full left-4 w-0 h-0 border-l-4 border-r-4 border-t-4 border-transparent border-t-gray-900 dark:border-t-gray-700"></div>
                            </div>}
                        </div>;
    })}
                  </div>
                </div>
              </div>}

            {}
            {showVerificationPopup && <div className="absolute bottom-full left-0 right-0 mb-2 z-50">
                <div className="bg-white dark:bg-zinc-900 border border-gray-200 dark:border-zinc-700 shadow-2xl rounded-xl overflow-hidden animate-expand-up">
                  {}
                  <div className="flex items-center justify-between px-3 py-2 border-b border-gray-200 dark:border-zinc-700 bg-gray-50 dark:bg-zinc-800/50">
                    <div className="flex items-center gap-2">
                      <div className="w-5 h-5 rounded-full bg-primary dark:bg-primary-light text-white text-xs font-bold flex items-center justify-center leading-none">
                        {verificationSteps.length}
                      </div>
                      <span className="text-sm font-semibold text-gray-900 dark:text-white leading-none">
                        {verificationComplete ? "Verification Complete" : "Verifying..."}
                      </span>
                    </div>
                    {verificationComplete && <button onClick={() => setShowVerificationPopup(false)} className="p-1 rounded-md hover:bg-gray-200 dark:hover:bg-zinc-700 transition-colors flex-shrink-0">
                        <svg className="w-4 h-4 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
                          <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
                        </svg>
                      </button>}
                  </div>

                  {}
                  <div className="p-3 space-y-2">
                    {verificationSteps.map((step, index) => {
      const isChecked = index <= currentVerificationIndex;
      const isActive = index === currentVerificationIndex && !verificationComplete;
      return <div key={step.id} className={`flex items-center gap-3 p-2 rounded-lg transition-all duration-300 ${isActive ? "bg-primary/5 dark:bg-primary-light/5" : ""}`}>
                          {}
                          <div className={`flex-shrink-0 w-6 h-6 rounded-md border-2 flex items-center justify-center transition-all duration-300 ${isChecked ? "bg-primary dark:bg-primary-light border-primary dark:border-primary-light" : "border-gray-300 dark:border-zinc-600"}`}>
                            {isChecked && <svg className="w-4 h-4 text-white animate-check" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={3}>
                                <path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
                              </svg>}
                          </div>
                          {}
                          <span className={`text-sm font-medium transition-all duration-300 ${isChecked ? "text-gray-900 dark:text-white" : "text-gray-400 dark:text-gray-500"}`}>
                            {step.label}
                          </span>
                          {}
                          {isActive && <div className="ml-auto">
                              <svg className="w-4 h-4 text-primary dark:text-primary-light animate-spin" fill="none" viewBox="0 0 24 24">
                                <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
                                <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
                              </svg>
                            </div>}
                        </div>;
    })}
                  </div>

                  {}
                  {verificationComplete && <div className="px-3 pb-3">
                      <button onClick={handleVerificationComplete} className="w-full flex items-center justify-center gap-2 px-4 py-2.5 rounded-lg bg-primary dark:bg-primary-light text-white font-medium hover:opacity-90 transition-all text-sm">
                        <span>Continue to {nextPage?.title}</span>
                        <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
                          <path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
                        </svg>
                      </button>
                    </div>}
                </div>
              </div>}
          </div>
        </div>

        {}
        <style>{`
          @keyframes expand-up {
            from {
              transform: translateY(8px) scale(0.98);
              opacity: 0;
            }
            to {
              transform: translateY(0) scale(1);
              opacity: 1;
            }
          }
          .animate-expand-up {
            animation: expand-up 0.2s ease-out;
          }
          @keyframes check {
            from {
              transform: scale(0);
            }
            to {
              transform: scale(1);
            }
          }
          .animate-check {
            animation: check 0.2s ease-out;
          }
        `}</style>
      </div>;
  };
  return <div className="mx-auto px-4 lg:px-8 flex flex-col">
      <SettingsNavInternal />

      <div className="rounded-2xl shadow-sm flex-1">
        <div className="grid gap-8 lg:gap-10 items-start" style={{
    gridTemplateColumns: "minmax(0, 0.8fr) minmax(0, 1.2fr)"
  }}>
          <div className="prose prose-gray dark:prose-invert max-w-none pr-2">
            <ActiveContent />
          </div>
          <div className="sticky top-20">
            <div style={{
    borderRadius: "0.5rem",
    overflow: "hidden",
    background: "#020617",
    height: "calc(100vh - 180px)"
  }}>
              <iframe ref={iframeRef} src={(() => {
    const src = previewSrc + "&theme=" + getCurrentColorInHexadecimal() + (iframeState.params.iframeUrl ? "&iframeUrl=" + encodeURIComponent(iframeState.params.iframeUrl) : "");
    return src;
  })()} title="App preview" style={{
    width: "100%",
    height: "calc(100vh - 180px)",
    border: "0"
  }} allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowFullScreen />
            </div>
          </div>
        </div>
      </div>
    </div>;
};

<SideBySideLayout previewSrc="https://crewai-sandbox.sampleapp.ai/crewai-payroll-app?framework=python&isFrame=true">
  <Architecture value="python" label="Python SDK">
    <FrameworkGuide framework="crewai" label="CrewAI">
      # Build an AI Marketing Strategy Crew with CrewAI

      Learn how to create a multi-agent marketing strategy system using CrewAI. This example demonstrates how to orchestrate specialized AI agents that collaborate to research markets, develop strategies, and create compelling marketing content.

      CrewAI enables **autonomous agent collaboration**, **task orchestration**, and **structured workflows** that automate complex business processes with intelligent AI agents.

      <DownloadAndOpenButtons downloadUrl="https://github.com/crewAIInc/crewAI-examples/archive/refs/heads/main.zip" gitUrl="https://github.com/crewAIInc/crewAI-examples/tree/main/crews/marketing_strategy" className="mt-4 mb-6" />

      ### ❶ Set up your CrewAI project

      <CodeFocusSection activeFilePath="pyproject.toml" linesStart={9} linesEnd={12} title="Install dependencies">
        Start by setting up your Python project with CrewAI and required dependencies. The CrewAI framework handles agent orchestration, task management, and workflow execution.

        **Install with uv or pip:**

        ```bash theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
        pip install "crewai[tools]>=0.152.0" "crewai-tools>=0.58.0"
        ```

        **Key packages:**

        * **crewai\[tools]** - Core CrewAI framework with tool support
        * **crewai-tools** - Pre-built tools for agents (web search, scraping, etc.)
      </CodeFocusSection>

      <CodeFocusSection activeFilePath="src/marketing_posts/crew.py" linesStart={31} linesEnd={44} title="Define your crew base class">
        Create a crew class that inherits from `CrewBase`. This provides the foundation for defining agents, tasks, and the crew orchestration. The `@CrewBase` decorator enables automatic configuration loading from YAML files.

        **Key features:**

        * `agents_config` - Path to agents configuration file
        * `tasks_config` - Path to tasks configuration file
        * Decorators for agents (`@agent`) and tasks (`@task`)
      </CodeFocusSection>

      ### ❷ Create specialized AI agents

      <CodeFocusSection activeFilePath="src/marketing_posts/crew.py" linesStart={37} linesEnd={44} title="Define the Lead Market Analyst agent">
        Create your first agent - a Lead Market Analyst that conducts market research. Use the `@agent` decorator to define agents with specific roles, goals, and backstories. Agents can be equipped with tools like `SerperDevTool` for web search and `ScrapeWebsiteTool` for data extraction.

        **Agent capabilities:**

        * **Role**: Defines the agent's professional identity
        * **Goal**: What the agent aims to achieve
        * **Backstory**: Context that shapes agent behavior
        * **Tools**: External capabilities (web search, scraping, APIs)
      </CodeFocusSection>

      <CodeFocusSection activeFilePath="src/marketing_posts/config/agents.yaml" linesStart={1} linesEnd={10} title="Configure agent details in YAML">
        Define agent configurations in YAML for easy customization. Each agent has a role, goal, and backstory that guide its behavior and decision-making. This separation of configuration from code makes it easy to iterate on agent personalities and capabilities.

        **Configuration structure:**

        * **role**: Professional title and responsibilities
        * **goal**: Primary objective
        * **backstory**: Context and expertise background
      </CodeFocusSection>

      <CodeFocusSection activeFilePath="src/marketing_posts/crew.py" linesStart={46} linesEnd={61} title="Add more specialized agents">
        Create additional agents for different aspects of marketing. The Chief Marketing Strategist synthesizes research into strategies, while the Creative Content Creator develops compelling marketing content. Each agent has specialized expertise that contributes to the overall workflow.

        **Agent collaboration:**

        * Agents work sequentially or in parallel
        * Each agent focuses on their area of expertise
        * Output from one agent can inform another
      </CodeFocusSection>

      ### ❸ Define tasks for your crew

      <CodeFocusSection activeFilePath="src/marketing_posts/crew.py" linesStart={63} linesEnd={68} title="Create research task">
        Define tasks that agents will execute. Tasks specify what needs to be done, which agent is responsible, and what the expected output should be. Use the `@task` decorator to automatically register tasks with your crew.

        **Task components:**

        * **description**: What the task accomplishes
        * **agent**: Which agent executes the task
        * **expected\_output**: What should be produced
      </CodeFocusSection>

      <CodeFocusSection activeFilePath="src/marketing_posts/config/tasks.yaml" linesStart={1} linesEnd={11} title="Configure task details in YAML">
        Define task configurations in YAML with dynamic inputs. Use curly braces `{variable_name}` to interpolate values at runtime. Tasks can reference project descriptions, customer domains, and other contextual information.

        **Task features:**

        * Dynamic input interpolation
        * Clear expected outputs
        * Context-aware descriptions
      </CodeFocusSection>

      <CodeFocusSection activeFilePath="src/marketing_posts/crew.py" linesStart={70} linesEnd={100} title="Create sequential task workflow">
        Build a workflow of interconnected tasks. Tasks can depend on outputs from previous tasks using the `context` parameter. This creates a pipeline where each task builds upon the work of previous agents.

        **Workflow pattern:**

        1. Research task gathers market intelligence
        2. Strategy task synthesizes insights
        3. Campaign task generates creative ideas
        4. Copy task creates final content
      </CodeFocusSection>

      ### ❹ Use structured output with Pydantic models

      <CodeFocusSection activeFilePath="src/marketing_posts/crew.py" linesStart={12} linesEnd={30} title="Define output schemas">
        Use Pydantic models to ensure structured, validated output from agents. Define schemas for complex data structures like marketing strategies, campaign ideas, and marketing copy. This ensures consistent, parseable results.

        **Schema benefits:**

        * Type-safe output
        * Automatic validation
        * Structured data for downstream processing
      </CodeFocusSection>

      <CodeFocusSection activeFilePath="src/marketing_posts/crew.py" linesStart={77} linesEnd={83} title="Apply output schemas to tasks">
        Attach Pydantic models to tasks using `output_json`. This tells CrewAI to format the agent's output according to the specified schema, ensuring consistent structure and validation.

        **Output formatting:**

        * Agents produce JSON matching the schema
        * Automatic validation of required fields
        * Type checking and error handling
      </CodeFocusSection>

      ### ❺ Orchestrate the crew workflow

      <CodeFocusSection activeFilePath="src/marketing_posts/crew.py" linesStart={102} linesEnd={111} title="Create and configure the crew">
        Assemble your agents and tasks into a crew. Use `Process.sequential` for tasks that must run in order, or `Process.hierarchical` for more complex workflows. The crew automatically manages task execution, agent assignment, and result passing.

        **Crew configuration:**

        * **agents**: List of all agents in the crew
        * **tasks**: Ordered list of tasks to execute
        * **process**: Execution strategy (sequential/hierarchical)
        * **verbose**: Enable detailed logging
      </CodeFocusSection>

      <CodeFocusSection activeFilePath="src/marketing_posts/main.py" linesStart={6} linesEnd={17} title="Run the crew with inputs">
        Execute your crew by providing inputs that will be interpolated into task descriptions. The crew orchestrates all agents and tasks, passing results between them as needed. Use `kickoff()` to run once or `train()` for iterative improvement.

        **Input structure:**

        * **customer\_domain**: Target customer or company
        * **project\_description**: Detailed project context
        * Custom inputs can be added as needed
      </CodeFocusSection>

      ### ❻ Leverage agent tools for research

      <CodeFocusSection activeFilePath="src/marketing_posts/crew.py" linesStart={9} linesEnd={9} title="Import CrewAI tools">
        CrewAI provides pre-built tools that agents can use to interact with external systems. Import tools like `SerperDevTool` for web search and `ScrapeWebsiteTool` for website content extraction.

        **Available tools:**

        * **SerperDevTool**: Google search API integration
        * **ScrapeWebsiteTool**: Web scraping capabilities
        * **Custom tools**: Build your own tool integrations
      </CodeFocusSection>

      <CodeFocusSection activeFilePath="src/marketing_posts/crew.py" linesStart={39} linesEnd={44} title="Assign tools to agents">
        Equip agents with tools by passing them in the `tools` parameter. Agents will automatically use these tools when needed to complete their tasks. Tools extend agent capabilities beyond just text generation.

        **Tool usage:**

        * Agents decide when to use tools
        * Tools provide real-time data access
        * Results are incorporated into agent reasoning
      </CodeFocusSection>

      ### ❼ Handle task dependencies and context

      <CodeFocusSection activeFilePath="src/marketing_posts/crew.py" linesStart={93} linesEnd={100} title="Pass context between tasks">
        Use the `context` parameter to pass outputs from previous tasks to subsequent tasks. This creates a knowledge pipeline where each task builds upon previous work. Reference tasks using `self.task_name()`.

        **Context passing:**

        * Previous task outputs become available
        * Agents can reference earlier work
        * Creates coherent, connected workflows
      </CodeFocusSection>

      ### ❽ Customize and extend your crew

      <CodeFocusSection activeFilePath="src/marketing_posts/main.py" linesStart={20} linesEnd={37} title="Train your crew iteratively">
        Use the `train()` method to improve your crew's performance over multiple iterations. Training allows the crew to learn from previous runs and refine its approach to tasks.

        **Training benefits:**

        * Iterative improvement
        * Performance optimization
        * Learning from experience
      </CodeFocusSection>

      <CodeFocusSection title="Get started with CrewAI">
        Ready to build your own multi-agent crew? CrewAI makes it easy to create sophisticated AI workflows with specialized agents working together. Explore more examples and start building at [CrewAI](https://www.crewai.com).

        <div className="mt-2">
          <a href="https://www.crewai.com" target="_blank" className="inline-block px-5 py-2 rounded text-white font-mono font-medium transition-colors bg-primary dark:bg-primary-light">
            Start Building with CrewAI
          </a>
        </div>
      </CodeFocusSection>
    </FrameworkGuide>
  </Architecture>
</SideBySideLayout>
