Centralize AI configuration into generic AE_AITools component
- Enhanced AE_AITools with a Settings tab for model, prompt, and parameter configuration. - Connected AE_AITools to Journals state via two-way bindings for persistent configuration. - Removed redundant AI settings from Journals config modal. - Standardized Svelte 5 patterns for cross-module component configuration.
This commit is contained in:
@@ -2,13 +2,14 @@
|
||||
/**
|
||||
* AE_AITools.svelte
|
||||
* GENERIC Aether AI Toolset (Runes/Svelte 5)
|
||||
* Reusable across all modules (Journals, Events, People, etc.)
|
||||
* Extracted logic from Journals module to be system-wide.
|
||||
*/
|
||||
import OpenAI from 'openai';
|
||||
import { Modal } from 'flowbite-svelte';
|
||||
import {
|
||||
Bot, BotMessageSquare, Loader, FileText,
|
||||
Save, FilePenLine, RotateCcw
|
||||
Save, FilePenLine, RotateCcw, Settings,
|
||||
RefreshCcw, Globe, Copy
|
||||
} from '@lucide/svelte';
|
||||
import { ae_loc, ae_api } from '$lib/stores/ae_stores';
|
||||
import E_app_codemirror_v5 from '$lib/app_components/e_app_codemirror_v5.svelte';
|
||||
@@ -18,61 +19,68 @@
|
||||
content: string; // The text to summarize/analyze
|
||||
summary: string; // The result (bindable)
|
||||
|
||||
// Configuration
|
||||
// Configuration (Bindable for global settings persistence)
|
||||
model?: string;
|
||||
baseUrl?: string;
|
||||
token?: string;
|
||||
systemPrompt?: string;
|
||||
maxTokens?: number;
|
||||
temperature?: number;
|
||||
|
||||
// Callbacks
|
||||
onSave?: (newSummary: string) => void;
|
||||
onSyncConfig?: () => void; // Optional: callback to sync from global site config
|
||||
|
||||
// UI Customization
|
||||
buttonClass?: string;
|
||||
showSavedButton?: boolean;
|
||||
log_lvl?: number;
|
||||
}
|
||||
|
||||
let {
|
||||
content,
|
||||
summary = $bindable(),
|
||||
model = '',
|
||||
baseUrl = '',
|
||||
token = '',
|
||||
systemPrompt = 'You are a helpful assistant that summarizes technical content.',
|
||||
model = $bindable('dgrzone-deepseek-8b-quick'),
|
||||
baseUrl = $bindable('https://ai.dgrzone.com/api'),
|
||||
token = $bindable(''),
|
||||
systemPrompt = $bindable('You are a helpful assistant.'),
|
||||
maxTokens = $bindable(512),
|
||||
temperature = $bindable(0.7),
|
||||
onSave,
|
||||
onSyncConfig,
|
||||
buttonClass = "btn btn-sm preset-tonal-primary shadow-lg hover:scale-105 transition-all",
|
||||
showSavedButton = true,
|
||||
log_lvl = 0
|
||||
}: Props = $props();
|
||||
|
||||
// Internal State
|
||||
let ae_promises: any = $state(null);
|
||||
let show_modal = $state(false);
|
||||
let active_tab: 'result' | 'settings' = $state('result');
|
||||
let tmp_summary = $state('');
|
||||
|
||||
async function generate_ai_result() {
|
||||
if (!content) {
|
||||
alert('No content available to summarize.');
|
||||
alert('No content available to analyze.');
|
||||
return;
|
||||
}
|
||||
|
||||
const ai_client = new OpenAI({
|
||||
apiKey: token || 'no-token-provided',
|
||||
baseURL: baseUrl || 'https://ai.dgrzone.com/api',
|
||||
baseURL: baseUrl,
|
||||
dangerouslyAllowBrowser: true
|
||||
});
|
||||
|
||||
try {
|
||||
active_tab = 'result';
|
||||
ae_promises = ai_client.chat.completions.create({
|
||||
model: model || 'dgrzone-deepseek-8b-quick',
|
||||
model: model,
|
||||
max_tokens: maxTokens,
|
||||
temperature: temperature,
|
||||
messages: [
|
||||
{ role: 'system', content: systemPrompt },
|
||||
{ role: 'user', content: content }
|
||||
]
|
||||
}).then((resp) => {
|
||||
const result = resp?.choices?.[0]?.message?.content || 'No result generated.';
|
||||
if (log_lvl) console.log('AE_AITools: Result generated', result);
|
||||
tmp_summary = result;
|
||||
show_modal = true;
|
||||
});
|
||||
@@ -89,7 +97,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="ae-ai-tools-wrapper">
|
||||
<div class="ae-ai-tools-wrapper inline-block">
|
||||
<!-- Trigger Button -->
|
||||
<button
|
||||
type="button"
|
||||
@@ -108,45 +116,104 @@
|
||||
{/await}
|
||||
</button>
|
||||
|
||||
<!-- Modal for Result Review -->
|
||||
<!-- Unified AI Modal -->
|
||||
{#if show_modal}
|
||||
<Modal
|
||||
title="AI Generated Summary"
|
||||
title="Aether AI Assistant"
|
||||
bind:open={show_modal}
|
||||
size="lg"
|
||||
class="bg-white dark:bg-gray-800"
|
||||
>
|
||||
<div class="space-y-4 p-2">
|
||||
<div class="flex gap-2 justify-between items-center border-b border-surface-500/20 pb-2">
|
||||
<div class="flex gap-2">
|
||||
<button class="btn btn-sm variant-filled-success" onclick={handle_save}>
|
||||
<Save size="1.1em" class="mr-1" /> Save Result
|
||||
</button>
|
||||
<button class="btn btn-sm variant-ghost-primary" onclick={generate_ai_result}>
|
||||
<RotateCcw size="1.1em" class="mr-1" /> Re-run
|
||||
</button>
|
||||
</div>
|
||||
<span class="text-xs opacity-50 uppercase font-bold tracking-tighter">
|
||||
{model || 'Default Model'}
|
||||
</span>
|
||||
<!-- Tab Navigation -->
|
||||
<div class="flex gap-1 border-b border-surface-500/20 pb-2">
|
||||
<button
|
||||
class="btn btn-sm {active_tab === 'result' ? 'variant-filled-primary' : 'variant-soft-surface'}"
|
||||
onclick={() => active_tab = 'result'}
|
||||
>
|
||||
<Bot size="1.1em" class="mr-1" /> Result
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-sm {active_tab === 'settings' ? 'variant-filled-secondary' : 'variant-soft-surface'}"
|
||||
onclick={() => active_tab = 'settings'}
|
||||
>
|
||||
<Settings size="1.1em" class="mr-1" /> Settings
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<E_app_codemirror_v5
|
||||
editable={true}
|
||||
content={tmp_summary}
|
||||
bind:new_content={tmp_summary}
|
||||
bind:theme_mode={$ae_loc.theme_mode}
|
||||
placeholder="AI Result will appear here..."
|
||||
class="p-2 border rounded-lg h-96 shadow-inner bg-surface-500/5"
|
||||
/>
|
||||
{#if active_tab === 'result'}
|
||||
<div class="space-y-4 animate-in fade-in duration-200">
|
||||
<div class="flex gap-2 justify-start">
|
||||
<button class="btn btn-sm variant-filled-success" onclick={handle_save}>
|
||||
<Save size="1.1em" class="mr-1" /> Save Result
|
||||
</button>
|
||||
<button class="btn btn-sm variant-ghost-primary" onclick={generate_ai_result}>
|
||||
<RotateCcw size="1.1em" class="mr-1" /> Re-run
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<E_app_codemirror_v5
|
||||
editable={true}
|
||||
content={tmp_summary}
|
||||
bind:new_content={tmp_summary}
|
||||
bind:theme_mode={$ae_loc.theme_mode}
|
||||
placeholder="AI Result will appear here..."
|
||||
class="p-2 border rounded-lg h-96 shadow-inner bg-surface-500/5"
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="space-y-6 animate-in slide-in-from-left-4 duration-200">
|
||||
<!-- Connection Settings -->
|
||||
<div class="space-y-4">
|
||||
<h3 class="text-sm font-bold uppercase tracking-widest text-surface-500 flex items-center gap-2">
|
||||
<Globe size="1.1em" /> API Connection
|
||||
</h3>
|
||||
|
||||
{#if onSyncConfig}
|
||||
<button class="btn btn-sm variant-soft-primary" onclick={onSyncConfig}>
|
||||
<Copy size="1.1em" class="mr-1" /> Sync Global Defaults
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<label class="label">
|
||||
<span>Base URL</span>
|
||||
<input type="text" bind:value={baseUrl} class="input input-sm" />
|
||||
</label>
|
||||
<label class="label">
|
||||
<span>Model</span>
|
||||
<input type="text" bind:value={model} class="input input-sm" />
|
||||
</label>
|
||||
</div>
|
||||
<label class="label">
|
||||
<span>API Token</span>
|
||||
<input type="password" bind:value={token} class="input input-sm font-mono" />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Model Parameters -->
|
||||
<div class="space-y-4 pt-4 border-t border-surface-500/10">
|
||||
<h3 class="text-sm font-bold uppercase tracking-widest text-surface-500 flex items-center gap-2">
|
||||
<FilePenLine size="1.1em" /> Inference Parameters
|
||||
</h3>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<label class="label">
|
||||
<span>Temperature ({temperature})</span>
|
||||
<input type="range" bind:value={temperature} min="0" max="1" step="0.1" class="range" />
|
||||
</label>
|
||||
<label class="label">
|
||||
<span>Max Tokens</span>
|
||||
<input type="number" bind:value={maxTokens} class="input input-sm" />
|
||||
</label>
|
||||
</div>
|
||||
<label class="label">
|
||||
<span>System Prompt</span>
|
||||
<textarea bind:value={systemPrompt} class="textarea h-24 text-xs font-mono"></textarea>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Modal>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* Add any generic wrapper styles here if needed */
|
||||
.ae-ai-tools-wrapper {
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
</div>
|
||||
Reference in New Issue
Block a user