+
-
-
-
- {/* Time Stats - Standardized Design */}
-
-
Today's Overview
-
-
-
-
-
-
-
{formatTime(getTodayStats().totalSeconds)}
-
Total Time Today
-
-
-
-
-
-
-
-
{getTodayStats().totalEntries}
-
Entries Today
-
-
-
-
-
-
-
-
{formatAmount(getTodayStats().totalBillableAmount)}
-
- Billable Today
- {currentRunningEntry() && currentRunningEntry()?.billable && (
-
- ● Live
-
- )}
-
-
-
-
-
-
-
-
-
{getTodayStats().runningCount}
-
Running Timers
-
-
-
+
Time Tracking
+
+ Track your time with solidtime.io integration
+
-
- {/* Time Entries List */}
-
-
+
+
+
+
+
+
+
+
+ {currentRunningEntry() ? 'Timer Running' : 'Ready to Track'}
+
+
+ {currentRunningEntry()
+ ? currentRunningEntry().description || 'No description'
+ : 'Start tracking your time'}
+
+
+
+
+
+ {formatTime(elapsedSeconds())}
+
+
+ {currentRunningEntry() ? 'Running' : 'Stopped'}
+
+
+
+
+ {!currentRunningEntry() ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
Total Today
+
{formatTime(stats().total)}
+
+
+
+
+
+
+
+
+
+
Billable
+
{formatTime(stats().billable)}
+
+
+
+
+
+
+
+
+
+
Non-Billable
+
{formatTime(stats().nonBillable)}
+
+
+
+
+
+ Recent Time Entries
+ {loading() ? (
+
+ ) : timeEntries().length === 0 ? (
+
+
+
No time entries yet
+
Start tracking your time to see entries here
+
+ ) : (
+
+ {timeEntries().map((entry) => (
+
+
+
+
+
{entry.description || 'Untitled'}
+
+ {new Date(entry.start).toLocaleString()}
+
+
+
+
+
+ {entry.billable ? 'Billable' : 'Non-Billable'}
+
+
+ {formatDuration(entry.start, entry.end)}
+
+
+
+ ))}
+
+ )}
+
);
};
diff --git a/frontend/src/pages/settings/BrowserExtensionSettings.tsx b/frontend/src/pages/settings/BrowserExtensionSettings.tsx
index a8ab52a..0d6e18a 100644
--- a/frontend/src/pages/settings/BrowserExtensionSettings.tsx
+++ b/frontend/src/pages/settings/BrowserExtensionSettings.tsx
@@ -2,6 +2,7 @@ import { createSignal, createEffect, Show, For } from 'solid-js';
import { Card } from '@/components/ui/Card';
import { Button } from '@/components/ui/Button';
import { Input } from '@/components/ui/Input';
+import { ConfirmModal } from '@/components/ui/ConfirmModal';
import { toast } from '@/components/ui/Toast';
import { CheckCircle, AlertCircle, Shield, Key, Globe, Clock, Users, Settings } from 'lucide-solid';
import { getApiV1BaseUrl } from '@/lib/api-url';
@@ -55,12 +56,19 @@ const BrowserExtensionSettings = () => {
'files:write'
]);
const [activeTab, setActiveTab] = createSignal<'overview' | 'api-keys' | 'extensions' | 'examples'>('api-keys');
+ const [showConfirmModal, setShowConfirmModal] = createSignal(false);
+ const [confirmModalConfig, setConfirmModalConfig] = createSignal({
+ title: 'Confirm Action',
+ message: 'Are you sure?',
+ onConfirm: () => {}
+ });
+ const [confirmTarget, setConfirmTarget] = createSignal<{type: 'key' | 'extension', id: number | string} | null>(null);
const quickStartGuides: QuickStartGuide[] = [
{
title: 'Generate API Key',
description: 'Create a secure API key for your browser extension',
- icon:
,
+ icon:
,
steps: [
'Go to Settings → Browser Extension',
'Click "Generate New Key"',
@@ -71,7 +79,7 @@ const BrowserExtensionSettings = () => {
{
title: 'Configure Extension',
description: 'Set up your browser extension with the API key',
- icon:
,
+ icon:
,
steps: [
'Install the Trackeep browser extension',
'Open extension options',
@@ -83,7 +91,7 @@ const BrowserExtensionSettings = () => {
{
title: 'Security Best Practices',
description: 'Keep your API keys secure and monitor usage',
- icon:
,
+ icon:
,
steps: [
'Use unique names for each key',
'Set expiration dates for temporary access',
@@ -258,12 +266,20 @@ curl -X POST \\\n -H "Authorization: Bearer tk_your_api_key_here" \\\n -H "Con
};
const revokeAPIKey = async (keyId: number) => {
- if (!confirm('Are you sure you want to revoke this API key? This action cannot be undone.')) {
- return;
- }
+ setConfirmTarget({type: 'key', id: keyId});
+ setConfirmModalConfig({
+ title: 'Revoke API Key',
+ message: 'Are you sure you want to revoke this API key? This action cannot be undone.',
+ onConfirm: confirmRevokeAPIKey
+ });
+ setShowConfirmModal(true);
+ };
+ const confirmRevokeAPIKey = async () => {
+ const target = confirmTarget();
+ if (!target || target.type !== 'key') return;
try {
- const response = await fetch(`${apiBaseUrl}/browser-extension/api-keys/${keyId}`, {
+ const response = await fetch(`${apiBaseUrl}/browser-extension/api-keys/${target.id}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`
@@ -279,16 +295,27 @@ curl -X POST \\\n -H "Authorization: Bearer tk_your_api_key_here" \\\n -H "Con
}
} catch (error) {
toast.error('Error revoking API key');
+ } finally {
+ setShowConfirmModal(false);
+ setConfirmTarget(null);
}
};
const revokeExtension = async (extensionId: string) => {
- if (!confirm('Are you sure you want to revoke this extension? This action cannot be undone.')) {
- return;
- }
+ setConfirmTarget({type: 'extension', id: extensionId});
+ setConfirmModalConfig({
+ title: 'Revoke Extension',
+ message: 'Are you sure you want to revoke this extension? This action cannot be undone.',
+ onConfirm: confirmRevokeExtension
+ });
+ setShowConfirmModal(true);
+ };
+ const confirmRevokeExtension = async () => {
+ const target = confirmTarget();
+ if (!target || target.type !== 'extension') return;
try {
- const response = await fetch(`${apiBaseUrl}/browser-extension/extensions/${extensionId}`, {
+ const response = await fetch(`${apiBaseUrl}/browser-extension/extensions/${target.id}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`
@@ -304,6 +331,9 @@ curl -X POST \\\n -H "Authorization: Bearer tk_your_api_key_here" \\\n -H "Con
}
} catch (error) {
toast.error('Error revoking extension');
+ } finally {
+ setShowConfirmModal(false);
+ setConfirmTarget(null);
}
};
@@ -324,55 +354,55 @@ curl -X POST \\\n -H "Authorization: Bearer tk_your_api_key_here" \\\n -H "Con
return (
-
Browser Extension Settings
-
Manage API keys and browser extensions for secure access to your Trackeep account.
+
Browser Extension Settings
+
Manage API keys and browser extensions for secure access to your Trackeep account.
{/* Tab Navigation */}
-
+
@@ -385,18 +415,18 @@ curl -X POST \\\n -H "Authorization: Bearer tk_your_api_key_here" \\\n -H "Con
{guide => (
-
+
{guide.icon}
-
{guide.title}
-
{guide.description}
+
{guide.title}
+
{guide.description}
{step => (
- {step}
+ {step}
)}
@@ -408,8 +438,8 @@ curl -X POST \\\n -H "Authorization: Bearer tk_your_api_key_here" \\\n -H "Con
-
-
+
+
Security Status
@@ -417,42 +447,42 @@ curl -X POST \\\n -H "Authorization: Bearer tk_your_api_key_here" \\\n -H "Con
API Keys Secure
-
All keys using secure API key authentication
+
All keys using secure API key authentication
Extensions Registered
-
{extensions().length} active extensions
+
{extensions().length} active extensions
Quick Setup Available
-
Get started in under 5 minutes
+
Get started in under 5 minutes
-
-
+
+
Recent Activity
-
-
Last API key created:
+
+
Last API key created:
{apiKeys().length > 0 ? new Date(apiKeys()[0].created_at).toLocaleDateString() : 'No keys created yet'}
-
-
Total API keys:
+
+
Total API keys:
{apiKeys().length} active, {apiKeys().filter(k => !k.is_active).length} revoked
-
-
Extensions active:
+
+
Extensions active:
{extensions().length} connected
@@ -464,18 +494,18 @@ curl -X POST \\\n -H "Authorization: Bearer tk_your_api_key_here" \\\n -H "Con
-
API Keys
-
-
-
Create New API Key
+
+
Create New API Key
-
+
setNewKeyName((e.target as HTMLInputElement).value)}
@@ -485,7 +515,7 @@ curl -X POST \\\n -H "Authorization: Bearer tk_your_api_key_here" \\\n -H "Con
-
+
{permission => (
@@ -494,11 +524,11 @@ curl -X POST \\\n -H "Authorization: Bearer tk_your_api_key_here" \\\n -H "Con
type="checkbox"
checked={newKeyPermissions().includes(permission.id)}
onChange={() => togglePermission(permission.id)}
- class="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
+ class="rounded border-input text-primary focus:ring-primary"
/>
- {permission.label}
- {permission.description}
+ {permission.label}
+ {permission.description}
)}
@@ -508,15 +538,14 @@ curl -X POST \\\n -H "Authorization: Bearer tk_your_api_key_here" \\\n -H "Con
setShowCreateKey(false)}
- class="btn-secondary"
>
Cancel
{loading() ? 'Creating...' : 'Create Key'}
@@ -527,24 +556,24 @@ curl -X POST \\\n -H "Authorization: Bearer tk_your_api_key_here" \\\n -H "Con
{key => (
-
+
-
{key.name}
-
+
{key.name}
+
Created: {new Date(key.created_at).toLocaleDateString()}
{permission => (
-
+
{permission}
)}
{key.expires_at && (
-
+
Expires: {new Date(key.expires_at).toLocaleDateString()}
)}
@@ -556,8 +585,8 @@ curl -X POST \\\n -H "Authorization: Bearer tk_your_api_key_here" \\\n -H "Con
{key.is_active ? 'Active' : 'Revoked'}
revokeAPIKey(key.id)}
- class="btn-secondary btn-sm"
disabled={!key.is_active}
>
Revoke
@@ -572,51 +601,53 @@ curl -X POST \\\n -H "Authorization: Bearer tk_your_api_key_here" \\\n -H "Con
{/* Browser Extensions Section */}
-
-
-
Registered Extensions
-
Manage browser extensions that have access to your account.
-
+
+
+
+
Registered Extensions
+
Manage browser extensions that have access to your account.
+
-
-
- {extension => (
-
-
-
-
{extension.name}
-
- Extension ID: {extension.extension_id}
-
-
- Registered: {new Date(extension.created_at).toLocaleDateString()}
-
- {extension.last_seen && (
-
- Last seen: {new Date(extension.last_seen).toLocaleDateString()}
+
+
+ {extension => (
+
+
+
+
{extension.name}
+
+ Extension ID: {extension.extension_id}
- )}
-
-
-
- {extension.is_active ? 'Active' : 'Revoked'}
-
-
revokeExtension(extension.extension_id)}
- class="btn-secondary btn-sm"
- disabled={!extension.is_active}
- >
- Revoke
-
+
+ Registered: {new Date(extension.created_at).toLocaleDateString()}
+
+ {extension.last_seen && (
+
+ Last seen: {new Date(extension.last_seen).toLocaleDateString()}
+
+ )}
+
+
+
+ {extension.is_active ? 'Active' : 'Revoked'}
+
+ revokeExtension(extension.extension_id)}
+ disabled={!extension.is_active}
+ >
+ Revoke
+
+
-
- )}
-
-
-
+ )}
+
+
+
+
{/* Examples Tab */}
@@ -625,34 +656,33 @@ curl -X POST \\\n -H "Authorization: Bearer tk_your_api_key_here" \\\n -H "Con
{example => (
-
-
-
{example.language.toUpperCase()}
+
+
+ {example.language.toUpperCase()}
{example.title}
-
{example.description}
+
{example.description}
-
-
+
{
navigator.clipboard.writeText(example.code);
toast.success('Code copied to clipboard!');
}}
- class="btn-secondary"
>
Copy Code
window.open(`https://your-trackeep.com/api/v1/browser-extension/validate`, '_blank')}
- class="btn-primary"
+ onClick={() => window.open(`${apiBaseUrl}/browser-extension/validate`, '_blank')}
>
Test API
@@ -662,6 +692,19 @@ curl -X POST \\\n -H "Authorization: Bearer tk_your_api_key_here" \\\n -H "Con
+
+ {
+ setShowConfirmModal(false);
+ setConfirmTarget(null);
+ }}
+ onConfirm={() => confirmModalConfig().onConfirm()}
+ title={confirmModalConfig().title}
+ message={confirmModalConfig().message}
+ confirmText="Revoke"
+ type="danger"
+ />
);
};
diff --git a/frontend/src/pages/settings/Settings.tsx b/frontend/src/pages/settings/Settings.tsx
index a19f11c..6ae8bcd 100644
--- a/frontend/src/pages/settings/Settings.tsx
+++ b/frontend/src/pages/settings/Settings.tsx
@@ -1,11 +1,10 @@
import { createSignal, onMount, Show, For } from 'solid-js';
import { useNavigate } from '@solidjs/router';
import { useAuth } from '@/lib/auth';
-import { IconUser, IconLock, IconKey, IconBrain, IconMail, IconSend, IconShield, IconDownload } from '@tabler/icons-solidjs';
+import { IconUser, IconLock, IconKey, IconMail, IconSend, IconShield, IconDownload, IconClock } from '@tabler/icons-solidjs';
import { TwoFactorAuth } from '@/components/TwoFactorAuth';
import { Button } from '@/components/ui/Button';
import { Card } from '@/components/ui/Card';
-import { AIProviderIcon } from '@/components/AIProviderIcon';
import { useHaptics } from '@/lib/haptics';
import { getApiV1BaseUrl, getApiOrigin } from '@/lib/api-url';
@@ -34,8 +33,7 @@ export const Settings = () => {
const [message, setMessage] = createSignal('');
const [profileData, setProfileData] = createSignal({
fullName: '',
- theme: 'dark',
- showBrowserSearch: true
+ theme: 'dark'
});
const [customColors, setCustomColors] = createSignal({
primary: '#5ab9ff',
@@ -112,15 +110,7 @@ export const Settings = () => {
newPassword: '',
confirmPassword: ''
});
- const [aiSettings, setAISettings] = createSignal({
- mistral: { enabled: false, api_key: '', model: 'mistral-small-latest', model_thinking: 'mistral-large-latest' },
- grok: { enabled: false, api_key: '', base_url: 'https://api.x.ai/v1', model: 'grok-4-1-fast-non-reasoning-latest', model_thinking: 'grok-4-1-fast-reasoning-latest' },
- deepseek: { enabled: false, api_key: '', base_url: 'https://api.deepseek.com', model: 'deepseek-chat', model_thinking: 'deepseek-reasoner' },
- ollama: { enabled: false, base_url: 'http://localhost:11434', model: 'llama3.1', model_thinking: 'llama3.1' },
- longcat: { enabled: false, api_key: '', base_url: 'https://api.longcat.chat', openai_endpoint: 'https://api.longcat.chat/openai', anthropic_endpoint: 'https://api.longcat.chat/anthropic', model: 'LongCat-Flash-Chat', model_thinking: 'LongCat-Flash-Thinking', model_thinking_upgraded: 'LongCat-Flash-Thinking-2601', format: 'openai' },
- openrouter: { enabled: false, api_key: '', base_url: 'https://openrouter.ai/api', model: 'openrouter/auto', model_thinking: 'openrouter/auto' }
- });
- const [availableAIProviders, setAvailableAIProviders] = createSignal([]);
+
const [emailSettings, setEmailSettings] = createSignal({
smtp_enabled: false,
smtp_host: '',
@@ -136,18 +126,7 @@ export const Settings = () => {
oauth_client_secret: '',
oauth_redirect_uri: ''
});
- const [searchSettings, setSearchSettings] = createSignal({
- brave_api_key: '',
- brave_search_base_url: 'https://api.search.brave.com/res/v1/web/search',
- serper_api_key: '',
- serper_base_url: 'https://google.serper.dev/search',
- search_api_provider: 'brave',
- search_results_limit: 10,
- search_cache_ttl: 300,
- search_rate_limit: 100
- });
const [emailSettingsExpanded, setEmailSettingsExpanded] = createSignal(true);
- const [aiLoading, setAiLoading] = createSignal(false);
const [activeTab, setActiveTab] = createSignal('account');
const [browserExtensionApiKeys, setBrowserExtensionApiKeys] = createSignal([]);
const [browserExtensions, setBrowserExtensions] = createSignal([]);
@@ -155,18 +134,21 @@ export const Settings = () => {
const tabs = [
{ id: 'account', name: 'Account', icon: IconUser },
{ id: 'security', name: 'Security', icon: IconShield },
- { id: 'ai', name: 'AI & Integration', icon: IconBrain },
{ id: 'communication', name: 'Communication', icon: IconMail },
- { id: 'search', name: 'Search API', icon: IconBrain },
- { id: 'tools', name: 'Tools', icon: IconDownload }
+ { id: 'tools', name: 'Tools', icon: IconDownload },
+ { id: 'solidtime', name: 'Solidtime', icon: IconClock }
];
+ const [solidtimeSettings, setSolidtimeSettings] = createSignal({
+ apiKey: localStorage.getItem('solidtime_api_key') || '',
+ orgId: localStorage.getItem('solidtime_org_id') || ''
+ });
+
onMount(() => {
if (authState.user) {
setProfileData({
fullName: authState.user.full_name,
- theme: authState.user.theme || 'dark',
- showBrowserSearch: localStorage.getItem('showBrowserSearch') !== 'false'
+ theme: authState.user.theme || 'dark'
});
}
@@ -188,77 +170,12 @@ export const Settings = () => {
}
}
- loadAISettings();
- loadAvailableAIProviders();
- loadSearchSettings();
loadBrowserExtensionAccess();
});
- const loadAISettings = async () => {
- try {
- const endpoint = `${getApiOrigin()}/api/v1/auth/ai/settings`;
-
- const response = await fetch(endpoint, {
- headers: {
- 'Authorization': `Bearer ${localStorage.getItem('token')}`,
- 'Content-Type': 'application/json'
- }
- });
-
- if (response.ok) {
- const data = await response.json();
- setAISettings(data);
- }
- } catch (error) {
- console.error('Failed to load AI settings:', error);
- }
- };
-
- const loadAvailableAIProviders = async () => {
- try {
- const endpoint = `${getApiOrigin()}/api/v1/ai/providers`;
-
- const response = await fetch(endpoint, {
- headers: {
- 'Authorization': `Bearer ${localStorage.getItem('token')}`,
- 'Content-Type': 'application/json'
- }
- });
-
- if (response.ok) {
- const data = await response.json();
- const providers = (data.providers || []) as { id: string }[];
- setAvailableAIProviders(providers.map((p) => p.id));
- }
- } catch (error) {
- console.error('Failed to load available AI providers:', error);
- setAvailableAIProviders(['mistral', 'grok', 'deepseek', 'ollama', 'longcat', 'openrouter']);
- }
- };
-
- const loadSearchSettings = async () => {
- try {
- const endpoint = `${getApiOrigin()}/api/v1/auth/search/settings`;
-
- const response = await fetch(endpoint, {
- headers: {
- 'Authorization': `Bearer ${localStorage.getItem('token')}`,
- 'Content-Type': 'application/json'
- }
- });
-
- if (response.ok) {
- const data = await response.json();
- setSearchSettings(data);
- }
- } catch (error) {
- console.error('Failed to load search settings:', error);
- }
- };
-
const loadBrowserExtensionAccess = async () => {
try {
const token = localStorage.getItem('trackeep_token') || localStorage.getItem('token');
@@ -287,35 +204,6 @@ export const Settings = () => {
}
};
- const handleUpdateAISettings = async () => {
- setAiLoading(true);
- setMessage('');
-
- try {
- const token = localStorage.getItem('token');
- const response = await fetch(`${getApiOrigin()}/api/v1/auth/ai/settings`, {
- method: 'PUT',
- headers: {
- 'Authorization': `Bearer ${token}`,
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify(aiSettings())
- });
-
- if (response.ok) {
- setMessage('AI settings updated successfully!');
- await loadAISettings(); // Reload to get masked keys
- } else {
- const error = await response.json();
- setMessage(error.error || 'Failed to update AI settings');
- }
- } catch (error) {
- setMessage('Failed to update AI settings');
- } finally {
- setAiLoading(false);
- }
- };
-
const handleUpdateProfile = async () => {
setIsLoading(true);
setMessage('');
@@ -326,9 +214,6 @@ export const Settings = () => {
theme: profileData().theme
});
- // Save browser search setting to localStorage
- localStorage.setItem('showBrowserSearch', profileData().showBrowserSearch.toString());
-
setMessage('Profile updated successfully!');
haptics.success();
} catch (error) {
@@ -365,35 +250,6 @@ export const Settings = () => {
}
};
- const handleUpdateSearchSettings = async () => {
- setIsLoading(true);
- setMessage('');
-
- try {
- const token = localStorage.getItem('token');
- const response = await fetch(`${getApiOrigin()}/api/v1/auth/search/settings`, {
- method: 'PUT',
- headers: {
- 'Authorization': `Bearer ${token}`,
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify(searchSettings())
- });
-
- if (response.ok) {
- setMessage('Search settings updated successfully!');
- await loadSearchSettings();
- } else {
- const error = await response.json();
- setMessage(error.error || 'Failed to update search settings');
- }
- } catch (error) {
- setMessage('Failed to update search settings');
- } finally {
- setIsLoading(false);
- }
- };
-
return (
@@ -633,792 +489,6 @@ export const Settings = () => {
- {/* AI & Integration Tab */}
-
-
- {/* AI Settings Section */}
-
-
-
- AI Settings
-
-
- {/* AI Settings Summary */}
-
-
-
- {(() => {
- const settings = aiSettings() || {};
- const providers = availableAIProviders();
- const enabledCount = Object.values(settings).filter((provider: any) => provider && provider.enabled).length;
- const totalAvailable = providers.length || Object.keys(settings).length;
- return `Active Providers: ${enabledCount} / ${totalAvailable}`;
- })()}
-
-
- {(() => {
- const settings = aiSettings() || {};
- const enabledCount = Object.values(settings).filter((provider: any) => provider && provider.enabled).length;
- const totalAvailable = availableAIProviders().length || Object.keys(settings).length;
-
- if (totalAvailable === 0) {
- return 'No AI providers are available on the server. Check backend AI configuration.';
- }
-
- if (enabledCount === 0) {
- return 'Providers are available but none are enabled. Enable at least one provider below.';
- }
-
- return `AI is ready. ${enabledCount} provider${enabledCount > 1 ? 's' : ''} enabled.`;
- })()}
-
-
-
-
- {/* Quick Setup Section */}
-
-
Quick Setup
-
- Configure the most commonly used AI providers quickly:
-
-
-
{
- const settings = aiSettings();
- setAISettings({
- ...settings,
- mistral: { ...settings.mistral, enabled: !settings.mistral.enabled }
- });
- haptics.selection();
- }}
- class="justify-start"
- >
-
- Mistral AI
-
-
{
- const settings = aiSettings();
- setAISettings({
- ...settings,
- longcat: { ...settings.longcat, enabled: !settings.longcat.enabled }
- });
- }}
- class="flex items-center gap-2"
- >
-
- LongCat AI
-
-
{
- const settings = aiSettings();
- setAISettings({
- ...settings,
- grok: { ...settings.grok, enabled: !settings.grok.enabled }
- });
- }}
- class="flex items-center gap-2"
- >
-
- Grok AI
-
-
{
- const settings = aiSettings();
- setAISettings({
- ...settings,
- deepseek: { ...settings.deepseek, enabled: !settings.deepseek.enabled }
- });
- }}
- class="flex items-center gap-2"
- >
-
- DeepSeek AI
-
-
{
- const settings = aiSettings();
- setAISettings({
- ...settings,
- ollama: { ...settings.ollama, enabled: !settings.ollama.enabled }
- });
- }}
- class="flex items-center gap-2"
- >
-
- Ollama (Local)
-
-
{
- const settings = aiSettings();
- setAISettings({
- ...settings,
- openrouter: { ...settings.openrouter, enabled: !settings.openrouter.enabled }
- });
- }}
- class="flex items-center gap-2"
- >
-
- OpenRouter
-
-
-
-
- {/* Detailed Configuration */}
-
-
Detailed Configuration
-
- {/* Mistral Settings */}
-
-
-
-
- Mistral AI
-
-
-
- {
- const settings = aiSettings();
- setAISettings({
- ...settings,
- mistral: { ...settings.mistral, enabled: e.currentTarget.checked }
- });
- }}
- class="rounded border-input"
- />
-
-
-
-
-
-
- {
- const settings = aiSettings();
- setAISettings({
- ...settings,
- mistral: { ...settings.mistral, api_key: e.currentTarget.value }
- });
- }}
- placeholder="Enter Mistral API key"
- required
- class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 pr-10 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring"
- />
-
-
-
-
-
-
-
-
-
-
-
- {/* LongCat Settings */}
-
-
-
-
- LongCat AI
-
-
-
- {
- const settings = aiSettings();
- setAISettings({
- ...settings,
- longcat: { ...settings.longcat, enabled: e.currentTarget.checked }
- });
- }}
- class="rounded border-input"
- />
-
-
-
-
-
-
- {
- const settings = aiSettings();
- setAISettings({
- ...settings,
- longcat: { ...settings.longcat, api_key: e.currentTarget.value }
- });
- }}
- placeholder="Enter LongCat API key"
- class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 pr-10 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring"
- />
-
-
-
-
-
-
-
-
- {
- const settings = aiSettings();
- setAISettings({
- ...settings,
- longcat: { ...settings.longcat, base_url: e.currentTarget.value }
- });
- }}
- placeholder="https://api.longcat.chat"
- class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring"
- />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {/* Grok Settings */}
-
-
-
-
- Grok AI
-
-
-
- {
- const settings = aiSettings();
- setAISettings({
- ...settings,
- grok: { ...settings.grok, enabled: e.currentTarget.checked }
- });
- }}
- class="rounded border-input"
- />
-
-
-
-
-
-
- {
- const settings = aiSettings();
- setAISettings({
- ...settings,
- grok: { ...settings.grok, api_key: e.currentTarget.value }
- });
- }}
- placeholder="Enter Grok API key"
- class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 pr-10 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring"
- />
-
-
-
-
-
-
-
-
- {
- const settings = aiSettings();
- setAISettings({
- ...settings,
- grok: { ...settings.grok, base_url: e.currentTarget.value }
- });
- }}
- placeholder="https://api.x.ai/v1"
- class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring"
- />
-
-
-
-
-
-
-
- {/* DeepSeek Settings */}
-
-
-
-
- DeepSeek AI
-
-
-
- {
- const settings = aiSettings();
- setAISettings({
- ...settings,
- deepseek: { ...settings.deepseek, enabled: e.currentTarget.checked }
- });
- }}
- class="rounded border-input"
- />
-
-
-
-
-
-
- {
- const settings = aiSettings();
- setAISettings({
- ...settings,
- deepseek: { ...settings.deepseek, api_key: e.currentTarget.value }
- });
- }}
- placeholder="Enter DeepSeek API key"
- class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 pr-10 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring"
- />
-
-
-
-
-
-
-
-
- {
- const settings = aiSettings();
- setAISettings({
- ...settings,
- deepseek: { ...settings.deepseek, base_url: e.currentTarget.value }
- });
- }}
- placeholder="https://api.deepseek.com"
- class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring"
- />
-
-
-
-
-
-
-
- {/* Ollama Settings */}
-
-
-
-
- Ollama (Local AI)
-
-
-
- {
- const settings = aiSettings();
- setAISettings({
- ...settings,
- ollama: { ...settings.ollama, enabled: e.currentTarget.checked }
- });
- }}
- class="rounded border-input"
- />
-
-
-
-
-
- {
- const settings = aiSettings();
- setAISettings({
- ...settings,
- ollama: { ...settings.ollama, base_url: e.currentTarget.value }
- });
- }}
- placeholder="http://localhost:11434"
- class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring"
- />
-
-
-
-
-
-
-
- {/* OpenRouter Settings */}
-
-
-
-
- OpenRouter
-
-
-
- {
- const settings = aiSettings();
- setAISettings({
- ...settings,
- openrouter: { ...settings.openrouter, enabled: e.currentTarget.checked }
- });
- }}
- class="rounded border-input"
- />
-
-
-
-
-
-
- {
- const settings = aiSettings();
- setAISettings({
- ...settings,
- openrouter: { ...settings.openrouter, api_key: e.currentTarget.value }
- });
- }}
- placeholder="Enter OpenRouter API key"
- class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 pr-10 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring"
- />
-
-
-
-
-
-
-
-
- {
- const settings = aiSettings();
- setAISettings({
- ...settings,
- openrouter: { ...settings.openrouter, base_url: e.currentTarget.value }
- });
- }}
- placeholder="https://openrouter.ai/api"
- class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring"
- />
-
-
-
-
-
-
-
-
-
-
- {aiLoading() ? 'Saving...' : 'Save AI Model Settings'}
-
- {
- // Test AI configuration
- const enabledProviders = Object.entries(aiSettings()).filter(([_, config]) => config.enabled);
- if (enabledProviders.length > 0) {
- alert(`AI configuration test successful! (${enabledProviders.length} providers enabled)`);
- } else {
- alert('No AI providers enabled. Please enable at least one provider.');
- }
- }}
- class="inline-flex justify-center rounded-md text-sm font-medium transition-shadow focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 bg-secondary text-secondary-foreground shadow hover:bg-secondary/90 h-auto items-center gap-2 py-2 px-4"
- >
- Test AI Configuration
-
-
-
-
-
-
{/* Communication Tab */}
@@ -1612,130 +682,6 @@ export const Settings = () => {
- {/* Search API Tab */}
-
-
-
-
-
- Browser Search API Configuration
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {isLoading() ? 'Saving...' : 'Save Search Settings'}
-
-
-
-
-
-
{/* Tools Tab */}
@@ -1854,6 +800,101 @@ export const Settings = () => {
+
+
+
+
+
+
+
+
+ Solidtime Integration
+
+
+ Configure your solidtime.io API credentials to enable time tracking integration.
+
+
+
+
+
+
setSolidtimeSettings(prev => ({ ...prev, apiKey: e.currentTarget.value }))}
+ placeholder="Enter your solidtime API key"
+ class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring"
+ />
+
+ Get your API key from solidtime.io Profile Settings
+
+
+
+
+
+
setSolidtimeSettings(prev => ({ ...prev, orgId: e.currentTarget.value }))}
+ placeholder="Enter your organization ID"
+ class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-1.5 focus-visible:ring-ring"
+ />
+
+ Find your organization ID in your solidtime.io dashboard URL
+
+
+
+
+ {
+ localStorage.setItem('solidtime_api_key', solidtimeSettings().apiKey);
+ localStorage.setItem('solidtime_org_id', solidtimeSettings().orgId);
+ setMessage('Solidtime credentials saved successfully!');
+ haptics.success();
+ }}
+ class="flex items-center gap-2"
+ >
+
+ Save Credentials
+
+ {
+ localStorage.removeItem('solidtime_api_key');
+ localStorage.removeItem('solidtime_org_id');
+ setSolidtimeSettings({ apiKey: '', orgId: '' });
+ setMessage('Solidtime credentials cleared');
+ haptics.selection();
+ }}
+ variant="outline"
+ >
+ Clear
+
+
+
+
+
+
How to get your credentials
+
+
+ 1.
+ Log in to your solidtime.io account
+
+
+ 2.
+ Go to Profile Settings → API Tokens
+
+
+ 3.
+ Create a new API token and copy it
+
+
+ 4.
+ Your organization ID is in the dashboard URL: /organizations/{'{id}'}/...
+
+
+
+
+
+
);