Last round of prettier: npx prettier --write src/
This commit is contained in:
@@ -1,125 +1,140 @@
|
||||
<script lang="ts">
|
||||
/**
|
||||
* AE_AITools.svelte
|
||||
* GENERIC Aether AI Toolset (Runes/Svelte 5)
|
||||
* 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, Settings,
|
||||
RefreshCcw, Globe, Copy
|
||||
} from '@lucide/svelte';
|
||||
import { ae_loc, ae_api } from '$lib/stores/ae_stores';
|
||||
import AE_Comp_Editor_CodeMirror from '$lib/elements/element_editor_codemirror.svelte';
|
||||
/**
|
||||
* AE_AITools.svelte
|
||||
* GENERIC Aether AI Toolset (Runes/Svelte 5)
|
||||
* 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,
|
||||
Settings,
|
||||
RefreshCcw,
|
||||
Globe,
|
||||
Copy
|
||||
} from '@lucide/svelte';
|
||||
import { ae_loc, ae_api } from '$lib/stores/ae_stores';
|
||||
import AE_Comp_Editor_CodeMirror from '$lib/elements/element_editor_codemirror.svelte';
|
||||
|
||||
interface Props {
|
||||
// Core Props
|
||||
content: string; // The text to summarize/analyze
|
||||
summary: string; // The result (bindable)
|
||||
|
||||
// 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;
|
||||
log_lvl?: number;
|
||||
interface Props {
|
||||
// Core Props
|
||||
content: string; // The text to summarize/analyze
|
||||
summary: string; // The result (bindable)
|
||||
|
||||
// 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;
|
||||
log_lvl?: number;
|
||||
}
|
||||
|
||||
let {
|
||||
content,
|
||||
summary = $bindable(),
|
||||
model = $bindable(),
|
||||
baseUrl = $bindable(),
|
||||
token = $bindable(),
|
||||
systemPrompt = $bindable(),
|
||||
maxTokens = $bindable(),
|
||||
temperature = $bindable(),
|
||||
onSave,
|
||||
onSyncConfig,
|
||||
buttonClass = 'btn btn-sm preset-tonal-primary shadow-lg hover:scale-105 transition-all',
|
||||
log_lvl = 0
|
||||
}: Props = $props();
|
||||
|
||||
// Apply defaults if undefined (Safe for Svelte 5 Runes)
|
||||
if (model === undefined) model = 'dgrzone-deepseek-8b-quick';
|
||||
if (baseUrl === undefined) baseUrl = 'https://ai.dgrzone.com/api';
|
||||
if (token === undefined) token = '';
|
||||
if (systemPrompt === undefined) systemPrompt = 'You are a helpful assistant.';
|
||||
if (maxTokens === undefined) maxTokens = 512;
|
||||
if (temperature === undefined) temperature = 0.7;
|
||||
|
||||
// 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 analyze.');
|
||||
return;
|
||||
}
|
||||
|
||||
let {
|
||||
content,
|
||||
summary = $bindable(),
|
||||
model = $bindable(),
|
||||
baseUrl = $bindable(),
|
||||
token = $bindable(),
|
||||
systemPrompt = $bindable(),
|
||||
maxTokens = $bindable(),
|
||||
temperature = $bindable(),
|
||||
onSave,
|
||||
onSyncConfig,
|
||||
buttonClass = "btn btn-sm preset-tonal-primary shadow-lg hover:scale-105 transition-all",
|
||||
log_lvl = 0
|
||||
}: Props = $props();
|
||||
active_tab = 'result';
|
||||
|
||||
// Apply defaults if undefined (Safe for Svelte 5 Runes)
|
||||
if (model === undefined) model = 'dgrzone-deepseek-8b-quick';
|
||||
if (baseUrl === undefined) baseUrl = 'https://ai.dgrzone.com/api';
|
||||
if (token === undefined) token = '';
|
||||
if (systemPrompt === undefined) systemPrompt = 'You are a helpful assistant.';
|
||||
if (maxTokens === undefined) maxTokens = 512;
|
||||
if (temperature === undefined) temperature = 0.7;
|
||||
|
||||
// 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 analyze.');
|
||||
return;
|
||||
}
|
||||
|
||||
active_tab = 'result';
|
||||
|
||||
// If no token is provided, trigger a "Demo Mode" placeholder after a fake delay
|
||||
if (!token || token === '') {
|
||||
console.log('AE_AITools: No token provided. Entering Demo Mode.');
|
||||
ae_promises = new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
tmp_summary = `### AI Summary (DEMO MODE)\n\nThis is a placeholder summary because no API token was provided in the settings. \n\n**Original Content Length:** ${content.length} characters.\n\n**System Prompt:** ${systemPrompt}\n\n**Model:** ${model}`;
|
||||
show_modal = true;
|
||||
resolve(true);
|
||||
}, 1500);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const ai_client = new OpenAI({
|
||||
apiKey: token,
|
||||
baseURL: baseUrl,
|
||||
dangerouslyAllowBrowser: true
|
||||
// If no token is provided, trigger a "Demo Mode" placeholder after a fake delay
|
||||
if (!token || token === '') {
|
||||
console.log('AE_AITools: No token provided. Entering Demo Mode.');
|
||||
ae_promises = new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
tmp_summary = `### AI Summary (DEMO MODE)\n\nThis is a placeholder summary because no API token was provided in the settings. \n\n**Original Content Length:** ${content.length} characters.\n\n**System Prompt:** ${systemPrompt}\n\n**Model:** ${model}`;
|
||||
show_modal = true;
|
||||
resolve(true);
|
||||
}, 1500);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
ae_promises = ai_client.chat.completions.create({
|
||||
const ai_client = new OpenAI({
|
||||
apiKey: token,
|
||||
baseURL: baseUrl,
|
||||
dangerouslyAllowBrowser: true
|
||||
});
|
||||
|
||||
try {
|
||||
ae_promises = ai_client.chat.completions
|
||||
.create({
|
||||
model: model || 'dgrzone-deepseek-8b-quick',
|
||||
max_tokens: maxTokens,
|
||||
temperature: temperature,
|
||||
messages: [
|
||||
{ role: 'system', content: systemPrompt || 'You are a helpful assistant.' },
|
||||
{
|
||||
role: 'system',
|
||||
content: systemPrompt || 'You are a helpful assistant.'
|
||||
},
|
||||
{ role: 'user', content: content }
|
||||
]
|
||||
}).then((resp) => {
|
||||
const result = resp?.choices?.[0]?.message?.content || 'No result generated.';
|
||||
})
|
||||
.then((resp) => {
|
||||
const result =
|
||||
resp?.choices?.[0]?.message?.content ||
|
||||
'No result generated.';
|
||||
tmp_summary = result;
|
||||
show_modal = true;
|
||||
});
|
||||
} catch (err: any) {
|
||||
console.error('AE_AITools: AI Error:', err);
|
||||
// Even on error, show the modal with the error message so the UI can be inspected
|
||||
tmp_summary = `### AI Error\n\nFailed to connect to the AI service.\n\n**Error:** ${err.message}\n\nCheck your Settings tab for Base URL and Token configuration.`;
|
||||
show_modal = true;
|
||||
ae_promises = Promise.resolve();
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error('AE_AITools: AI Error:', err);
|
||||
// Even on error, show the modal with the error message so the UI can be inspected
|
||||
tmp_summary = `### AI Error\n\nFailed to connect to the AI service.\n\n**Error:** ${err.message}\n\nCheck your Settings tab for Base URL and Token configuration.`;
|
||||
show_modal = true;
|
||||
ae_promises = Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
function handle_save() {
|
||||
summary = tmp_summary;
|
||||
if (onSave) onSave(tmp_summary);
|
||||
show_modal = false;
|
||||
}
|
||||
function handle_save() {
|
||||
summary = tmp_summary;
|
||||
if (onSave) onSave(tmp_summary);
|
||||
show_modal = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="ae-ai-tools-wrapper inline-flex items-center gap-1">
|
||||
@@ -128,13 +143,12 @@
|
||||
type="button"
|
||||
onclick={generate_ai_result}
|
||||
class={buttonClass}
|
||||
title="Generate AI summary/analysis"
|
||||
>
|
||||
title="Generate AI summary/analysis">
|
||||
{#await ae_promises}
|
||||
<Loader class="inline-block mr-1 animate-spin" size="1.2em" />
|
||||
<Loader class="mr-1 inline-block animate-spin" size="1.2em" />
|
||||
<span class="text-sm">Processing...</span>
|
||||
{:then}
|
||||
<BotMessageSquare class="inline-block mr-1" size="1.2em" />
|
||||
<BotMessageSquare class="mr-1 inline-block" size="1.2em" />
|
||||
<span class="text-sm">Summarize</span>
|
||||
{:catch}
|
||||
<span class="text-sm text-red-500">Error</span>
|
||||
@@ -149,8 +163,7 @@
|
||||
show_modal = true;
|
||||
}}
|
||||
class="btn btn-sm variant-soft-surface shadow-md"
|
||||
title="AI Settings"
|
||||
>
|
||||
title="AI Settings">
|
||||
<Settings size="1.2em" />
|
||||
</button>
|
||||
|
||||
@@ -160,32 +173,37 @@
|
||||
title="Aether AI Assistant"
|
||||
bind:open={show_modal}
|
||||
size="lg"
|
||||
class="bg-white dark:bg-gray-800"
|
||||
>
|
||||
class="bg-white dark:bg-gray-800">
|
||||
<div class="space-y-4 p-2">
|
||||
<!-- 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'}
|
||||
>
|
||||
<div class="border-surface-500/20 flex gap-1 border-b 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'}
|
||||
>
|
||||
<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>
|
||||
|
||||
{#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}>
|
||||
<div class="animate-in fade-in space-y-4 duration-200">
|
||||
<div class="flex justify-start 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}>
|
||||
<button
|
||||
class="btn btn-sm variant-ghost-primary"
|
||||
onclick={generate_ai_result}>
|
||||
<RotateCcw size="1.1em" class="mr-1" /> Re-run
|
||||
</button>
|
||||
</div>
|
||||
@@ -195,57 +213,84 @@
|
||||
bind:new_content={tmp_summary}
|
||||
theme_mode={$ae_loc.theme_mode}
|
||||
placeholder="AI Result will appear here..."
|
||||
class_li="p-2 border rounded-lg h-96 shadow-inner bg-surface-500/5"
|
||||
/>
|
||||
class_li="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">
|
||||
<div
|
||||
class="animate-in slide-in-from-left-4 space-y-6 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">
|
||||
<h3
|
||||
class="text-surface-500 flex items-center gap-2 text-sm font-bold tracking-widest uppercase">
|
||||
<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
|
||||
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">
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<label class="label">
|
||||
<span>Base URL</span>
|
||||
<input type="text" bind:value={baseUrl} class="input input-sm" />
|
||||
<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" />
|
||||
<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" />
|
||||
<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">
|
||||
<div
|
||||
class="border-surface-500/10 space-y-4 border-t pt-4">
|
||||
<h3
|
||||
class="text-surface-500 flex items-center gap-2 text-sm font-bold tracking-widest uppercase">
|
||||
<FilePenLine size="1.1em" /> Inference Parameters
|
||||
</h3>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<label class="label">
|
||||
<span>Temperature ({temperature})</span>
|
||||
<input type="range" bind:value={temperature} min="0" max="1" step="0.1" class="range" />
|
||||
<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" />
|
||||
<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>
|
||||
<textarea
|
||||
bind:value={systemPrompt}
|
||||
class="textarea h-24 font-mono text-xs"
|
||||
></textarea>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -253,4 +298,4 @@
|
||||
</div>
|
||||
</Modal>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user