Files
claude-mem/src/ui/viewer/hooks/useSettings.ts
T
bigphoot ec8dd08c32 feat: add Gemini API as alternative AI provider
Adds support for Google's Gemini API as an alternative to Claude Agent SDK
for observation extraction. Users can now choose between providers in the
settings UI.

Features:
- New GeminiAgent class using Gemini REST API
- Provider selection in Settings (Claude vs Gemini)
- Gemini API key configuration (via UI or GEMINI_API_KEY env var)
- Model selection: gemini-2.0-flash-exp, gemini-1.5-flash, gemini-1.5-pro
- Graceful fallback to Claude SDK if Gemini selected but no API key
- Seamless transition between providers without worker restart

Settings:
- CLAUDE_MEM_PROVIDER: 'claude' | 'gemini'
- CLAUDE_MEM_GEMINI_API_KEY: API key for Gemini
- CLAUDE_MEM_GEMINI_MODEL: Model selection

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-24 11:02:56 -08:00

82 lines
4.0 KiB
TypeScript

import { useState, useEffect } from 'react';
import { Settings } from '../types';
import { DEFAULT_SETTINGS } from '../constants/settings';
import { API_ENDPOINTS } from '../constants/api';
import { TIMING } from '../constants/timing';
export function useSettings() {
const [settings, setSettings] = useState<Settings>(DEFAULT_SETTINGS);
const [isSaving, setIsSaving] = useState(false);
const [saveStatus, setSaveStatus] = useState('');
useEffect(() => {
// Load initial settings
fetch(API_ENDPOINTS.SETTINGS)
.then(res => res.json())
.then(data => {
setSettings({
CLAUDE_MEM_MODEL: data.CLAUDE_MEM_MODEL || DEFAULT_SETTINGS.CLAUDE_MEM_MODEL,
CLAUDE_MEM_CONTEXT_OBSERVATIONS: data.CLAUDE_MEM_CONTEXT_OBSERVATIONS || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_OBSERVATIONS,
CLAUDE_MEM_WORKER_PORT: data.CLAUDE_MEM_WORKER_PORT || DEFAULT_SETTINGS.CLAUDE_MEM_WORKER_PORT,
CLAUDE_MEM_WORKER_HOST: data.CLAUDE_MEM_WORKER_HOST || DEFAULT_SETTINGS.CLAUDE_MEM_WORKER_HOST,
// AI Provider Configuration
CLAUDE_MEM_PROVIDER: data.CLAUDE_MEM_PROVIDER || DEFAULT_SETTINGS.CLAUDE_MEM_PROVIDER,
CLAUDE_MEM_GEMINI_API_KEY: data.CLAUDE_MEM_GEMINI_API_KEY || DEFAULT_SETTINGS.CLAUDE_MEM_GEMINI_API_KEY,
CLAUDE_MEM_GEMINI_MODEL: data.CLAUDE_MEM_GEMINI_MODEL || DEFAULT_SETTINGS.CLAUDE_MEM_GEMINI_MODEL,
// Token Economics Display
CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS: data.CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS,
CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS: data.CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS,
CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT: data.CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT,
CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT: data.CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT,
// Observation Filtering
CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES: data.CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES,
CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS: data.CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS,
// Display Configuration
CLAUDE_MEM_CONTEXT_FULL_COUNT: data.CLAUDE_MEM_CONTEXT_FULL_COUNT || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_FULL_COUNT,
CLAUDE_MEM_CONTEXT_FULL_FIELD: data.CLAUDE_MEM_CONTEXT_FULL_FIELD || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_FULL_FIELD,
CLAUDE_MEM_CONTEXT_SESSION_COUNT: data.CLAUDE_MEM_CONTEXT_SESSION_COUNT || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_SESSION_COUNT,
// Feature Toggles
CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY: data.CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY,
CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE: data.CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE || DEFAULT_SETTINGS.CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE,
});
})
.catch(error => {
console.error('Failed to load settings:', error);
});
}, []);
const saveSettings = async (newSettings: Settings) => {
setIsSaving(true);
setSaveStatus('Saving...');
try {
const response = await fetch(API_ENDPOINTS.SETTINGS, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newSettings)
});
const result = await response.json();
if (result.success) {
setSettings(newSettings);
setSaveStatus('✓ Saved');
setTimeout(() => setSaveStatus(''), TIMING.SAVE_STATUS_DISPLAY_DURATION_MS);
} else {
setSaveStatus(`✗ Error: ${result.error}`);
}
} catch (error) {
setSaveStatus(`✗ Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
} finally {
setIsSaving(false);
}
};
return { settings, saveSettings, isSaving, saveStatus };
}