v0.1.1: System default AI config, configurable URLs, single .env, website refactor
This commit is contained in:
@@ -81,7 +81,7 @@ function Footer({ appName = 'DearDiary' }: { appName?: string }) {
|
||||
const { resolvedTheme } = useTheme();
|
||||
return (
|
||||
<footer className={`py-6 text-center text-sm text-slate-500 ${resolvedTheme === 'dark' ? 'border-t border-slate-800' : 'border-t border-slate-200'}`}>
|
||||
<p>{appName} v{packageJson.version} — Self-hosted AI-powered journaling · <a href="https://github.com/lotherk/deardiary" className="hover:text-purple-400 transition">GitHub</a> · <a href="https://deardiary.io" className="hover:text-purple-400 transition">deardiary.io</a> · MIT License · © 2026 Konrad Lother</p>
|
||||
<p>{appName} v{packageJson.version} — Self-hosted AI-powered journaling · <a href={import.meta.env.VITE_GIT_URL || 'https://git.kropa.tech/lotherk/deardiary'} className="hover:text-purple-400 transition">Git Repository</a> · MIT License · © 2026 Konrad Lother</p>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,9 +19,30 @@ export default function QuickAddWidget({ isOpen, onClose }: Props) {
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
checkDiaryStatus();
|
||||
setTimeout(() => inputRef.current?.focus(), 50);
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
if (content.trim() && !locked && !loading) {
|
||||
const form = inputRef.current?.form;
|
||||
if (form) {
|
||||
const submitEvent = new Event('submit', { bubbles: true, cancelable: true });
|
||||
form.dispatchEvent(submitEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (isOpen) {
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
}
|
||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||
}, [isOpen, content, locked, loading]);
|
||||
|
||||
const checkDiaryStatus = async () => {
|
||||
const res = await api.getDay(today);
|
||||
if (res.data) {
|
||||
|
||||
@@ -221,7 +221,7 @@ export interface Task {
|
||||
}
|
||||
|
||||
export interface Settings {
|
||||
aiProvider: 'openai' | 'anthropic' | 'ollama' | 'lmstudio' | 'groq';
|
||||
aiProvider: 'openai' | 'anthropic' | 'ollama' | 'lmstudio' | 'groq' | 'xai' | 'custom';
|
||||
aiApiKey?: string;
|
||||
aiModel: string;
|
||||
aiBaseUrl?: string;
|
||||
@@ -229,10 +229,13 @@ export interface Settings {
|
||||
language: string;
|
||||
timezone: string;
|
||||
journalContextDays: number;
|
||||
useSystemDefault: boolean;
|
||||
providerSettings?: Record<string, {
|
||||
apiKey?: string;
|
||||
model?: string;
|
||||
baseUrl?: string;
|
||||
headers?: Record<string, string>;
|
||||
parameters?: Record<string, unknown>;
|
||||
}>;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ interface ProviderSettings {
|
||||
apiKey?: string;
|
||||
model?: string;
|
||||
baseUrl?: string;
|
||||
headers?: Record<string, string>;
|
||||
parameters?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
interface FullSettings extends Settings {
|
||||
@@ -15,15 +17,21 @@ interface FullSettings extends Settings {
|
||||
|
||||
const DEFAULT_MODELS: Record<string, string> = {
|
||||
groq: 'llama-3.3-70b-versatile',
|
||||
openai: 'gpt-4o',
|
||||
openai: 'gpt-5-nano',
|
||||
anthropic: 'claude-3-5-sonnet-20241022',
|
||||
ollama: 'llama3.2',
|
||||
lmstudio: 'local-model',
|
||||
custom: '',
|
||||
xai: 'grok-4-1-fast-reasoning',
|
||||
};
|
||||
|
||||
const PROVIDERS_WITH_API_KEY: string[] = ['openai', 'anthropic', 'groq', 'xai', 'custom'];
|
||||
const PROVIDERS_WITH_BASE_URL: string[] = ['ollama', 'lmstudio', 'custom'];
|
||||
|
||||
const DEFAULT_BASE_URLS: Record<string, string> = {
|
||||
ollama: 'http://localhost:11434',
|
||||
lmstudio: 'http://localhost:1234/v1',
|
||||
custom: 'https://your-api.example.com/v1',
|
||||
};
|
||||
|
||||
export default function SettingsPage() {
|
||||
@@ -34,6 +42,7 @@ export default function SettingsPage() {
|
||||
language: 'en',
|
||||
timezone: 'UTC',
|
||||
journalContextDays: 10,
|
||||
useSystemDefault: true,
|
||||
});
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
@@ -80,6 +89,9 @@ export default function SettingsPage() {
|
||||
if (!data.providerSettings) {
|
||||
data.providerSettings = {};
|
||||
}
|
||||
if (data.useSystemDefault === undefined) {
|
||||
data.useSystemDefault = true;
|
||||
}
|
||||
const provider = data.aiProvider || 'groq';
|
||||
const providerData = data.providerSettings[provider] || {};
|
||||
data.aiApiKey = providerData.apiKey;
|
||||
@@ -158,6 +170,7 @@ export default function SettingsPage() {
|
||||
await api.updateSettings({
|
||||
aiProvider: settings.aiProvider,
|
||||
providerSettings: newProviderSettings,
|
||||
useSystemDefault: settings.useSystemDefault,
|
||||
});
|
||||
setSaving(false);
|
||||
setSaved(true);
|
||||
@@ -448,13 +461,33 @@ export default function SettingsPage() {
|
||||
>
|
||||
<option value="groq">Groq (Free, Fast)</option>
|
||||
<option value="openai">OpenAI (GPT-4)</option>
|
||||
<option value="xai">xAI (Grok)</option>
|
||||
<option value="anthropic">Anthropic (Claude)</option>
|
||||
<option value="ollama">Ollama (Local)</option>
|
||||
<option value="lmstudio">LM Studio (Local)</option>
|
||||
<option value="custom">Custom (OpenAI-compatible)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{(provider === 'openai' || provider === 'anthropic' || provider === 'groq') && (
|
||||
<div className="flex items-center gap-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="useSystemDefault"
|
||||
checked={settings.useSystemDefault}
|
||||
onChange={(e) => setSettings({ ...settings, useSystemDefault: e.target.checked })}
|
||||
className="w-4 h-4 rounded border-slate-600 bg-slate-800 text-purple-600 focus:ring-purple-500"
|
||||
/>
|
||||
<label htmlFor="useSystemDefault" className="text-sm text-slate-300">
|
||||
Use system default settings
|
||||
</label>
|
||||
</div>
|
||||
<p className="text-xs text-slate-500 -mt-2">
|
||||
{settings.useSystemDefault
|
||||
? 'Using the default AI configuration set by the administrator.'
|
||||
: 'Override with your own AI settings.'}
|
||||
</p>
|
||||
|
||||
{!settings.useSystemDefault && PROVIDERS_WITH_API_KEY.includes(provider) && (
|
||||
<div>
|
||||
<label className="block text-sm text-slate-400 mb-1">API Key</label>
|
||||
<input
|
||||
@@ -467,7 +500,7 @@ export default function SettingsPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(provider === 'ollama' || provider === 'lmstudio') && (
|
||||
{!settings.useSystemDefault && PROVIDERS_WITH_BASE_URL.includes(provider) && (
|
||||
<div>
|
||||
<label className="block text-sm text-slate-400 mb-1">Base URL</label>
|
||||
<input
|
||||
@@ -480,16 +513,58 @@ export default function SettingsPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<label className="block text-sm text-slate-400 mb-1">Model</label>
|
||||
<input
|
||||
type="text"
|
||||
value={currentProviderSettings.model || ''}
|
||||
onChange={(e) => updateProviderSettings({ model: e.target.value })}
|
||||
placeholder={DEFAULT_MODELS[provider]}
|
||||
className="w-full px-4 py-2 bg-slate-800 rounded-lg border border-slate-700 focus:border-blue-500 focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
{!settings.useSystemDefault && (
|
||||
<div>
|
||||
<label className="block text-sm text-slate-400 mb-1">Model</label>
|
||||
<input
|
||||
type="text"
|
||||
value={currentProviderSettings.model || ''}
|
||||
onChange={(e) => updateProviderSettings({ model: e.target.value })}
|
||||
placeholder={DEFAULT_MODELS[provider]}
|
||||
className="w-full px-4 py-2 bg-slate-800 rounded-lg border border-slate-700 focus:border-blue-500 focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!settings.useSystemDefault && (
|
||||
<details className="group">
|
||||
<summary className="cursor-pointer text-sm text-slate-400 hover:text-slate-300 list-none flex items-center gap-2">
|
||||
<span className="transform transition-transform group-open:rotate-90">▶</span>
|
||||
Advanced Settings
|
||||
</summary>
|
||||
<div className="mt-4 space-y-4 pl-4 border-l-2 border-slate-700">
|
||||
<div>
|
||||
<label className="block text-sm text-slate-400 mb-1">Custom Headers (JSON)</label>
|
||||
<textarea
|
||||
value={JSON.stringify(currentProviderSettings.headers || {}, null, 2)}
|
||||
onChange={(e) => {
|
||||
try {
|
||||
updateProviderSettings({ headers: JSON.parse(e.target.value || '{}') });
|
||||
} catch {}
|
||||
}}
|
||||
rows={3}
|
||||
placeholder={'{"Authorization": "Bearer ..."}'}
|
||||
className="w-full px-4 py-2 bg-slate-800 rounded-lg border border-slate-700 focus:border-blue-500 focus:outline-none resize-none font-mono text-sm"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm text-slate-400 mb-1">Custom Parameters (JSON)</label>
|
||||
<textarea
|
||||
value={JSON.stringify(currentProviderSettings.parameters || {}, null, 2)}
|
||||
onChange={(e) => {
|
||||
try {
|
||||
updateProviderSettings({ parameters: JSON.parse(e.target.value || '{}') });
|
||||
} catch {}
|
||||
}}
|
||||
rows={4}
|
||||
placeholder={'{"think": false}'}
|
||||
className="w-full px-4 py-2 bg-slate-800 rounded-lg border border-slate-700 focus:border-blue-500 focus:outline-none resize-none font-mono text-sm"
|
||||
/>
|
||||
<p className="text-xs text-slate-500 mt-1">Examples: {"{ \"think\": false }"} for Ollama, {"{ \"thinking\": { \"type\": \"disabled\" } }"} for OpenAI</p>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
)}
|
||||
|
||||
<div className="flex gap-3 items-center">
|
||||
<button
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import { defineConfig, loadEnv } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: {
|
||||
port: 5173,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: process.env.VITE_API_URL || 'http://localhost:3000',
|
||||
changeOrigin: true,
|
||||
export default defineConfig(({ mode }) => {
|
||||
const env = loadEnv(mode, process.cwd(), '');
|
||||
|
||||
return {
|
||||
plugins: [react()],
|
||||
server: {
|
||||
port: 5173,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: process.env.VITE_API_URL || 'http://localhost:3000',
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
define: {
|
||||
'import.meta.env.VITE_GIT_URL': JSON.stringify(env.VITE_GIT_URL || 'https://git.kropa.tech/lotherk/deardiary'),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user