diff --git a/app/src/components/settings/panels/MessagingPanel.tsx b/app/src/components/settings/panels/MessagingPanel.tsx index bb8f7dfa0..2f74f1a72 100644 --- a/app/src/components/settings/panels/MessagingPanel.tsx +++ b/app/src/components/settings/panels/MessagingPanel.tsx @@ -19,120 +19,11 @@ import type { ChannelType, } from '../../../types/channels'; import { openUrl } from '../../../utils/openUrl'; -import ChannelCapabilities from '../../channels/ChannelCapabilities'; import ChannelFieldInput from '../../channels/ChannelFieldInput'; import ChannelStatusBadge from '../../channels/ChannelStatusBadge'; import SettingsHeader from '../components/SettingsHeader'; import { useSettingsNavigation } from '../hooks/useSettingsNavigation'; -const STATUS_STYLES: Record = { - connected: { label: 'Connected', className: 'bg-sage-50 text-sage-700 border-sage-200' }, - connecting: { label: 'Connecting', className: 'bg-amber-50 text-amber-700 border-amber-200' }, - disconnected: { - label: 'Disconnected', - className: 'bg-stone-100 text-stone-600 border-stone-200', - }, - error: { label: 'Error', className: 'bg-coral-50 text-coral-600 border-coral-200' }, -}; - -const AUTH_MODE_LABELS: Record = { - managed_dm: 'Managed DM', - oauth: 'OAuth Sign-in', - bot_token: 'Bot Token', - api_key: 'API Key', -}; - -/** Fallback definitions used when the core sidecar is unreachable. */ -const FALLBACK_DEFINITIONS: ChannelDefinition[] = [ - { - id: 'telegram', - display_name: 'Telegram', - description: 'Send and receive messages via Telegram.', - icon: 'telegram', - auth_modes: [ - { - mode: 'bot_token', - description: 'Provide your own Telegram Bot token from @BotFather.', - fields: [ - { - key: 'bot_token', - label: 'Bot Token', - field_type: 'secret', - required: true, - placeholder: '123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11', - }, - { - key: 'allowed_users', - label: 'Allowed Users', - field_type: 'string', - required: false, - placeholder: 'Comma-separated Telegram usernames', - }, - ], - auth_action: undefined, - }, - { - mode: 'managed_dm', - description: 'Message the OpenHuman Telegram bot directly.', - fields: [], - auth_action: 'telegram_managed_dm', - }, - ], - capabilities: ['send_text', 'receive_text', 'typing', 'draft_updates'], - }, - { - id: 'discord', - display_name: 'Discord', - description: 'Send and receive messages via Discord.', - icon: 'discord', - auth_modes: [ - { - mode: 'bot_token', - description: 'Provide your own Discord bot token.', - fields: [ - { - key: 'bot_token', - label: 'Bot Token', - field_type: 'secret', - required: true, - placeholder: 'Your Discord bot token', - }, - { - key: 'guild_id', - label: 'Server (Guild) ID', - field_type: 'string', - required: false, - placeholder: 'Optional: restrict to a specific server', - }, - ], - auth_action: undefined, - }, - { - mode: 'oauth', - description: 'Install the OpenHuman bot to your Discord server via OAuth.', - fields: [], - auth_action: 'discord_oauth', - }, - ], - capabilities: ['send_text', 'receive_text', 'typing', 'threaded_replies'], - }, - { - id: 'web', - display_name: 'Web', - description: 'Chat via the built-in web UI.', - icon: 'web', - auth_modes: [ - { - mode: 'managed_dm', - description: 'Use the embedded web chat — no setup required.', - fields: [], - auth_action: undefined, - }, - ], - capabilities: ['send_text', 'send_rich_text', 'receive_text'], - }, -]; - const MessagingPanel = () => { const { navigateBack } = useSettingsNavigation(); const dispatch = useAppDispatch(); @@ -364,9 +255,9 @@ const MessagingPanel = () => { key={field.key} field={field} value={fieldValues[compositeKey]?.[field.key] ?? ''} - onChange={e => updateField(compositeKey, field.key, e.target.value)} - placeholder={field.placeholder || field.label} - className="w-full rounded-lg border border-stone-200 bg-white px-3 py-2 text-sm text-stone-900 placeholder:text-stone-400 focus:outline-none focus:border-primary-500/60" + onChange={value => updateField(compositeKey, field.key, value)} + // placeholder={field.placeholder || field.label} + // className="w-full rounded-lg border border-stone-200 bg-white px-3 py-2 text-sm text-stone-900 placeholder:text-stone-400 focus:outline-none focus:border-primary-500/60" /> ))} diff --git a/app/src/components/settings/panels/WebhooksDebugPanel.tsx b/app/src/components/settings/panels/WebhooksDebugPanel.tsx index 925cb1703..88d32afe8 100644 --- a/app/src/components/settings/panels/WebhooksDebugPanel.tsx +++ b/app/src/components/settings/panels/WebhooksDebugPanel.tsx @@ -1,7 +1,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; -import { getCoreHttpBaseUrl } from '../../../services/coreRpcClient'; import { tunnelsApi } from '../../../services/api/tunnelsApi'; +import { getCoreHttpBaseUrl } from '../../../services/coreRpcClient'; import { BACKEND_URL } from '../../../utils/config'; import { openhumanWebhooksClearLogs, diff --git a/app/src/components/skills/AuthModeSelector.tsx b/app/src/components/skills/AuthModeSelector.tsx new file mode 100644 index 000000000..a4e6ebdf3 --- /dev/null +++ b/app/src/components/skills/AuthModeSelector.tsx @@ -0,0 +1,139 @@ +/** + * Card-based selector for skill authentication modes. + * Displays available auth modes (managed, self_hosted, text) as clickable cards. + * For managed mode, clicking immediately triggers the OAuth flow. + */ + +import type { ReactElement } from "react"; +import type { AuthMode } from "../../lib/skills/types.ts"; + +interface AuthModeSelectorProps { + modes: AuthMode[]; + onSelect: (mode: AuthMode) => void; + disabled?: boolean; +} + +const MODE_ICONS: Record ReactElement> = { + managed: ({ className }) => ( + + + + ), + self_hosted: ({ className }) => ( + + + + ), + text: ({ className }) => ( + + + + ), +}; + +const DEFAULT_LABELS: Record = { + managed: "OpenHuman Managed", + self_hosted: "Self-hosted", + text: "Credential Text", +}; + +const DEFAULT_DESCRIPTIONS: Record = { + managed: "One-click setup through OpenHuman", + self_hosted: "Enter your own API credentials", + text: "Paste credential content directly", +}; + +function formatProviderName(provider: string): string { + const names: Record = { + notion: "Notion", + google: "Google", + github: "GitHub", + slack: "Slack", + discord: "Discord", + twitter: "Twitter", + linear: "Linear", + gitlab: "GitLab", + }; + return names[provider] ?? provider.charAt(0).toUpperCase() + provider.slice(1); +} + +export default function AuthModeSelector({ + modes, + onSelect, + disabled, +}: AuthModeSelectorProps) { + return ( +
+
+

+ Choose how to connect +

+

+ Select an authentication method +

+
+ + {modes.map((mode) => { + const IconComponent = MODE_ICONS[mode.type] ?? MODE_ICONS.self_hosted; + const label = + mode.type === "managed" && mode.provider + ? mode.label ?? `Connect with ${formatProviderName(mode.provider)}` + : mode.label ?? DEFAULT_LABELS[mode.type] ?? mode.type; + const description = + mode.description ?? DEFAULT_DESCRIPTIONS[mode.type] ?? ""; + + return ( + + ); + })} +
+ ); +} diff --git a/app/src/components/skills/SetupFormRenderer.tsx b/app/src/components/skills/SetupFormRenderer.tsx index dd25d45d0..f9624fe38 100644 --- a/app/src/components/skills/SetupFormRenderer.tsx +++ b/app/src/components/skills/SetupFormRenderer.tsx @@ -174,6 +174,22 @@ export default function SetupFormRenderer({ )} + {/* Textarea */} + {field.type === "textarea" && ( +