Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 0 additions & 53 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,6 @@ tracing-subscriber = { version = "0.3", default-features = false, features = ["f
tracing-appender = "0.2"
prometheus = { version = "0.14", default-features = false }
urlencoding = "2.1"
motosan-ai-oauth = { version = "0.2", features = ["codex"] }
thiserror = "2.0"
ring = "0.17"
prost = { version = "0.14", default-features = false }
Expand Down
2 changes: 0 additions & 2 deletions app/src/lib/i18n/chunks/de-3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,6 @@ const de3: TranslationMap = {
'subconscious.decision.failed': 'Fehlgeschlagen',
'subconscious.decision.cancelled': 'Abgesagt',
'subconscious.decision.skipped': 'Übersprungen',
'subconscious.providerUnavailableTitle': 'Unterbewusstsein pausiert',
'subconscious.providerSettings': 'KI-Einstellungen',
'actionable.complete': 'Komplett',
'actionable.dismiss': 'Entlassen',
'actionable.snooze': 'Schlummern',
Expand Down
22 changes: 0 additions & 22 deletions app/src/lib/i18n/chunks/de-5.ts
Original file line number Diff line number Diff line change
Expand Up @@ -523,28 +523,6 @@ const de5: TranslationMap = {
'settings.mascot.colorYellow': 'Gelb',
'settings.mascot.libraryUnavailable': 'OpenHuman Bibliothek nicht verfügbar',
'settings.mascot.title': 'OpenHuman',
'settings.developerMenu.mcpServer.title': 'MCP-Server',
'settings.developerMenu.mcpServer.desc':
'Externe MCP-Clients für die Verbindung zu OpenHuman konfigurieren',
'settings.mcpServer.title': 'MCP-Server',
'settings.mcpServer.toolsSectionTitle': 'Verfügbare Tools',
'settings.mcpServer.toolsSectionDesc':
'Tools, die über den MCP-stdio-Server bereitgestellt werden, wenn openhuman-core mcp läuft',
'settings.mcpServer.configSectionTitle': 'Client-Konfiguration',
'settings.mcpServer.configSectionDesc':
'Wähle deinen MCP-Client, um den passenden Konfigurations-Snippet zu erzeugen',
'settings.mcpServer.copySnippet': 'In Zwischenablage kopieren',
'settings.mcpServer.copied': 'Kopiert!',
'settings.mcpServer.openConfigFile': 'Konfigurationsdatei öffnen',
'settings.mcpServer.binaryPathNotFound':
'OpenHuman-Binary nicht gefunden. Bei Quellbau bitte mit `cargo build --bin openhuman-core` bauen.',
'settings.mcpServer.openConfigError': 'Konfigurationsdatei konnte nicht geöffnet werden',
'settings.mcpServer.clientClaudeDesktop': 'Claude Desktop',
'settings.mcpServer.clientCursor': 'Cursor',
'settings.mcpServer.clientCodex': 'Codex',
'settings.mcpServer.clientZed': 'Zed',
'settings.mcpServer.configFilePath': 'Konfigurationsdatei',
'settings.mcpServer.clientSelectorAriaLabel': 'MCP-Client-Auswahl',
};

export default de5;
155 changes: 9 additions & 146 deletions app/src/pages/onboarding/steps/ApiKeysStep.tsx
Original file line number Diff line number Diff line change
@@ -1,109 +1,25 @@
import { useCallback, useEffect, useState } from 'react';
import { useState } from 'react';

import { useT } from '../../../lib/i18n/I18nContext';
import { setCloudProviderKey } from '../../../services/api/aiSettingsApi';
import { callCoreRpc } from '../../../services/coreRpcClient';
import { openUrl } from '../../../utils/openUrl';
import { isTauri } from '../../../utils/tauriCommands/common';
import OnboardingNextButton from '../components/OnboardingNextButton';

interface ApiKeysStepProps {
onNext: () => void;
onSkip: () => void;
}

type OpenAiOAuthStatus = { connected: boolean; authMethod?: string | null };

const OPENAI_OAUTH_CONNECTED_LABEL = 'Connected with ChatGPT';
const OPENAI_OAUTH_CONNECT_LABEL = 'Sign in with ChatGPT';
const OPENAI_OAUTH_CALLBACK_HINT =
'After signing in, paste the full redirect URL from your browser (starts with http://127.0.0.1:1455/).';
const OPENAI_OAUTH_CALLBACK_PLACEHOLDER = 'http://127.0.0.1:1455/auth/callback?code=...&state=...';

const ApiKeysStep = ({ onNext, onSkip }: ApiKeysStepProps) => {
const { t } = useT();
const [openai, setOpenai] = useState('');
const [anthropic, setAnthropic] = useState('');
const [saving, setSaving] = useState(false);
const [error, setError] = useState<string | null>(null);
const [oauthConnected, setOauthConnected] = useState(false);
const [oauthBusy, setOauthBusy] = useState(false);
const [oauthAwaitingCallback, setOauthAwaitingCallback] = useState(false);
const [oauthCallbackUrl, setOauthCallbackUrl] = useState('');

const refreshOAuthStatus = useCallback(async () => {
if (!isTauri()) {
return;
}
try {
const res = await callCoreRpc<{ result: OpenAiOAuthStatus }>({
method: 'openhuman.inference_openai_oauth_status',
params: {},
});
setOauthConnected(Boolean(res?.result?.connected));
} catch (err) {
console.debug('[onboarding:api-keys] oauth status check failed', err);
}
}, []);

useEffect(() => {
void refreshOAuthStatus();
}, [refreshOAuthStatus]);

const handleOpenAiOAuthStart = async () => {
if (!isTauri()) {
setError('ChatGPT sign-in is only available in the desktop app.');
return;
}
setOauthBusy(true);
setError(null);
try {
const res = await callCoreRpc<{ result: { authUrl: string } }>({
method: 'openhuman.inference_openai_oauth_start',
params: {},
});
const authUrl = res?.result?.authUrl?.trim();
if (!authUrl) {
throw new Error('missing authUrl');
}
setOauthAwaitingCallback(true);
await openUrl(authUrl);
} catch (err) {
console.warn('[onboarding:api-keys] oauth start failed', err);
setError('Could not start ChatGPT sign-in. Try again or use an API key.');
} finally {
setOauthBusy(false);
}
};

const handleOpenAiOAuthComplete = async () => {
const callback = oauthCallbackUrl.trim();
if (!callback) {
setError('Paste the redirect URL from your browser after signing in.');
return;
}
setOauthBusy(true);
setError(null);
try {
await callCoreRpc({
method: 'openhuman.inference_openai_oauth_complete',
params: { callback_url: callback },
});
setOauthCallbackUrl('');
setOauthAwaitingCallback(false);
setOauthConnected(true);
} catch (err) {
console.warn('[onboarding:api-keys] oauth complete failed', err);
setError('ChatGPT sign-in did not complete. Check the redirect URL and try again.');
} finally {
setOauthBusy(false);
}
};

const handleSave = async () => {
const trimmedOpenai = openai.trim();
const trimmedAnthropic = anthropic.trim();
if (!trimmedOpenai && !trimmedAnthropic && !oauthConnected) {
if (!trimmedOpenai && !trimmedAnthropic) {
onSkip();
return;
}
Expand Down Expand Up @@ -140,65 +56,12 @@ const ApiKeysStep = ({ onNext, onSkip }: ApiKeysStepProps) => {
</div>

<div className="mt-6 flex flex-col gap-4">
<div className="flex flex-col gap-2 rounded-lg border border-stone-200 dark:border-neutral-800 bg-stone-50 dark:bg-neutral-800/40 p-3">
<div className="flex flex-wrap items-center justify-between gap-2">
<span className="text-xs font-medium text-stone-700 dark:text-neutral-200">
{t('onboarding.apiKeys.openaiLabel')}
</span>
{oauthConnected ? (
<span
data-testid="onboarding-openai-oauth-connected"
className="text-xs font-medium text-sage-700 dark:text-sage-300">
{OPENAI_OAUTH_CONNECTED_LABEL}
</span>
) : null}
</div>
<p className="text-[11px] text-stone-500 dark:text-neutral-400">
Use ChatGPT Plus/Pro (subscription) or an OpenAI API key — not both required.
</p>
<button
type="button"
data-testid="onboarding-openai-oauth-connect"
disabled={oauthBusy || oauthConnected || saving}
onClick={() => void handleOpenAiOAuthStart()}
className="rounded-lg border border-primary-500 bg-primary-50 dark:bg-primary-500/10 px-3 py-2 text-sm font-medium text-primary-700 dark:text-primary-300 hover:bg-primary-100 dark:hover:bg-primary-500/20 disabled:opacity-50">
{oauthBusy ? 'Opening sign-in…' : OPENAI_OAUTH_CONNECT_LABEL}
</button>
{oauthAwaitingCallback && !oauthConnected ? (
<div className="flex flex-col gap-1.5">
<p className="text-[11px] text-stone-500 dark:text-neutral-400">
{OPENAI_OAUTH_CALLBACK_HINT}
</p>
<input
data-testid="onboarding-openai-oauth-callback-input"
type="text"
autoComplete="off"
spellCheck={false}
placeholder={OPENAI_OAUTH_CALLBACK_PLACEHOLDER}
value={oauthCallbackUrl}
onChange={e => {
setOauthCallbackUrl(e.target.value);
setError(null);
}}
className="rounded-lg border border-stone-300 dark:border-neutral-700 bg-white dark:bg-neutral-900 px-3 py-2 text-xs text-stone-900 dark:text-neutral-100 placeholder-stone-400 dark:placeholder-neutral-500 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500"
/>
<button
type="button"
data-testid="onboarding-openai-oauth-complete"
disabled={oauthBusy || saving}
onClick={() => void handleOpenAiOAuthComplete()}
className="self-start text-xs font-medium text-primary-600 dark:text-primary-400 underline disabled:opacity-50">
Finish ChatGPT sign-in
</button>
</div>
) : null}
<div className="relative flex items-center gap-2 py-1">
<div className="h-px flex-1 bg-stone-200 dark:bg-neutral-700" />
<span className="text-[10px] uppercase tracking-wide text-stone-400 dark:text-neutral-500">
or API key
</span>
<div className="h-px flex-1 bg-stone-200 dark:bg-neutral-700" />
</div>
<div className="flex flex-col gap-1.5">
<label
htmlFor="onboarding-openai-key"
className="text-xs font-medium text-stone-700 dark:text-neutral-200">
{t('onboarding.apiKeys.openaiLabel')}
</label>
<input
id="onboarding-openai-key"
data-testid="onboarding-api-keys-openai-input"
Expand Down Expand Up @@ -254,7 +117,7 @@ const ApiKeysStep = ({ onNext, onSkip }: ApiKeysStepProps) => {
type="button"
onClick={onSkip}
disabled={saving}
className="text-xs text-stone-500 dark:text-neutral-400 hover:text-stone-700 dark:hover:text-neutral-200 underline disabled:opacity-50">
className="text-xs text-stone-500 dark:text-neutral-400 hover:text-stone-700 dark:hover:text-neutral-200 dark:text-neutral-200 underline disabled:opacity-50">
{t('onboarding.apiKeys.skipForNow')}
</button>
</div>
Expand Down
Loading