Working on cleaning this up and breaking it into smaller chunks.

This commit is contained in:
Scott Idem
2026-01-08 17:49:25 -05:00
parent 467a49e86b
commit 28a4e9457e
2 changed files with 130 additions and 119 deletions

View File

@@ -1,23 +1,30 @@
<script lang="ts"> <script lang="ts">
/** /**
* JournalEntry_AITools.svelte * JournalEntry_AITools.svelte
* Extracted 2026-01-08 to modularize the massive Journal Entry God Component. * Extracted 2026-01-08 to modularize the Journal Entry view.
* Manages OpenAI summarization and display. * Encapsulates OpenAI logic and the Summary Modal.
*/ */
import OpenAI from 'openai'; import OpenAI from 'openai';
import { BotMessageSquare, Loader, FileText } from '@lucide/svelte'; import { Modal } from 'flowbite-svelte';
import {
Bot, BotMessageSquare, Loader, FileText,
Save, FilePenLine
} from '@lucide/svelte';
import { import {
journals_loc, journals_loc,
journals_sess journals_sess
} from '$lib/ae_journals/ae_journals_stores'; } from '$lib/ae_journals/ae_journals_stores';
import { ae_loc, ae_api } from '$lib/stores/ae_stores';
import type { ae_JournalEntry } from '$lib/types/ae_types'; import type { ae_JournalEntry } from '$lib/types/ae_types';
import E_app_codemirror_v5 from '$lib/app_components/e_app_codemirror_v5.svelte';
interface Props { interface Props {
entry: ae_JournalEntry; entry: ae_JournalEntry;
onSave: () => void;
log_lvl?: number; log_lvl?: number;
} }
let { entry, log_lvl = 0 }: Props = $props(); let { entry, onSave, log_lvl = 0 }: Props = $props();
let ae_promises: any = $state(null); let ae_promises: any = $state(null);
async function generate_summary() { async function generate_summary() {
@@ -36,59 +43,87 @@
{ {
role: 'system', role: 'system',
content: $journals_loc?.entry?.llm__system_prompt || content: $journals_loc?.entry?.llm__system_prompt ||
'You are a helpful assistant that helps people find information.' 'You are a helpful assistant.'
}, },
{ role: 'user', content: entry.content } { role: 'user', content: entry.content }
] ]
}).then((resp__chat) => { }).then((resp) => {
if (log_lvl) console.log('AI API Response:', resp__chat); $journals_sess.entry.ai_summary = resp?.choices?.[0]?.message?.content || 'No summary available';
$journals_sess.entry.ai_summary =
resp__chat?.choices?.[0]?.message?.content || 'No summary available';
$journals_sess.entry.show__ai_summary = true; $journals_sess.entry.show__ai_summary = true;
}); });
} catch (err: any) { } catch (err: any) {
console.error('Error from chat completion:', err); console.error('AI Error:', err);
alert('Error from chat completion: ' + (err?.message || err)); alert('AI Error: ' + err.message);
} }
} }
function use_saved_summary() {
$journals_sess.entry.ai_summary = entry.summary;
$journals_sess.entry.show__ai_summary = true;
}
</script> </script>
<div class="journal-ai-tools relative"> <!-- Summary Trigger Button -->
{#if !$journals_sess?.entry?.show__ai_summary} {#if !$journals_sess?.entry?.show__ai_summary}
{#if entry.content && entry.content.length > 50} {#if entry.content && entry.content.length > 50}
<button <button
type="button" type="button"
onclick={generate_summary} onclick={generate_summary}
class="btn btn-sm variant-filled-primary absolute top-2 right-2 z-10 shadow-lg" class="btn btn-sm preset-tonal-primary hover:preset-filled-primary-500 absolute top-2 right-2 z-10"
title="Generate AI summary" >
> {#await ae_promises}
{#await ae_promises} <Loader class="inline-block mr-1 animate-spin" />
<Loader class="inline-block mr-1 animate-spin" /> <span class="text-sm">Summarizing...</span>
<span class="text-sm">Summarizing...</span> {:then}
{:then} <BotMessageSquare class="inline-block mr-1" />
<BotMessageSquare class="inline-block mr-1" /> <span class="text-sm">Summarize</span>
<span class="text-sm">Summarize</span> {:catch}
{:catch} <span class="text-sm text-red-500">Error</span>
<span class="text-sm text-red-500">Error</span> {/await}
{/await} </button>
</button> {:else if entry.summary}
{:else if entry.summary} <button
<button type="button"
type="button" onclick={() => {
onclick={use_saved_summary} $journals_sess.entry.ai_summary = entry.summary;
class="btn btn-sm variant-filled-primary absolute top-2 right-2 z-10 shadow-lg" $journals_sess.entry.show__ai_summary = true;
title="Show saved summary" }}
> class="btn btn-sm preset-tonal-primary absolute top-2 right-2 z-10"
<FileText class="inline-block mr-1" /> >
Use Saved Summary <FileText size="1.25em" class="mr-1" /> Use Saved
</button> </button>
{/if}
{/if} {/if}
</div> {/if}
<!-- AI Summary Modal -->
{#if $journals_sess?.entry?.show__ai_summary && $journals_sess?.entry?.ai_summary}
<Modal
title="AI Summary"
bind:open={$journals_sess.entry.show__ai_summary}
size="md"
class="bg-white dark:bg-gray-800"
>
<div class="space-y-4">
<div class="flex gap-2 justify-center">
<button
class="btn btn-sm variant-filled-success"
onclick={() => {
entry.summary = $journals_sess.entry.ai_summary;
onSave();
}}
>
<Save size="1.2em" class="mr-1" /> Save to Entry
</button>
<button
class="btn btn-sm variant-filled-primary"
onclick={generate_summary}
>
<BotMessageSquare size="1.2em" class="mr-1" /> Re-Summarize
</button>
</div>
<E_app_codemirror_v5
editable={true}
content={$journals_sess.entry.ai_summary}
bind:new_content={$journals_sess.entry.ai_summary}
bind:theme_mode={$ae_loc.theme_mode}
class="p-2 border rounded-lg h-64"
/>
</div>
</Modal>
{/if}

View File

@@ -92,7 +92,8 @@
import Comp_hosted_files_download_button from '$lib/ae_core/ae_comp__hosted_files_download_button.svelte'; import Comp_hosted_files_download_button from '$lib/ae_core/ae_comp__hosted_files_download_button.svelte';
// *** Configuration // *** Configuration
let llm_api_token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVhYjI2MzdlLThiMjktNGM2Zi05MzVhLWFkYjU1MDkwMGU5MCJ9.4y5AStXZJAVnWRlgG3lVV0-xKIfMzqdNRuInGwT0ThQ'; let llm_api_token =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVhYjI2MzdlLThiMjktNGM2Zi05MzVhLWFkYjU1MDkwMGU5MCJ9.4y5AStXZJAVnWRlgG3lVV0-xKIfMzqdNRuInGwT0ThQ';
let llm_api_base_url = 'https://ai.dgrzone.com/api'; let llm_api_base_url = 'https://ai.dgrzone.com/api';
let llm_api_model = $journals_loc?.llm__api_model; let llm_api_model = $journals_loc?.llm__api_model;
@@ -123,7 +124,12 @@
// Idiomatic Svelte 5 change detection // Idiomatic Svelte 5 change detection
let tmp_entry_obj_changed = $derived.by(() => { let tmp_entry_obj_changed = $derived.by(() => {
if (!tmp_entry_obj || !orig_entry_obj || not_obj(tmp_entry_obj) || not_obj(orig_entry_obj)) { if (
!tmp_entry_obj ||
!orig_entry_obj ||
not_obj(tmp_entry_obj) ||
not_obj(orig_entry_obj)
) {
return false; return false;
} }
@@ -366,26 +372,18 @@
let { left_over_string, cut_out_string } = handle_cut_string( let { left_over_string, cut_out_string } = handle_cut_string(
tmp_entry_obj?.content tmp_entry_obj?.content
); );
// Need to decrypt the current content and current history. Assume the history has not been decrypted yet.
let content_enc_combined_data = await ae_util.encrypt_wrapper( let content_enc_combined_data = await ae_util.encrypt_wrapper(
left_over_string, left_over_string,
decrypt_key decrypt_key
); );
if (log_lvl) {
console.log('TEST: Encrypted string:', content_enc_combined_data); if (log_lvl) console.log('TEST: Encrypted string:', content_enc_combined_data);
}
// return content_enc_combined_data;
// let content_enc_combined_data = await handle_encrypt_string(left_over_string, decrypt_key)
data_kv.content_encrypted = content_enc_combined_data; data_kv.content_encrypted = content_enc_combined_data;
data_kv.content = null; data_kv.content = null;
tmp_entry_obj.content = null; tmp_entry_obj.content = null;
// decrypted_content = '';
if (log_lvl) {
console.log('TEST: Decrypting the history before saving it...');
}
if (tmp_entry_obj?.history_encrypted) { if (tmp_entry_obj?.history_encrypted) {
decrypted_history = await ae_util.decrypt_wrapper( decrypted_history = await ae_util.decrypt_wrapper(
tmp_entry_obj?.history_encrypted, tmp_entry_obj?.history_encrypted,
@@ -397,21 +395,15 @@
if (cut_out_string) { if (cut_out_string) {
let cut_prefix = `# Cut on ${ae_util.iso_datetime_formatter(new Date().toISOString(), 'datetime_iso_12_no_seconds')}\n`; let cut_prefix = `# Cut on ${ae_util.iso_datetime_formatter(new Date().toISOString(), 'datetime_iso_12_no_seconds')}\n`;
cut_out_string = cut_prefix + cut_out_string; cut_out_string = cut_prefix + cut_out_string;
console.log(`TEST: Cut out string with prefix: "${cut_out_string}"`);
} }
if (tmp_entry_obj?.history?.length && cut_out_string) { if (tmp_entry_obj?.history?.length && cut_out_string) {
console.log(`HERE 1`);
data_kv.history = tmp_entry_obj?.history + '\n' + cut_out_string + '\n'; data_kv.history = tmp_entry_obj?.history + '\n' + cut_out_string + '\n';
} else { } else {
console.log(`HERE 2`);
data_kv.history = cut_out_string + '\n'; data_kv.history = cut_out_string + '\n';
} }
console.log('TEST: data_kv.history:', data_kv.history);
if (log_lvl) { if (log_lvl) console.log('TEST: Encrypting history...');
console.log('TEST: Encrypting the history before saving it...');
}
let history_enc_combined_data = await ae_util.encrypt_wrapper( let history_enc_combined_data = await ae_util.encrypt_wrapper(
data_kv.history, data_kv.history,
decrypt_key decrypt_key
@@ -419,14 +411,10 @@
data_kv.history_encrypted = history_enc_combined_data; data_kv.history_encrypted = history_enc_combined_data;
data_kv.history = null; data_kv.history = null;
tmp_entry_obj.history;
decrypted_history = ''; decrypted_history = '';
} }
} }
// Call API to save the content // Call API to save the content
try { try {
const response = await journals_func.update_ae_obj__journal_entry({ const response = await journals_func.update_ae_obj__journal_entry({
@@ -435,7 +423,7 @@
data_kv: data_kv, data_kv: data_kv,
log_lvl: 1 log_lvl: 1
}); });
console.log('Journal entry updated successfully:', response); if (log_lvl) console.log('Journal entry updated successfully:', response);
updated_obj = true; updated_obj = true;
} catch (error) { } catch (error) {
console.error('Error updating journal entry:', error); console.error('Error updating journal entry:', error);
@@ -449,11 +437,8 @@
return; return;
} }
let data_kv = { let data_kv = { journal_id_random: tmp_entry_obj?.journal_id };
journal_id_random: tmp_entry_obj?.journal_id
};
// Call API to save the content
try { try {
await journals_func.update_ae_obj__journal_entry({ await journals_func.update_ae_obj__journal_entry({
api_cfg: $ae_api, api_cfg: $ae_api,
@@ -463,10 +448,10 @@
}); });
updated_obj = true; updated_obj = true;
updated_idb = false; updated_idb = false;
console.log('Journal entry updated successfully!'); if (log_lvl) console.log('Journal entry journal_id changed successfully!');
} catch (error) { } catch (error) {
console.error('Error updating journal entry:', error); console.error('Error changing journal ID:', error);
alert('Failed to update journal entry.'); alert('Failed to change journal ID.');
} }
$journals_slct.journal_id = tmp_entry_obj?.journal_id; $journals_slct.journal_id = tmp_entry_obj?.journal_id;
goto(`/journals/${tmp_entry_obj?.journal_id}`); goto(`/journals/${tmp_entry_obj?.journal_id}`);
@@ -561,12 +546,17 @@
p-2 md:p-3 rounded-lg shadow-md p-2 md:p-3 rounded-lg shadow-md
" "
> >
<div class="flex-1 flex flex-row flex-wrap gap-2 items-center justify-start min-w-[200px]"> <div
class="flex-1 flex flex-row flex-wrap gap-2 items-center justify-start min-w-[200px]"
>
<!-- Toggle edit for journal entry --> <!-- Toggle edit for journal entry -->
<button <button
type="button" type="button"
onclick={() => { onclick={() => {
const isEditing = $journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id] === 'current'; const isEditing =
$journals_loc.entry.edit_kv[
$lq__journal_entry_obj?.journal_entry_id
] === 'current';
if (isEditing) { if (isEditing) {
if (tmp_entry_obj_changed) { if (tmp_entry_obj_changed) {
@@ -574,11 +564,14 @@
update_journal_entry(); update_journal_entry();
} else { } else {
// Action: CANCEL/BACK TO VIEW // Action: CANCEL/BACK TO VIEW
$journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id] = false; $journals_loc.entry.edit_kv[
$lq__journal_entry_obj?.journal_entry_id
] = false;
} }
} else { } else {
// Action: ENTER EDIT MODE // Action: ENTER EDIT MODE
$journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id] = 'current'; $journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id] =
'current';
} }
}} }}
class=" class="
@@ -586,7 +579,9 @@
preset-tonal-surface hover:preset-filled-primary-500 preset-tonal-surface hover:preset-filled-primary-500
transition-all transition-all
" "
class:preset-filled-success-500={tmp_entry_obj_changed && $journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id] === 'current'} class:preset-filled-success-500={tmp_entry_obj_changed &&
$journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id] ===
'current'}
title="Toggle view/edit/save mode" title="Toggle view/edit/save mode"
> >
{#if $journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id] === 'current'} {#if $journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id] === 'current'}
@@ -600,7 +595,9 @@
{/if} {/if}
</button> </button>
<h2 class="journal_entry__name text-base md:text-lg font-bold truncate max-w-[200px] md:max-w-md"> <h2
class="journal_entry__name text-base md:text-lg font-bold truncate max-w-[200px] md:max-w-md"
>
{#if $journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id] == 'current'} {#if $journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id] == 'current'}
<input <input
type="text" type="text"
@@ -1818,46 +1815,27 @@
<!-- Select option list of Journals to choose from. This is used to assign the Journal Entry to a different Journal ID. --> <!-- Select option list of Journals to choose from. This is used to assign the Journal Entry to a different Journal ID. -->
{#if $ae_loc.edit_mode && $lq__journal_obj_li?.length} {#if $ae_loc.edit_mode && $lq__journal_obj_li?.length}
<div <div
class="w-full flex flex-row flex-wrap gap-2 items-center justify-start border border-gray-200 rounded-lg" class="w-full flex flex-row flex-wrap gap-2 items-center justify-start border border-gray-200 rounded-lg p-1"
> >
<span class="w-full text-center"> <span class="w-full text-center">
<SquareLibrary size="1em" class="mx-1 inline-block" /> <SquareLibrary size="1em" class="mx-1 inline-block text-primary-500" />
<span class="text-sm text-gray-500 hidden sm:inline"> <span class="text-xs text-gray-500 hidden sm:inline uppercase font-bold tracking-wider">
Move to Journal: Move to Journal:
</span> </span>
</span> </span>
<select <select
class="btn btn-secondary btn-sm class="select select-sm w-full bg-surface-500/10 border-none text-xs"
preset-tonal-primary
hover:preset-filled-primary-500
transition
text-xs
border-none
w-full
"
bind:value={tmp_entry_obj.journal_id} bind:value={tmp_entry_obj.journal_id}
onchange={(event) => { onchange={() => {
tmp_entry_obj.journal_id = ( if (confirm(`Are you sure you want to change the journal for this entry?`)) {
event.target as HTMLInputElement
).value;
console.log('Selected journal:', tmp_entry_obj.journal_id);
if (
confirm(
`Are you sure you want to change the journal for this entry?`
)
) {
change_journal_id(); change_journal_id();
} }
}} }}
title="Select a different journal for this entry"
> >
<option value="">Select Journal</option> <option value="">Select Journal</option>
{#each $lq__journal_obj_li as journal} {#each $lq__journal_obj_li as journal}
<option <option value={journal.journal_id}>
value={journal.journal_id}
title={`Journal: ${journal.name}`}
>
{journal.name} {journal.name}
</option> </option>
{/each} {/each}
@@ -1869,8 +1847,6 @@
</div> </div>
</header> </header>
<section <section
class=" class="
grow grow
@@ -2066,7 +2042,7 @@
{/if} {/if}
</article> </article>
{/if} {/if}
{#if $lq__journal_entry_obj?.data_json?.hosted_file_kv} {#if $lq__journal_entry_obj?.data_json?.hosted_file_kv}
<div class="flex flex-row flex-wrap gap-1 items-center justify-center w-full"> <div class="flex flex-row flex-wrap gap-1 items-center justify-center w-full">
<span class=""> <span class="">
<!-- <SquareDownload size="1em" class="mx-1 inline-block"/> --> <!-- <SquareDownload size="1em" class="mx-1 inline-block"/> -->
@@ -2172,7 +2148,7 @@
<E_app_codemirror_v5 <E_app_codemirror_v5
content={tmp_entry_obj?.content ?? ''} content={tmp_entry_obj?.content ?? ''}
bind:new_content={tmp_entry_obj.content} bind:new_content={tmp_entry_obj.content}
bind:editorView={editorView} bind:editorView
bind:theme_mode={$ae_loc.theme_mode} bind:theme_mode={$ae_loc.theme_mode}
placeholder="Write using Markdown here..." placeholder="Write using Markdown here..."
class=" class="
@@ -2318,7 +2294,9 @@
</section> </section>
{/if} {/if}
{:else if $journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id] == 'history'} {:else if $journals_loc.entry.edit_kv[$lq__journal_entry_obj?.journal_entry_id] == 'history'}
<div class="grow basis-full flex flex-col items-center justify-center h-full w-full max-w-6xl"> <div
class="grow basis-full flex flex-col items-center justify-center h-full w-full max-w-6xl"
>
{#if $journals_sess?.show__content__journal_entry_history == 'view'} {#if $journals_sess?.show__content__journal_entry_history == 'view'}
<E_app_codemirror_v5 <E_app_codemirror_v5
editable={false} editable={false}
@@ -2342,7 +2320,6 @@
{/if} {/if}
</section> </section>
<section class="ae_meta flex flex-row flex-wrap gap-1 items-center justify-between w-full"> <section class="ae_meta flex flex-row flex-wrap gap-1 items-center justify-between w-full">
<span class="flex flex-row items-center justify-center text-sm text-gray-500"> <span class="flex flex-row items-center justify-center text-sm text-gray-500">
{#if !$ae_loc.edit_mode} {#if !$ae_loc.edit_mode}
@@ -2515,7 +2492,6 @@
</label> </label>
</div> </div>
<E_app_codemirror_v5 <E_app_codemirror_v5
editable={true} editable={true}
readonly={false} readonly={false}