File size: 4,451 Bytes
3201ca6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
import { useState, type ReactNode } from "react";
import { Trans, useTranslation } from "react-i18next";
import { I18nKey } from "#/i18n/declaration";
import ChevronDownIcon from "#/icons/chevron-down.svg?react";
import MessageSquareShareIcon from "#/icons/message-square-share.svg?react";
import { cn } from "#/utils/utils";
import { BrandButton } from "#/components/features/settings/brand-button";
import { useLaunchSkillInChat } from "#/hooks/use-launch-skill-in-chat";
import { useActiveBackend } from "#/contexts/active-backend-context";
import { useTracking } from "#/hooks/use-tracking";
import { getAutomationsDocsUrl } from "#/manifests/automation-interface";

function InlineExampleWrap({ children }: { children?: ReactNode }) {
  return <span className="whitespace-nowrap">{children}</span>;
}

function InlineCodeChip({ children }: { children?: ReactNode }) {
  return (
    <code
      data-testid="automations-create-instructions-example"
      className={cn(
        "mx-0.5 inline-block rounded-sm border border-[var(--oh-border-subtle)]",
        "bg-[var(--oh-surface-raised)] px-1.5 py-0.5 align-baseline font-mono text-[11px] text-white",
      )}
    >
      {children}
    </code>
  );
}

function InlinePunctuation({ children }: { children?: ReactNode }) {
  return <>{children}</>;
}

const CREATE_INSTRUCTIONS_INLINE_COMPONENTS = {
  example: <InlineExampleWrap />,
  cmd: <InlineCodeChip />,
  punct: <InlinePunctuation />,
};

interface CreateInstructionsProps {
  /** If true, the instructions are collapsible and start collapsed */
  collapsible?: boolean;
}

interface CreateInstructionsContentProps {
  onLaunch?: () => void;
}

export function CreateInstructionsContent({
  onLaunch,
}: CreateInstructionsContentProps = {}) {
  const { t } = useTranslation("openhands");
  const launchInChat = useLaunchSkillInChat();
  const active = useActiveBackend();
  const { trackAutomationCreatedButton } = useTracking();

  const handleCreateAutomation = () => {
    trackAutomationCreatedButton({ backendKind: active.backend.kind });
    launchInChat(t(I18nKey.AUTOMATIONS$CREATE_AUTOMATION_PROMPT), onLaunch);
  };

  return (
    <div className="flex flex-col gap-5">
      <p className="text-sm leading-relaxed text-tertiary-light">
        <Trans
          ns="openhands"
          i18nKey={I18nKey.AUTOMATIONS$EMPTY_OPTION_CONVERSATION_DESC}
          components={CREATE_INSTRUCTIONS_INLINE_COMPONENTS}
        />{" "}
        {t(I18nKey.AUTOMATIONS$CREATE_INSTRUCTIONS_GUIDANCE)}{" "}
        <a
          href={getAutomationsDocsUrl()}
          target="_blank"
          rel="noopener noreferrer"
          className="text-muted underline transition-colors hover:text-foreground"
        >
          {t(I18nKey.AUTOMATIONS$EMPTY_LEARN_MORE)}
        </a>
      </p>

      <div className="flex justify-center">
        <BrandButton
          type="button"
          variant="primary"
          testId="automations-create-automation"
          onClick={handleCreateAutomation}
          startContent={
            <MessageSquareShareIcon className="size-4" aria-hidden />
          }
        >
          {t(I18nKey.AUTOMATIONS$CREATE_AUTOMATION_BUTTON)}
        </BrandButton>
      </div>
    </div>
  );
}

export function CreateInstructions({
  collapsible = false,
}: CreateInstructionsProps) {
  const { t } = useTranslation("openhands");
  const [isExpanded, setIsExpanded] = useState(!collapsible);

  if (collapsible) {
    return (
      <div className="w-full rounded-lg border border-[var(--oh-border)] bg-[var(--oh-surface)]">
        <button
          type="button"
          onClick={() => setIsExpanded(!isExpanded)}
          aria-expanded={isExpanded}
          className="flex w-full items-center justify-between rounded-lg p-4 text-left transition-colors hover:bg-surface-raised"
        >
          <span className="text-sm font-normal text-content">
            {t(I18nKey.AUTOMATIONS$EMPTY_HOW_TO_CREATE_TITLE)}
          </span>
          <ChevronDownIcon
            className={cn(
              "size-5 text-muted transition-transform",
              isExpanded && "rotate-180",
            )}
          />
        </button>
        {isExpanded ? (
          <div className="px-4 pb-4">
            <CreateInstructionsContent />
          </div>
        ) : null}
      </div>
    );
  }

  return (
    <div className="w-full max-w-2xl">
      <CreateInstructionsContent />
    </div>
  );
}