Implement generic AE_AITools component and integrate into Journals

- Created reusable AE_AITools.svelte in src/lib/ae_elements for system-wide AI features.
- Refactored Journal Entry view to utilize the generic AI toolset.
- Cleaned up redundant module-specific AI logic and modal code.
- Standardized Svelte 5  patterns for AI summary results.
This commit is contained in:
Scott Idem
2026-01-08 18:02:05 -05:00
parent 0c16649cb4
commit efe8677ab6
2 changed files with 166 additions and 2 deletions

View File

@@ -0,0 +1,152 @@
<script lang="ts">
/**
* AE_AITools.svelte
* GENERIC Aether AI Toolset (Runes/Svelte 5)
* Reusable across all modules (Journals, Events, People, etc.)
*/
import OpenAI from 'openai';
import { Modal } from 'flowbite-svelte';
import {
Bot, BotMessageSquare, Loader, FileText,
Save, FilePenLine, RotateCcw
} 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';
interface Props {
// Core Props
content: string; // The text to summarize/analyze
summary: string; // The result (bindable)
// Configuration
model?: string;
baseUrl?: string;
token?: string;
systemPrompt?: string;
// Callbacks
onSave?: (newSummary: string) => void;
// 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.',
onSave,
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 tmp_summary = $state('');
async function generate_ai_result() {
if (!content) {
alert('No content available to summarize.');
return;
}
const ai_client = new OpenAI({
apiKey: token || 'no-token-provided',
baseURL: baseUrl || 'https://ai.dgrzone.com/api',
dangerouslyAllowBrowser: true
});
try {
ae_promises = ai_client.chat.completions.create({
model: model || 'dgrzone-deepseek-8b-quick',
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;
});
} catch (err: any) {
console.error('AE_AITools: AI Error:', err);
alert('AI Error: ' + err.message);
}
}
function handle_save() {
summary = tmp_summary;
if (onSave) onSave(tmp_summary);
show_modal = false;
}
</script>
<div class="ae-ai-tools-wrapper">
<!-- Trigger Button -->
<button
type="button"
onclick={generate_ai_result}
class={buttonClass}
title="Generate AI summary/analysis"
>
{#await ae_promises}
<Loader class="inline-block mr-1 animate-spin" size="1.2em" />
<span class="text-sm">Processing...</span>
{:then}
<BotMessageSquare class="inline-block mr-1" size="1.2em" />
<span class="text-sm">Summarize</span>
{:catch}
<span class="text-sm text-red-500">Error</span>
{/await}
</button>
<!-- Modal for Result Review -->
{#if show_modal}
<Modal
title="AI Generated Summary"
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>
</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>
</Modal>
{/if}
</div>
<style>
/* Add any generic wrapper styles here if needed */
.ae-ai-tools-wrapper {
display: inline-block;
}
</style>

View File

@@ -90,7 +90,7 @@
import { journals_func } from '$lib/ae_journals/ae_journals_functions';
import Comp_journal_entry_file_li from './ae_comp__journal_entry_obj_file_li.svelte';
import Comp_hosted_files_download_button from '$lib/ae_core/ae_comp__hosted_files_download_button.svelte';
import JournalEntry_AITools from './JournalEntry_AITools.svelte';
import AE_AITools from '$lib/ae_elements/AE_AITools.svelte';
// *** Configuration
let llm_api_token =
@@ -1866,7 +1866,19 @@
$lq__journal_entry_obj?.journal_entry_id
] == 'current'}
>
<JournalEntry_AITools entry={tmp_entry_obj} onSave={update_journal_entry} {log_lvl} />
<!-- Generic AI Toolset -->
<div class="absolute top-2 right-2 z-10">
<AE_AITools
content={tmp_entry_obj.content}
bind:summary={tmp_entry_obj.summary}
onSave={update_journal_entry}
token={$journals_loc?.llm__api_token}
baseUrl={$journals_loc?.llm__api_base_url}
model={$journals_loc?.llm__api_model}
systemPrompt={$journals_loc?.entry?.llm__system_prompt}
{log_lvl}
/>
</div>
{#if !$journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id]}
{#if $lq__journal_obj?.cfg_json?.pref_viewer == 'codemirror'}