mirror of
https://github.com/open-webui/computer.git
synced 2026-11-15 02:15:44 +03:00
refac
This commit is contained in:
@@ -1,255 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { onMount } from 'svelte';
|
||||
import { getAdminConfig, updateConfig as apiUpdateConfig } from '$lib/apis/admin';
|
||||
import { t } from '$lib/i18n';
|
||||
import ToggleSwitch from '$lib/components/common/ToggleSwitch.svelte';
|
||||
import Spinner from '$lib/components/common/Spinner.svelte';
|
||||
|
||||
let config = $state<Record<string, any>>({});
|
||||
let loading = $state(true);
|
||||
let saving = $state(false);
|
||||
|
||||
// Local state for key inputs (save on blur, not every keystroke)
|
||||
let exaKey = $state('');
|
||||
let tavilyKey = $state('');
|
||||
let braveKey = $state('');
|
||||
let perplexityKey = $state('');
|
||||
let ccKey = $state('');
|
||||
let ccBaseUrl = $state('');
|
||||
let ccModel = $state('');
|
||||
|
||||
async function loadConfig() {
|
||||
try {
|
||||
config = await getAdminConfig();
|
||||
exaKey = config['web.exa_api_key'] || '';
|
||||
tavilyKey = config['web.tavily_api_key'] || '';
|
||||
braveKey = config['web.brave_api_key'] || '';
|
||||
perplexityKey = config['web.perplexity_api_key'] || '';
|
||||
ccKey = config['web.chat_completions_api_key'] || '';
|
||||
ccBaseUrl = config['web.chat_completions_base_url'] || '';
|
||||
ccModel = config['web.chat_completions_model'] || '';
|
||||
} catch {
|
||||
toast.error($t('admin.failedToLoadConfig'));
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function updateConfig(key: string, value: any) {
|
||||
saving = true;
|
||||
try {
|
||||
await apiUpdateConfig({ [key]: value });
|
||||
config[key] = value;
|
||||
toast.success($t('settings.saved'));
|
||||
} catch {
|
||||
toast.error($t('admin.failedToSave'));
|
||||
} finally {
|
||||
saving = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function saveKey(key: string, value: string) {
|
||||
if (value !== (config[key] || '')) {
|
||||
await updateConfig(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
onMount(loadConfig);
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col min-h-full">
|
||||
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">{$t('admin.settings')}</h2>
|
||||
|
||||
{#if loading}
|
||||
<div class="flex justify-center py-8">
|
||||
<Spinner size={16} />
|
||||
</div>
|
||||
{:else}
|
||||
<!-- Auth settings -->
|
||||
<h3 class="text-xs text-gray-400 dark:text-gray-600 mb-2">{$t('admin.authentication')}</h3>
|
||||
|
||||
<div class="flex items-center justify-between h-8">
|
||||
<span class="text-[13px] text-gray-700 dark:text-gray-300">{$t('admin.allowSignUp')}</span>
|
||||
<ToggleSwitch
|
||||
value={config['auth.signup_enabled']}
|
||||
onchange={(v) => updateConfig('auth.signup_enabled', v)}
|
||||
disabled={saving}
|
||||
/>
|
||||
</div>
|
||||
<p class="text-[11px] text-gray-400 dark:text-gray-600 mt-0.5">
|
||||
{config['auth.signup_enabled'] ? $t('admin.signUpEnabled') : $t('admin.signUpDisabled')}
|
||||
</p>
|
||||
|
||||
<!-- Web settings -->
|
||||
<h3 class="text-xs text-gray-400 dark:text-gray-600 mb-2 mt-6">{$t('admin.web')}</h3>
|
||||
|
||||
<!-- Enable/disable toggle -->
|
||||
<div class="flex items-center justify-between h-8">
|
||||
<span class="text-[13px] text-gray-700 dark:text-gray-300">{$t('admin.webEnabled')}</span>
|
||||
<ToggleSwitch
|
||||
value={config['web.enabled'] !== false}
|
||||
onchange={() => updateConfig('web.enabled', config['web.enabled'] === false)}
|
||||
disabled={saving}
|
||||
/>
|
||||
</div>
|
||||
<p class="text-[11px] text-gray-400 dark:text-gray-600 mt-0.5">
|
||||
{config['web.enabled'] !== false ? $t('admin.webEnabledHint') : $t('admin.webDisabledHint')}
|
||||
</p>
|
||||
|
||||
{#if config['web.enabled'] !== false}
|
||||
<!-- Provider selector -->
|
||||
<div class="flex items-center justify-between h-8 mt-3">
|
||||
<span class="text-[13px] text-gray-700 dark:text-gray-300"
|
||||
>{$t('admin.webSearchProvider')}</span
|
||||
>
|
||||
<select
|
||||
class="text-[13px] bg-transparent text-gray-700 dark:text-gray-300 text-right outline-none cursor-pointer"
|
||||
value={config['web.search_provider'] || 'auto'}
|
||||
onchange={(e) =>
|
||||
updateConfig('web.search_provider', (e.target as HTMLSelectElement).value)}
|
||||
disabled={saving}
|
||||
>
|
||||
<option value="auto">{$t('admin.webProviderAuto')}</option>
|
||||
<option value="exa">Exa</option>
|
||||
<option value="tavily">Tavily</option>
|
||||
<option value="brave">Brave</option>
|
||||
<option value="perplexity">Perplexity</option>
|
||||
<option value="duckduckgo">DuckDuckGo</option>
|
||||
<option value="chat_completions">{$t('admin.webChatCompletions')}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Conditionally mounted API key fields -->
|
||||
{@const provider = config['web.search_provider'] || 'auto'}
|
||||
|
||||
{#if provider === 'auto'}
|
||||
<p class="text-[11px] text-gray-400 dark:text-gray-600 mt-2">{$t('admin.webAutoHint')}</p>
|
||||
{:else if provider === 'exa'}
|
||||
<div class="mt-3">
|
||||
<label class="text-xs text-gray-600 dark:text-gray-400 mb-1"
|
||||
>{$t('admin.webExaKey')}</label
|
||||
>
|
||||
<input
|
||||
type="password"
|
||||
class="w-full mt-1 h-7 px-2 rounded-lg text-xs bg-gray-100 dark:bg-white/6 text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-white/8 outline-none focus:border-blue-400 dark:focus:border-blue-500 transition-colors"
|
||||
placeholder="exa-..."
|
||||
bind:value={exaKey}
|
||||
onblur={() => saveKey('web.exa_api_key', exaKey)}
|
||||
disabled={saving}
|
||||
/>
|
||||
<p class="text-[11px] text-gray-400 dark:text-gray-600 mt-0.5">
|
||||
{$t('admin.webExaHint')}
|
||||
</p>
|
||||
</div>
|
||||
{:else if provider === 'tavily'}
|
||||
<div class="mt-3">
|
||||
<label class="text-xs text-gray-600 dark:text-gray-400 mb-1"
|
||||
>{$t('admin.webTavilyKey')}</label
|
||||
>
|
||||
<input
|
||||
type="password"
|
||||
class="w-full mt-1 h-7 px-2 rounded-lg text-xs bg-gray-100 dark:bg-white/6 text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-white/8 outline-none focus:border-blue-400 dark:focus:border-blue-500 transition-colors"
|
||||
placeholder="tvly-..."
|
||||
bind:value={tavilyKey}
|
||||
onblur={() => saveKey('web.tavily_api_key', tavilyKey)}
|
||||
disabled={saving}
|
||||
/>
|
||||
<p class="text-[11px] text-gray-400 dark:text-gray-600 mt-0.5">
|
||||
{$t('admin.webTavilyHint')}
|
||||
</p>
|
||||
</div>
|
||||
{:else if provider === 'brave'}
|
||||
<div class="mt-3">
|
||||
<label class="text-xs text-gray-600 dark:text-gray-400 mb-1"
|
||||
>{$t('admin.webBraveKey')}</label
|
||||
>
|
||||
<input
|
||||
type="password"
|
||||
class="w-full mt-1 h-7 px-2 rounded-lg text-xs bg-gray-100 dark:bg-white/6 text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-white/8 outline-none focus:border-blue-400 dark:focus:border-blue-500 transition-colors"
|
||||
placeholder="BSA..."
|
||||
bind:value={braveKey}
|
||||
onblur={() => saveKey('web.brave_api_key', braveKey)}
|
||||
disabled={saving}
|
||||
/>
|
||||
<p class="text-[11px] text-gray-400 dark:text-gray-600 mt-0.5">
|
||||
{$t('admin.webBraveHint')}
|
||||
</p>
|
||||
</div>
|
||||
{:else if provider === 'duckduckgo'}
|
||||
<p class="text-[11px] text-gray-400 dark:text-gray-600 mt-2">
|
||||
{$t('admin.webDuckDuckGoNote')}
|
||||
</p>
|
||||
{:else if provider === 'perplexity'}
|
||||
<div class="mt-3">
|
||||
<label class="text-xs text-gray-600 dark:text-gray-400 mb-1"
|
||||
>{$t('admin.webPerplexityKey')}</label
|
||||
>
|
||||
<input
|
||||
type="password"
|
||||
class="w-full mt-1 h-7 px-2 rounded-lg text-xs bg-gray-100 dark:bg-white/6 text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-white/8 outline-none focus:border-blue-400 dark:focus:border-blue-500 transition-colors"
|
||||
placeholder="pplx-..."
|
||||
bind:value={perplexityKey}
|
||||
onblur={() => saveKey('web.perplexity_api_key', perplexityKey)}
|
||||
disabled={saving}
|
||||
/>
|
||||
<p class="text-[11px] text-gray-400 dark:text-gray-600 mt-0.5">
|
||||
{$t('admin.webPerplexityHint')}
|
||||
</p>
|
||||
</div>
|
||||
{:else if provider === 'chat_completions'}
|
||||
<div class="mt-3 space-y-3">
|
||||
<div>
|
||||
<label class="text-xs text-gray-600 dark:text-gray-400 mb-1"
|
||||
>{$t('admin.webCcBaseUrl')}</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="w-full mt-1 h-7 px-2 rounded-lg text-xs bg-gray-100 dark:bg-white/6 text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-white/8 outline-none focus:border-blue-400 dark:focus:border-blue-500 transition-colors"
|
||||
placeholder="https://api.perplexity.ai/v1"
|
||||
bind:value={ccBaseUrl}
|
||||
onblur={() => saveKey('web.chat_completions_base_url', ccBaseUrl)}
|
||||
disabled={saving}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-xs text-gray-600 dark:text-gray-400 mb-1"
|
||||
>{$t('admin.webCcKey')}</label
|
||||
>
|
||||
<input
|
||||
type="password"
|
||||
class="w-full mt-1 h-7 px-2 rounded-lg text-xs bg-gray-100 dark:bg-white/6 text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-white/8 outline-none focus:border-blue-400 dark:focus:border-blue-500 transition-colors"
|
||||
placeholder="sk-..."
|
||||
bind:value={ccKey}
|
||||
onblur={() => saveKey('web.chat_completions_api_key', ccKey)}
|
||||
disabled={saving}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-xs text-gray-600 dark:text-gray-400 mb-1"
|
||||
>{$t('admin.webCcModel')}</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="w-full mt-1 h-7 px-2 rounded-lg text-xs bg-gray-100 dark:bg-white/6 text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-white/8 outline-none focus:border-blue-400 dark:focus:border-blue-500 transition-colors"
|
||||
placeholder="sonar-pro"
|
||||
bind:value={ccModel}
|
||||
onblur={() => saveKey('web.chat_completions_model', ccModel)}
|
||||
disabled={saving}
|
||||
/>
|
||||
</div>
|
||||
<p class="text-[11px] text-gray-400 dark:text-gray-600">
|
||||
{$t('admin.webCcHint')}
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<div class="mt-auto pt-6 flex justify-end">
|
||||
<button
|
||||
class="text-[13px] text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white transition-colors duration-100"
|
||||
onclick={() => toast.success($t('settings.saved'))}>{$t('settings.save')}</button
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -4,10 +4,11 @@
|
||||
import CreateUserModal from './CreateUserModal.svelte';
|
||||
import EditUserModal from './EditUserModal.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import { listUsers } from '$lib/apis/admin';
|
||||
import { listUsers, getAdminConfig, updateConfig } from '$lib/apis/admin';
|
||||
import { session } from '$lib/session';
|
||||
import { t } from '$lib/i18n';
|
||||
import Spinner from '$lib/components/common/Spinner.svelte';
|
||||
import ToggleSwitch from '$lib/components/common/ToggleSwitch.svelte';
|
||||
|
||||
interface User {
|
||||
user_id: string;
|
||||
@@ -24,6 +25,10 @@
|
||||
let loading = $state(true);
|
||||
let page = $state(0);
|
||||
|
||||
// Auth config
|
||||
let signupEnabled = $state(false);
|
||||
let savingConfig = $state(false);
|
||||
|
||||
let showCreate = $state(false);
|
||||
let editUser = $state<User | null>(null);
|
||||
|
||||
@@ -41,6 +46,26 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function loadAuthConfig() {
|
||||
try {
|
||||
const config = await getAdminConfig();
|
||||
signupEnabled = config['auth.signup_enabled'] === true;
|
||||
} catch {}
|
||||
}
|
||||
|
||||
async function toggleSignup(value: boolean) {
|
||||
savingConfig = true;
|
||||
try {
|
||||
await updateConfig({ 'auth.signup_enabled': value });
|
||||
signupEnabled = value;
|
||||
toast.success($t('settings.saved'));
|
||||
} catch {
|
||||
toast.error($t('admin.failedToSave'));
|
||||
} finally {
|
||||
savingConfig = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleCreated() {
|
||||
showCreate = false;
|
||||
loadUsers();
|
||||
@@ -52,7 +77,10 @@
|
||||
if (page >= totalPages) page = Math.max(0, totalPages - 1);
|
||||
}
|
||||
|
||||
onMount(loadUsers);
|
||||
onMount(() => {
|
||||
loadUsers();
|
||||
loadAuthConfig();
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
@@ -65,6 +93,21 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Sign-up toggle -->
|
||||
<div class="flex flex-col gap-2.5 mb-3">
|
||||
<label class="flex items-center justify-between cursor-pointer">
|
||||
<span class="text-xs text-gray-600 dark:text-gray-400">{$t('admin.allowSignUp')}</span>
|
||||
<ToggleSwitch
|
||||
value={signupEnabled}
|
||||
onchange={(v) => toggleSignup(v)}
|
||||
disabled={savingConfig}
|
||||
/>
|
||||
</label>
|
||||
<p class="text-[11px] text-gray-400 dark:text-gray-600 -mt-1">
|
||||
{signupEnabled ? $t('admin.signUpEnabled') : $t('admin.signUpDisabled')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{#if loading}
|
||||
<div class="flex justify-center py-8">
|
||||
<Spinner size={16} />
|
||||
|
||||
@@ -0,0 +1,314 @@
|
||||
<script lang="ts">
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { onMount } from 'svelte';
|
||||
import { getAdminConfig, updateConfig } from '$lib/apis/admin';
|
||||
import { t } from '$lib/i18n';
|
||||
import ToggleSwitch from '$lib/components/common/ToggleSwitch.svelte';
|
||||
import Spinner from '$lib/components/common/Spinner.svelte';
|
||||
|
||||
let loading = $state(true);
|
||||
let saving = $state(false);
|
||||
let testing = $state(false);
|
||||
let testResult = $state<{ ok: boolean; message: string } | null>(null);
|
||||
|
||||
// ── Web search ────────────────────────────────────────
|
||||
let webEnabled = $state(true);
|
||||
let searchProvider = $state('auto');
|
||||
let exaKey = $state('');
|
||||
let tavilyKey = $state('');
|
||||
let braveKey = $state('');
|
||||
let perplexityKey = $state('');
|
||||
let ccKey = $state('');
|
||||
let ccBaseUrl = $state('');
|
||||
let ccModel = $state('');
|
||||
|
||||
// ── Browser ───────────────────────────────────────────
|
||||
let browserEnabled = $state(false);
|
||||
let browserProvider = $state<'local' | 'firecrawl' | 'browser_use'>('local');
|
||||
let cdpUrl = $state('http://localhost:9222');
|
||||
let autoLaunch = $state(true);
|
||||
let sessionTimeout = $state(10);
|
||||
let firecrawlApiKey = $state('');
|
||||
let firecrawlBaseUrl = $state('https://api.firecrawl.dev');
|
||||
let browserUseApiKey = $state('');
|
||||
let browserUseBaseUrl = $state('https://api.browser-use.com');
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
const config = await getAdminConfig();
|
||||
|
||||
// Web search
|
||||
webEnabled = config['web.enabled'] !== false;
|
||||
searchProvider = config['web.search_provider'] || 'auto';
|
||||
exaKey = (config['web.exa_api_key'] as string) || '';
|
||||
tavilyKey = (config['web.tavily_api_key'] as string) || '';
|
||||
braveKey = (config['web.brave_api_key'] as string) || '';
|
||||
perplexityKey = (config['web.perplexity_api_key'] as string) || '';
|
||||
ccKey = (config['web.chat_completions_api_key'] as string) || '';
|
||||
ccBaseUrl = (config['web.chat_completions_base_url'] as string) || '';
|
||||
ccModel = (config['web.chat_completions_model'] as string) || '';
|
||||
|
||||
// Browser
|
||||
browserEnabled = config['browser.enabled'] === true || config['browser.enabled'] === 'true';
|
||||
browserProvider = (config['browser.provider'] as typeof browserProvider) || 'local';
|
||||
cdpUrl = (config['browser.cdp_url'] as string) || 'http://localhost:9222';
|
||||
autoLaunch = config['browser.auto_launch'] !== false && config['browser.auto_launch'] !== 'false';
|
||||
sessionTimeout = Number(config['browser.session_timeout_minutes']) || 10;
|
||||
firecrawlApiKey = (config['browser.firecrawl_api_key'] as string) || '';
|
||||
firecrawlBaseUrl = (config['browser.firecrawl_base_url'] as string) || 'https://api.firecrawl.dev';
|
||||
browserUseApiKey = (config['browser.browser_use_api_key'] as string) || '';
|
||||
browserUseBaseUrl = (config['browser.browser_use_base_url'] as string) || 'https://api.browser-use.com';
|
||||
} catch {
|
||||
toast.error($t('admin.failedToLoadConfig'));
|
||||
}
|
||||
loading = false;
|
||||
});
|
||||
|
||||
async function save() {
|
||||
saving = true;
|
||||
try {
|
||||
const cfg: Record<string, unknown> = {
|
||||
'web.enabled': webEnabled,
|
||||
'web.search_provider': searchProvider,
|
||||
'web.exa_api_key': exaKey,
|
||||
'web.tavily_api_key': tavilyKey,
|
||||
'web.brave_api_key': braveKey,
|
||||
'web.perplexity_api_key': perplexityKey,
|
||||
'web.chat_completions_api_key': ccKey,
|
||||
'web.chat_completions_base_url': ccBaseUrl,
|
||||
'web.chat_completions_model': ccModel,
|
||||
'browser.enabled': browserEnabled,
|
||||
'browser.provider': browserProvider,
|
||||
'browser.cdp_url': cdpUrl,
|
||||
'browser.auto_launch': autoLaunch,
|
||||
'browser.session_timeout_minutes': sessionTimeout,
|
||||
'browser.firecrawl_api_key': firecrawlApiKey,
|
||||
'browser.firecrawl_base_url': firecrawlBaseUrl,
|
||||
'browser.browser_use_api_key': browserUseApiKey,
|
||||
'browser.browser_use_base_url': browserUseBaseUrl
|
||||
};
|
||||
await updateConfig(cfg);
|
||||
toast.success($t('settings.saved'));
|
||||
} catch {
|
||||
toast.error($t('admin.failedToSave'));
|
||||
} finally {
|
||||
saving = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function testConnection() {
|
||||
testing = true;
|
||||
testResult = null;
|
||||
try {
|
||||
const resp = await fetch(`${cdpUrl}/json/version`);
|
||||
if (resp.ok) {
|
||||
const data = await resp.json();
|
||||
testResult = { ok: true, message: data.Browser || 'Connected' };
|
||||
} else {
|
||||
testResult = { ok: false, message: `HTTP ${resp.status}` };
|
||||
}
|
||||
} catch {
|
||||
testResult = { ok: false, message: 'Could not connect' };
|
||||
} finally {
|
||||
testing = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col min-h-full">
|
||||
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">{$t('admin.web')}</h2>
|
||||
|
||||
{#if loading}
|
||||
<div class="flex justify-center py-8"><Spinner size={16} /></div>
|
||||
{:else}
|
||||
<!-- Search -->
|
||||
<h3 class="text-xs text-gray-400 dark:text-gray-600 mb-2">Search</h3>
|
||||
|
||||
<div class="flex flex-col gap-2.5">
|
||||
<label class="flex items-center justify-between cursor-pointer">
|
||||
<span class="text-xs text-gray-600 dark:text-gray-400">{$t('admin.webEnabled')}</span>
|
||||
<ToggleSwitch value={webEnabled} onchange={(v) => { webEnabled = v; }} />
|
||||
</label>
|
||||
<p class="text-[11px] text-gray-400 dark:text-gray-600 -mt-1">
|
||||
{webEnabled ? $t('admin.webEnabledHint') : $t('admin.webDisabledHint')}
|
||||
</p>
|
||||
|
||||
{#if webEnabled}
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-xs text-gray-600 dark:text-gray-400">{$t('admin.webSearchProvider')}</span>
|
||||
<select
|
||||
bind:value={searchProvider}
|
||||
class="bg-transparent text-xs text-gray-600 dark:text-gray-400 outline-none cursor-pointer"
|
||||
>
|
||||
<option value="auto">{$t('admin.webProviderAuto')}</option>
|
||||
<option value="exa">Exa</option>
|
||||
<option value="tavily">Tavily</option>
|
||||
<option value="brave">Brave</option>
|
||||
<option value="perplexity">Perplexity</option>
|
||||
<option value="duckduckgo">DuckDuckGo</option>
|
||||
<option value="chat_completions">{$t('admin.webChatCompletions')}</option>
|
||||
</select>
|
||||
</div>
|
||||
<p class="text-[11px] text-gray-400 dark:text-gray-600 -mt-1">
|
||||
{#if searchProvider === 'auto'}
|
||||
{$t('admin.webAutoHint')}
|
||||
{:else if searchProvider === 'duckduckgo'}
|
||||
{$t('admin.webDuckDuckGoNote')}
|
||||
{/if}
|
||||
</p>
|
||||
|
||||
{#if searchProvider === 'exa'}
|
||||
<div>
|
||||
<label class="text-xs text-gray-600 dark:text-gray-400" for="exa-key">{$t('admin.webExaKey')}</label>
|
||||
<input id="exa-key" type="password" bind:value={exaKey} placeholder="exa-..."
|
||||
class="w-full mt-1 h-7 px-2 rounded-lg text-xs bg-gray-100 dark:bg-white/6 text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-white/8 outline-none focus:border-blue-400 dark:focus:border-blue-500 transition-colors" />
|
||||
<p class="text-[11px] text-gray-400 dark:text-gray-600 mt-0.5">{$t('admin.webExaHint')}</p>
|
||||
</div>
|
||||
{:else if searchProvider === 'tavily'}
|
||||
<div>
|
||||
<label class="text-xs text-gray-600 dark:text-gray-400" for="tavily-key">{$t('admin.webTavilyKey')}</label>
|
||||
<input id="tavily-key" type="password" bind:value={tavilyKey} placeholder="tvly-..."
|
||||
class="w-full mt-1 h-7 px-2 rounded-lg text-xs bg-gray-100 dark:bg-white/6 text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-white/8 outline-none focus:border-blue-400 dark:focus:border-blue-500 transition-colors" />
|
||||
<p class="text-[11px] text-gray-400 dark:text-gray-600 mt-0.5">{$t('admin.webTavilyHint')}</p>
|
||||
</div>
|
||||
{:else if searchProvider === 'brave'}
|
||||
<div>
|
||||
<label class="text-xs text-gray-600 dark:text-gray-400" for="brave-key">{$t('admin.webBraveKey')}</label>
|
||||
<input id="brave-key" type="password" bind:value={braveKey} placeholder="BSA..."
|
||||
class="w-full mt-1 h-7 px-2 rounded-lg text-xs bg-gray-100 dark:bg-white/6 text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-white/8 outline-none focus:border-blue-400 dark:focus:border-blue-500 transition-colors" />
|
||||
<p class="text-[11px] text-gray-400 dark:text-gray-600 mt-0.5">{$t('admin.webBraveHint')}</p>
|
||||
</div>
|
||||
{:else if searchProvider === 'perplexity'}
|
||||
<div>
|
||||
<label class="text-xs text-gray-600 dark:text-gray-400" for="pplx-key">{$t('admin.webPerplexityKey')}</label>
|
||||
<input id="pplx-key" type="password" bind:value={perplexityKey} placeholder="pplx-..."
|
||||
class="w-full mt-1 h-7 px-2 rounded-lg text-xs bg-gray-100 dark:bg-white/6 text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-white/8 outline-none focus:border-blue-400 dark:focus:border-blue-500 transition-colors" />
|
||||
<p class="text-[11px] text-gray-400 dark:text-gray-600 mt-0.5">{$t('admin.webPerplexityHint')}</p>
|
||||
</div>
|
||||
{:else if searchProvider === 'chat_completions'}
|
||||
<div>
|
||||
<label class="text-xs text-gray-600 dark:text-gray-400" for="cc-base-url">{$t('admin.webCcBaseUrl')}</label>
|
||||
<input id="cc-base-url" type="text" bind:value={ccBaseUrl} placeholder="https://api.perplexity.ai/v1"
|
||||
class="w-full mt-1 h-7 px-2 rounded-lg text-xs bg-gray-100 dark:bg-white/6 text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-white/8 outline-none focus:border-blue-400 dark:focus:border-blue-500 transition-colors" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-xs text-gray-600 dark:text-gray-400" for="cc-key">{$t('admin.webCcKey')}</label>
|
||||
<input id="cc-key" type="password" bind:value={ccKey} placeholder="sk-..."
|
||||
class="w-full mt-1 h-7 px-2 rounded-lg text-xs bg-gray-100 dark:bg-white/6 text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-white/8 outline-none focus:border-blue-400 dark:focus:border-blue-500 transition-colors" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-xs text-gray-600 dark:text-gray-400" for="cc-model">{$t('admin.webCcModel')}</label>
|
||||
<input id="cc-model" type="text" bind:value={ccModel} placeholder="sonar-pro"
|
||||
class="w-full mt-1 h-7 px-2 rounded-lg text-xs bg-gray-100 dark:bg-white/6 text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-white/8 outline-none focus:border-blue-400 dark:focus:border-blue-500 transition-colors" />
|
||||
</div>
|
||||
<p class="text-[11px] text-gray-400 dark:text-gray-600 -mt-1">{$t('admin.webCcHint')}</p>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Browser -->
|
||||
<h3 class="text-xs text-gray-400 dark:text-gray-600 mb-2 mt-5">Browser</h3>
|
||||
|
||||
<div class="flex flex-col gap-2.5">
|
||||
<label class="flex items-center justify-between cursor-pointer">
|
||||
<span class="text-xs text-gray-600 dark:text-gray-400">Browser tools</span>
|
||||
<ToggleSwitch value={browserEnabled} onchange={(v) => { browserEnabled = v; }} />
|
||||
</label>
|
||||
<p class="text-[11px] text-gray-400 dark:text-gray-600 -mt-1">
|
||||
Give the AI access to a web browser for navigating pages, clicking elements, and taking screenshots.
|
||||
</p>
|
||||
|
||||
{#if browserEnabled}
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-xs text-gray-600 dark:text-gray-400">Provider</span>
|
||||
<select
|
||||
bind:value={browserProvider}
|
||||
class="bg-transparent text-xs text-gray-600 dark:text-gray-400 outline-none cursor-pointer"
|
||||
>
|
||||
<option value="local">Local CDP</option>
|
||||
<option value="firecrawl">Firecrawl</option>
|
||||
<option value="browser_use">Browser-Use</option>
|
||||
</select>
|
||||
</div>
|
||||
<p class="text-[11px] text-gray-400 dark:text-gray-600 -mt-1">
|
||||
{#if browserProvider === 'local'}
|
||||
Connects to Chrome via DevTools Protocol. Full interactive browsing with clicking, typing, and screenshots.
|
||||
{:else if browserProvider === 'firecrawl'}
|
||||
Cloud API that converts web pages to markdown. Fast extraction, no interactive browsing.
|
||||
{:else}
|
||||
Cloud API for LLM-driven browser tasks. Describe what you need in natural language.
|
||||
{/if}
|
||||
</p>
|
||||
|
||||
{#if browserProvider === 'local'}
|
||||
<label class="flex items-center justify-between cursor-pointer">
|
||||
<div>
|
||||
<span class="text-xs text-gray-600 dark:text-gray-400">Auto-launch Chrome</span>
|
||||
<p class="text-[10px] text-gray-400 dark:text-gray-600">Start a headless Chrome if none is running</p>
|
||||
</div>
|
||||
<ToggleSwitch value={autoLaunch} onchange={(v) => { autoLaunch = v; }} />
|
||||
</label>
|
||||
|
||||
<div>
|
||||
<label class="text-xs text-gray-600 dark:text-gray-400" for="cdp-url">CDP URL</label>
|
||||
<div class="flex gap-1.5 mt-1">
|
||||
<input id="cdp-url" type="text" bind:value={cdpUrl} placeholder="http://localhost:9222"
|
||||
class="flex-1 h-7 px-2 rounded-lg text-xs bg-gray-100 dark:bg-white/6 text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-white/8 outline-none focus:border-blue-400 dark:focus:border-blue-500 transition-colors" />
|
||||
<button
|
||||
class="h-7 px-2.5 rounded-lg text-xs bg-gray-200/50 dark:bg-white/8 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white transition-colors disabled:opacity-50"
|
||||
onclick={() => testConnection()}
|
||||
disabled={testing}
|
||||
>{testing ? '...' : 'Test'}</button>
|
||||
</div>
|
||||
{#if testResult}
|
||||
<p class="text-[11px] mt-1 {testResult.ok ? 'text-emerald-600 dark:text-emerald-400' : 'text-red-500'}">
|
||||
{testResult.message}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="text-xs text-gray-600 dark:text-gray-400" for="session-timeout">Session timeout</label>
|
||||
<div class="flex items-center gap-1.5 mt-1">
|
||||
<input id="session-timeout" type="number" bind:value={sessionTimeout} min="1" max="120"
|
||||
class="w-16 h-7 px-2 rounded-lg text-xs bg-gray-100 dark:bg-white/6 text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-white/8 outline-none focus:border-blue-400 dark:focus:border-blue-500 transition-colors" />
|
||||
<span class="text-[11px] text-gray-400 dark:text-gray-600">minutes</span>
|
||||
</div>
|
||||
</div>
|
||||
{:else if browserProvider === 'firecrawl'}
|
||||
<div>
|
||||
<label class="text-xs text-gray-600 dark:text-gray-400" for="fc-key">API Key</label>
|
||||
<input id="fc-key" type="password" bind:value={firecrawlApiKey} placeholder="fc-..."
|
||||
class="w-full mt-1 h-7 px-2 rounded-lg text-xs bg-gray-100 dark:bg-white/6 text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-white/8 outline-none focus:border-blue-400 dark:focus:border-blue-500 transition-colors" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-xs text-gray-600 dark:text-gray-400" for="fc-url">Base URL</label>
|
||||
<input id="fc-url" type="text" bind:value={firecrawlBaseUrl} placeholder="https://api.firecrawl.dev"
|
||||
class="w-full mt-1 h-7 px-2 rounded-lg text-xs bg-gray-100 dark:bg-white/6 text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-white/8 outline-none focus:border-blue-400 dark:focus:border-blue-500 transition-colors" />
|
||||
<p class="text-[11px] text-gray-400 dark:text-gray-600 mt-0.5">Change for self-hosted Firecrawl instances</p>
|
||||
</div>
|
||||
{:else if browserProvider === 'browser_use'}
|
||||
<div>
|
||||
<label class="text-xs text-gray-600 dark:text-gray-400" for="bu-key">API Key</label>
|
||||
<input id="bu-key" type="password" bind:value={browserUseApiKey} placeholder="bu-..."
|
||||
class="w-full mt-1 h-7 px-2 rounded-lg text-xs bg-gray-100 dark:bg-white/6 text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-white/8 outline-none focus:border-blue-400 dark:focus:border-blue-500 transition-colors" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-xs text-gray-600 dark:text-gray-400" for="bu-url">Base URL</label>
|
||||
<input id="bu-url" type="text" bind:value={browserUseBaseUrl} placeholder="https://api.browser-use.com"
|
||||
class="w-full mt-1 h-7 px-2 rounded-lg text-xs bg-gray-100 dark:bg-white/6 text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-white/8 outline-none focus:border-blue-400 dark:focus:border-blue-500 transition-colors" />
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Save -->
|
||||
<div class="mt-auto pt-6 flex justify-end">
|
||||
<button
|
||||
class="text-[13px] text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white transition-colors duration-100 disabled:opacity-50"
|
||||
onclick={() => save()}
|
||||
disabled={saving}
|
||||
>{$t('settings.save')}</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -360,6 +360,13 @@
|
||||
<path d="M13 2.04932C13 2.04932 16 6 16 12C16 18 13 21.9507 13 21.9507" />
|
||||
<path d="M11 21.9507C11 21.9507 8 18 8 12C8 6 11 2.04932 11 2.04932" />
|
||||
<path d="M2 12H22" />
|
||||
{:else if name === 'globe'}
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<path d="M2 12H22" />
|
||||
<path d="M12 2C14.5 5.5 14.5 18.5 12 22" />
|
||||
<path d="M12 2C9.5 5.5 9.5 18.5 12 22" />
|
||||
<path d="M4 7H20" />
|
||||
<path d="M4 17H20" />
|
||||
{:else if name === 'microphone'}
|
||||
<path
|
||||
d="M12 1C10.3431 1 9 2.34315 9 4V12C9 13.6569 10.3431 15 12 15C13.6569 15 15 13.6569 15 12V4C15 2.34315 13.6569 1 12 1Z"
|
||||
|
||||
@@ -1,254 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { toast } from 'svelte-sonner';
|
||||
import ToggleSwitch from '../common/ToggleSwitch.svelte';
|
||||
import Spinner from '../common/Spinner.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import { getAdminConfig, updateConfig } from '$lib/apis/admin';
|
||||
import { t } from '$lib/i18n';
|
||||
|
||||
let loading = $state(true);
|
||||
let saving = $state(false);
|
||||
let testing = $state(false);
|
||||
let testResult = $state<{ ok: boolean; message: string } | null>(null);
|
||||
|
||||
// Config state
|
||||
let enabled = $state(false);
|
||||
let provider = $state<'local' | 'firecrawl' | 'browser_use'>('local');
|
||||
let cdpUrl = $state('http://localhost:9222');
|
||||
let autoLaunch = $state(true);
|
||||
let sessionTimeout = $state(10);
|
||||
let firecrawlApiKey = $state('');
|
||||
let firecrawlBaseUrl = $state('https://api.firecrawl.dev');
|
||||
let browserUseApiKey = $state('');
|
||||
let browserUseBaseUrl = $state('https://api.browser-use.com');
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
const config = await getAdminConfig();
|
||||
enabled = config['browser.enabled'] === true || config['browser.enabled'] === 'true';
|
||||
provider = (config['browser.provider'] as typeof provider) || 'local';
|
||||
cdpUrl = (config['browser.cdp_url'] as string) || 'http://localhost:9222';
|
||||
autoLaunch = config['browser.auto_launch'] !== false && config['browser.auto_launch'] !== 'false';
|
||||
sessionTimeout = Number(config['browser.session_timeout_minutes']) || 10;
|
||||
firecrawlApiKey = (config['browser.firecrawl_api_key'] as string) || '';
|
||||
firecrawlBaseUrl = (config['browser.firecrawl_base_url'] as string) || 'https://api.firecrawl.dev';
|
||||
browserUseApiKey = (config['browser.browser_use_api_key'] as string) || '';
|
||||
browserUseBaseUrl = (config['browser.browser_use_base_url'] as string) || 'https://api.browser-use.com';
|
||||
} catch {}
|
||||
loading = false;
|
||||
});
|
||||
|
||||
async function save() {
|
||||
saving = true;
|
||||
try {
|
||||
await updateConfig({
|
||||
'browser.enabled': enabled,
|
||||
'browser.provider': provider,
|
||||
'browser.cdp_url': cdpUrl,
|
||||
'browser.auto_launch': autoLaunch,
|
||||
'browser.session_timeout_minutes': sessionTimeout,
|
||||
'browser.firecrawl_api_key': firecrawlApiKey,
|
||||
'browser.firecrawl_base_url': firecrawlBaseUrl,
|
||||
'browser.browser_use_api_key': browserUseApiKey,
|
||||
'browser.browser_use_base_url': browserUseBaseUrl
|
||||
});
|
||||
toast.success($t('settings.saved'));
|
||||
} catch {
|
||||
toast.error('Failed to save browser settings');
|
||||
} finally {
|
||||
saving = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function testConnection() {
|
||||
testing = true;
|
||||
testResult = null;
|
||||
try {
|
||||
const resp = await fetch(`${cdpUrl}/json/version`);
|
||||
if (resp.ok) {
|
||||
const data = await resp.json();
|
||||
testResult = { ok: true, message: data.Browser || 'Connected' };
|
||||
} else {
|
||||
testResult = { ok: false, message: `HTTP ${resp.status}` };
|
||||
}
|
||||
} catch {
|
||||
testResult = { ok: false, message: 'Could not connect' };
|
||||
} finally {
|
||||
testing = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col min-h-full">
|
||||
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">Browser</h2>
|
||||
|
||||
{#if loading}
|
||||
<div class="flex justify-center py-8"><Spinner size={16} /></div>
|
||||
{:else}
|
||||
<!-- Enable -->
|
||||
<h3 class="text-xs text-gray-400 dark:text-gray-600 mb-2">Enable</h3>
|
||||
|
||||
<div class="flex flex-col gap-2.5">
|
||||
<label class="flex items-center justify-between cursor-pointer">
|
||||
<span class="text-xs text-gray-600 dark:text-gray-400">Browser tools</span>
|
||||
<ToggleSwitch value={enabled} onchange={(v) => { enabled = v; }} />
|
||||
</label>
|
||||
<p class="text-[11px] text-gray-400 dark:text-gray-600 -mt-1">
|
||||
Give the AI access to a web browser for navigating pages, clicking elements, and taking screenshots.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{#if enabled}
|
||||
<!-- Provider -->
|
||||
<h3 class="text-xs text-gray-400 dark:text-gray-600 mb-2 mt-5">Provider</h3>
|
||||
|
||||
<div class="flex gap-1">
|
||||
{#each [
|
||||
{ value: 'local' as const, label: 'Local CDP' },
|
||||
{ value: 'firecrawl' as const, label: 'Firecrawl' },
|
||||
{ value: 'browser_use' as const, label: 'Browser-Use' }
|
||||
] as opt}
|
||||
<button
|
||||
class="flex items-center gap-1.5 h-7 px-2.5 rounded-lg text-xs transition-colors duration-100
|
||||
{provider === opt.value
|
||||
? 'bg-gray-200/50 dark:bg-white/8 text-gray-900 dark:text-white font-medium'
|
||||
: 'text-gray-500 hover:text-gray-700 dark:hover:text-gray-300'}"
|
||||
onclick={() => { provider = opt.value; }}
|
||||
>
|
||||
{opt.label}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
<p class="text-[11px] text-gray-400 dark:text-gray-600 mt-1">
|
||||
{#if provider === 'local'}
|
||||
Connects to Chrome via DevTools Protocol. Full interactive browsing with clicking, typing, and screenshots.
|
||||
{:else if provider === 'firecrawl'}
|
||||
Cloud API that converts web pages to markdown. Fast extraction, no interactive browsing.
|
||||
{:else}
|
||||
Cloud API for LLM-driven browser tasks. Describe what you need in natural language.
|
||||
{/if}
|
||||
</p>
|
||||
|
||||
<!-- Local CDP settings -->
|
||||
{#if provider === 'local'}
|
||||
<h3 class="text-xs text-gray-400 dark:text-gray-600 mb-2 mt-5">Connection</h3>
|
||||
|
||||
<div class="flex flex-col gap-2.5">
|
||||
<label class="flex items-center justify-between cursor-pointer">
|
||||
<div>
|
||||
<span class="text-xs text-gray-600 dark:text-gray-400">Auto-launch Chrome</span>
|
||||
<p class="text-[10px] text-gray-400 dark:text-gray-600">Start a headless Chrome if none is running</p>
|
||||
</div>
|
||||
<ToggleSwitch value={autoLaunch} onchange={(v) => { autoLaunch = v; }} />
|
||||
</label>
|
||||
|
||||
<div>
|
||||
<label class="text-xs text-gray-600 dark:text-gray-400" for="cdp-url">CDP URL</label>
|
||||
<div class="flex gap-1.5 mt-1">
|
||||
<input
|
||||
id="cdp-url"
|
||||
type="text"
|
||||
bind:value={cdpUrl}
|
||||
placeholder="http://localhost:9222"
|
||||
class="flex-1 h-7 px-2 rounded-lg text-xs bg-gray-100 dark:bg-white/6 text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-white/8 outline-none focus:border-blue-400 dark:focus:border-blue-500 transition-colors"
|
||||
/>
|
||||
<button
|
||||
class="h-7 px-2.5 rounded-lg text-xs bg-gray-200/50 dark:bg-white/8 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white transition-colors disabled:opacity-50"
|
||||
onclick={() => testConnection()}
|
||||
disabled={testing}
|
||||
>
|
||||
{testing ? '...' : 'Test'}
|
||||
</button>
|
||||
</div>
|
||||
{#if testResult}
|
||||
<p class="text-[11px] mt-1 {testResult.ok ? 'text-emerald-600 dark:text-emerald-400' : 'text-red-500'}">
|
||||
{testResult.message}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="text-xs text-gray-600 dark:text-gray-400" for="session-timeout">Session timeout</label>
|
||||
<div class="flex items-center gap-1.5 mt-1">
|
||||
<input
|
||||
id="session-timeout"
|
||||
type="number"
|
||||
bind:value={sessionTimeout}
|
||||
min="1"
|
||||
max="120"
|
||||
class="w-16 h-7 px-2 rounded-lg text-xs bg-gray-100 dark:bg-white/6 text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-white/8 outline-none focus:border-blue-400 dark:focus:border-blue-500 transition-colors"
|
||||
/>
|
||||
<span class="text-[11px] text-gray-400 dark:text-gray-600">minutes</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Firecrawl settings -->
|
||||
{#if provider === 'firecrawl'}
|
||||
<h3 class="text-xs text-gray-400 dark:text-gray-600 mb-2 mt-5">Firecrawl</h3>
|
||||
|
||||
<div class="flex flex-col gap-2.5">
|
||||
<div>
|
||||
<label class="text-xs text-gray-600 dark:text-gray-400" for="fc-key">API Key</label>
|
||||
<input
|
||||
id="fc-key"
|
||||
type="password"
|
||||
bind:value={firecrawlApiKey}
|
||||
placeholder="fc-..."
|
||||
class="w-full mt-1 h-7 px-2 rounded-lg text-xs bg-gray-100 dark:bg-white/6 text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-white/8 outline-none focus:border-blue-400 dark:focus:border-blue-500 transition-colors"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-xs text-gray-600 dark:text-gray-400" for="fc-url">Base URL</label>
|
||||
<input
|
||||
id="fc-url"
|
||||
type="text"
|
||||
bind:value={firecrawlBaseUrl}
|
||||
placeholder="https://api.firecrawl.dev"
|
||||
class="w-full mt-1 h-7 px-2 rounded-lg text-xs bg-gray-100 dark:bg-white/6 text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-white/8 outline-none focus:border-blue-400 dark:focus:border-blue-500 transition-colors"
|
||||
/>
|
||||
<p class="text-[11px] text-gray-400 dark:text-gray-600 mt-1">Change for self-hosted Firecrawl instances</p>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Browser-Use settings -->
|
||||
{#if provider === 'browser_use'}
|
||||
<h3 class="text-xs text-gray-400 dark:text-gray-600 mb-2 mt-5">Browser-Use</h3>
|
||||
|
||||
<div class="flex flex-col gap-2.5">
|
||||
<div>
|
||||
<label class="text-xs text-gray-600 dark:text-gray-400" for="bu-key">API Key</label>
|
||||
<input
|
||||
id="bu-key"
|
||||
type="password"
|
||||
bind:value={browserUseApiKey}
|
||||
placeholder="bu-..."
|
||||
class="w-full mt-1 h-7 px-2 rounded-lg text-xs bg-gray-100 dark:bg-white/6 text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-white/8 outline-none focus:border-blue-400 dark:focus:border-blue-500 transition-colors"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-xs text-gray-600 dark:text-gray-400" for="bu-url">Base URL</label>
|
||||
<input
|
||||
id="bu-url"
|
||||
type="text"
|
||||
bind:value={browserUseBaseUrl}
|
||||
placeholder="https://api.browser-use.com"
|
||||
class="w-full mt-1 h-7 px-2 rounded-lg text-xs bg-gray-100 dark:bg-white/6 text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-white/8 outline-none focus:border-blue-400 dark:focus:border-blue-500 transition-colors"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<!-- Save -->
|
||||
<div class="mt-auto pt-6 flex justify-end">
|
||||
<button
|
||||
class="text-[13px] text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white transition-colors duration-100 disabled:opacity-50"
|
||||
onclick={() => save()}
|
||||
disabled={saving}
|
||||
>{$t('settings.save')}</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -4,7 +4,6 @@
|
||||
import General from './Settings/General.svelte';
|
||||
import Account from './Settings/Account.svelte';
|
||||
import Keyboard from './Settings/Keyboard.svelte';
|
||||
import Browser from './Settings/Browser.svelte';
|
||||
import About from './Settings/About.svelte';
|
||||
import Users from './Admin/Users.svelte';
|
||||
import Connections from './Admin/Connections.svelte';
|
||||
@@ -12,14 +11,13 @@
|
||||
import Messaging from './Admin/Messaging.svelte';
|
||||
import Gateway from './Admin/Gateway.svelte';
|
||||
import AudioSettings from './Admin/AudioSettings.svelte';
|
||||
import AdminSettings from './Admin/Settings.svelte';
|
||||
import AdminWeb from './Admin/Web.svelte';
|
||||
import { session } from '$lib/session';
|
||||
import { t } from '$lib/i18n';
|
||||
|
||||
type Tab =
|
||||
| 'general'
|
||||
| 'keyboard'
|
||||
| 'browser'
|
||||
| 'account'
|
||||
| 'about'
|
||||
| 'users'
|
||||
@@ -28,7 +26,7 @@
|
||||
| 'messaging'
|
||||
| 'gateway'
|
||||
| 'audio'
|
||||
| 'admin_settings';
|
||||
| 'web';
|
||||
|
||||
interface Props {
|
||||
onclose: () => void;
|
||||
@@ -55,8 +53,7 @@
|
||||
{ id: 'messaging', label: $t('admin.messaging'), icon: 'chat-bubble' },
|
||||
{ id: 'gateway', label: $t('admin.gateway.tab'), icon: 'gateway' },
|
||||
{ id: 'audio', label: 'Audio', icon: 'microphone' },
|
||||
{ id: 'browser', label: 'Browser', icon: 'browser' },
|
||||
{ id: 'admin_settings', label: $t('settings.configuration'), icon: 'shield' }
|
||||
{ id: 'web', label: $t('admin.web'), icon: 'globe' }
|
||||
]);
|
||||
</script>
|
||||
|
||||
@@ -117,8 +114,6 @@
|
||||
<General />
|
||||
{:else if activeTab === 'keyboard'}
|
||||
<Keyboard />
|
||||
{:else if activeTab === 'browser'}
|
||||
<Browser />
|
||||
{:else if activeTab === 'account'}
|
||||
<Account />
|
||||
{:else if activeTab === 'about'}
|
||||
@@ -135,8 +130,8 @@
|
||||
<Gateway />
|
||||
{:else if activeTab === 'audio'}
|
||||
<AudioSettings />
|
||||
{:else if activeTab === 'admin_settings'}
|
||||
<AdminSettings />
|
||||
{:else if activeTab === 'web'}
|
||||
<AdminWeb />
|
||||
{/if}
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
@@ -322,7 +322,6 @@
|
||||
"general.interruptDesc": "Sending a message cancels the current generation.",
|
||||
|
||||
"settings.keyboard": "Keyboard",
|
||||
"settings.configuration": "Configuration",
|
||||
|
||||
"keyboard.title": "Keyboard Shortcuts",
|
||||
"keyboard.resetDefaults": "Reset defaults",
|
||||
|
||||
@@ -295,7 +295,6 @@
|
||||
"general.queueDesc": "Les messages envoyés pendant la génération sont mis en file et envoyés après la fin.",
|
||||
"general.interruptDesc": "L'envoi d'un message annule la génération en cours.",
|
||||
"settings.keyboard": "Clavier",
|
||||
"settings.configuration": "Configuration",
|
||||
"keyboard.title": "Raccourcis clavier",
|
||||
"keyboard.resetDefaults": "Réinitialiser",
|
||||
"keyboard.command": "Commande",
|
||||
|
||||
Reference in New Issue
Block a user