feat: v0.1.0 - geolocation capture, calendar, search, Starlight docs site
- Automatic browser geolocation capture on event creation - Reverse geocoding via Nominatim API for place names - Full-text search with SQLite FTS5 - Calendar view for browsing past entries - DateNavigator component for day navigation - SearchModal with Ctrl+K shortcut - QuickAddWidget with Ctrl+J shortcut - Starlight documentation site with GitHub Pages deployment - Multiple AI provider support (Groq, OpenAI, Anthropic, Ollama, LM Studio) - Multi-user registration support BREAKING: Events now include latitude/longitude/placeName fields
This commit is contained in:
@@ -12,8 +12,8 @@ export class AnthropicProvider implements AIProvider {
|
||||
this.baseUrl = config.baseUrl || 'https://api.anthropic.com/v1';
|
||||
}
|
||||
|
||||
async generate(prompt: string, systemPrompt?: string): Promise<AIProviderResult> {
|
||||
const requestBody = {
|
||||
async generate(prompt: string, systemPrompt?: string, options?: { jsonMode?: boolean }): Promise<AIProviderResult> {
|
||||
const requestBody: Record<string, unknown> = {
|
||||
model: this.model,
|
||||
max_tokens: 2000,
|
||||
system: systemPrompt,
|
||||
@@ -22,6 +22,10 @@ export class AnthropicProvider implements AIProvider {
|
||||
],
|
||||
};
|
||||
|
||||
if (options?.jsonMode) {
|
||||
requestBody.output = { format: { type: 'json_object' } };
|
||||
}
|
||||
|
||||
const response = await fetch(`${this.baseUrl}/messages`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -39,10 +43,22 @@ export class AnthropicProvider implements AIProvider {
|
||||
throw new Error(`Anthropic API error: ${response.status} ${JSON.stringify(responseData)}`);
|
||||
}
|
||||
|
||||
const content = responseData.content?.[0]?.text || '';
|
||||
let content = responseData.content?.[0]?.text || '';
|
||||
let title: string | undefined;
|
||||
|
||||
if (options?.jsonMode) {
|
||||
try {
|
||||
const parsed = JSON.parse(content);
|
||||
title = parsed.title;
|
||||
content = parsed.content || content;
|
||||
} catch {
|
||||
// If JSON parsing fails, use content as-is
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
content,
|
||||
title,
|
||||
request: requestBody,
|
||||
response: responseData,
|
||||
};
|
||||
|
||||
@@ -12,7 +12,7 @@ export class GroqProvider implements AIProvider {
|
||||
this.baseUrl = config.baseUrl || 'https://api.groq.com/openai/v1';
|
||||
}
|
||||
|
||||
async generate(prompt: string, systemPrompt?: string): Promise<AIProviderResult> {
|
||||
async generate(prompt: string, systemPrompt?: string, options?: { jsonMode?: boolean }): Promise<AIProviderResult> {
|
||||
const messages: Array<{ role: string; content: string }> = [];
|
||||
|
||||
if (systemPrompt) {
|
||||
@@ -21,13 +21,17 @@ export class GroqProvider implements AIProvider {
|
||||
|
||||
messages.push({ role: 'user', content: prompt });
|
||||
|
||||
const requestBody = {
|
||||
const requestBody: Record<string, unknown> = {
|
||||
model: this.model,
|
||||
messages,
|
||||
temperature: 0.7,
|
||||
max_tokens: 2000,
|
||||
};
|
||||
|
||||
if (options?.jsonMode) {
|
||||
requestBody.response_format = { type: 'json_object' };
|
||||
}
|
||||
|
||||
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -44,10 +48,22 @@ export class GroqProvider implements AIProvider {
|
||||
}
|
||||
|
||||
const responseData = JSON.parse(responseText);
|
||||
const content = responseData.choices?.[0]?.message?.content || '';
|
||||
let content = responseData.choices?.[0]?.message?.content || '';
|
||||
let title: string | undefined;
|
||||
|
||||
if (options?.jsonMode) {
|
||||
try {
|
||||
const parsed = JSON.parse(content);
|
||||
title = parsed.title;
|
||||
content = parsed.content || content;
|
||||
} catch {
|
||||
// If JSON parsing fails, use content as-is
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
content,
|
||||
title,
|
||||
request: requestBody,
|
||||
response: responseData,
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@ export class LMStudioProvider implements AIProvider {
|
||||
this.model = config.model || 'local-model';
|
||||
}
|
||||
|
||||
async generate(prompt: string, systemPrompt?: string): Promise<AIProviderResult> {
|
||||
async generate(prompt: string, systemPrompt?: string, options?: { jsonMode?: boolean }): Promise<AIProviderResult> {
|
||||
const messages: Array<{ role: string; content: string }> = [];
|
||||
|
||||
if (systemPrompt) {
|
||||
@@ -40,10 +40,22 @@ export class LMStudioProvider implements AIProvider {
|
||||
throw new Error(`LM Studio API error: ${response.status} ${JSON.stringify(responseData)}`);
|
||||
}
|
||||
|
||||
const content = responseData.choices?.[0]?.message?.content || '';
|
||||
let content = responseData.choices?.[0]?.message?.content || '';
|
||||
let title: string | undefined;
|
||||
|
||||
if (options?.jsonMode) {
|
||||
try {
|
||||
const parsed = JSON.parse(content);
|
||||
title = parsed.title;
|
||||
content = parsed.content || content;
|
||||
} catch {
|
||||
// If JSON parsing fails, use content as-is
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
content,
|
||||
title,
|
||||
request: requestBody,
|
||||
response: responseData,
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@ export class OllamaProvider implements AIProvider {
|
||||
this.model = config.model || 'llama3.2';
|
||||
}
|
||||
|
||||
async generate(prompt: string, systemPrompt?: string): Promise<AIProviderResult> {
|
||||
async generate(prompt: string, systemPrompt?: string, options?: { jsonMode?: boolean }): Promise<AIProviderResult> {
|
||||
const requestBody = {
|
||||
model: this.model,
|
||||
stream: false,
|
||||
@@ -34,10 +34,22 @@ export class OllamaProvider implements AIProvider {
|
||||
throw new Error(`Ollama API error: ${response.status} ${JSON.stringify(responseData)}`);
|
||||
}
|
||||
|
||||
const content = responseData.message?.content || '';
|
||||
let content = responseData.message?.content || '';
|
||||
let title: string | undefined;
|
||||
|
||||
if (options?.jsonMode) {
|
||||
try {
|
||||
const parsed = JSON.parse(content);
|
||||
title = parsed.title;
|
||||
content = parsed.content || content;
|
||||
} catch {
|
||||
// If JSON parsing fails, use content as-is
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
content,
|
||||
title,
|
||||
request: requestBody,
|
||||
response: responseData,
|
||||
};
|
||||
|
||||
@@ -12,7 +12,7 @@ export class OpenAIProvider implements AIProvider {
|
||||
this.baseUrl = config.baseUrl || 'https://api.openai.com/v1';
|
||||
}
|
||||
|
||||
async generate(prompt: string, systemPrompt?: string): Promise<AIProviderResult> {
|
||||
async generate(prompt: string, systemPrompt?: string, options?: { jsonMode?: boolean }): Promise<AIProviderResult> {
|
||||
const messages: Array<{ role: string; content: string }> = [];
|
||||
|
||||
if (systemPrompt) {
|
||||
@@ -21,13 +21,17 @@ export class OpenAIProvider implements AIProvider {
|
||||
|
||||
messages.push({ role: 'user', content: prompt });
|
||||
|
||||
const requestBody = {
|
||||
const requestBody: Record<string, unknown> = {
|
||||
model: this.model,
|
||||
messages,
|
||||
temperature: 0.7,
|
||||
max_tokens: 2000,
|
||||
};
|
||||
|
||||
if (options?.jsonMode) {
|
||||
requestBody.response_format = { type: 'json_object' };
|
||||
}
|
||||
|
||||
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -43,10 +47,22 @@ export class OpenAIProvider implements AIProvider {
|
||||
throw new Error(`OpenAI API error: ${response.status} ${JSON.stringify(responseData)}`);
|
||||
}
|
||||
|
||||
const content = responseData.choices?.[0]?.message?.content || '';
|
||||
let content = responseData.choices?.[0]?.message?.content || '';
|
||||
let title: string | undefined;
|
||||
|
||||
if (options?.jsonMode) {
|
||||
try {
|
||||
const parsed = JSON.parse(content);
|
||||
title = parsed.title;
|
||||
content = parsed.content || content;
|
||||
} catch {
|
||||
// If JSON parsing fails, use content as-is
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
content,
|
||||
title,
|
||||
request: requestBody,
|
||||
response: responseData,
|
||||
};
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
export interface AIProviderResult {
|
||||
content: string;
|
||||
title?: string;
|
||||
request: Record<string, unknown>;
|
||||
response: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface AIProvider {
|
||||
provider: 'openai' | 'anthropic' | 'ollama' | 'lmstudio' | 'groq';
|
||||
generate(prompt: string, systemPrompt?: string): Promise<AIProviderResult>;
|
||||
generate(prompt: string, systemPrompt?: string, options?: { jsonMode?: boolean }): Promise<AIProviderResult>;
|
||||
validate?(): Promise<boolean>;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user