mirror of
https://github.com/danielmiessler/Fabric.git
synced 2026-01-10 06:48:04 -05:00
Moved pattern loader to ModelConfig. Editing styles in chat/. Added page fly transitions. Tidying. Removed - ChatHeader, unused modal from Transcripts, FlyandScaleParams from lib/types/utils.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/types/utils";
|
||||
let className: string | undefined = undefined;
|
||||
export let value = undefined;
|
||||
export let value: string | undefined = undefined;
|
||||
export { className as class };
|
||||
export let readonly = undefined;
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script>
|
||||
import { cn } from "$lib/types/utils.ts";
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/types/utils";
|
||||
let className = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { clsx, type ClassValue } from 'clsx';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import { cubicOut } from 'svelte/easing';
|
||||
import type { TransitionConfig } from 'svelte/transition';
|
||||
// import { cubicOut } from 'svelte/easing';
|
||||
// import type { TransitionConfig } from 'svelte/transition';
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
type FlyAndScaleParams = {
|
||||
/* type FlyAndScaleParams = {
|
||||
y?: number;
|
||||
x?: number;
|
||||
start?: number;
|
||||
@@ -53,4 +53,4 @@ export const flyAndScale = (
|
||||
},
|
||||
easing: cubicOut
|
||||
};
|
||||
};
|
||||
}; */
|
||||
@@ -4,6 +4,8 @@
|
||||
import Footer from './Footer.svelte';
|
||||
import Header from './Header.svelte';
|
||||
import { initializeStores } from '@skeletonlabs/skeleton';
|
||||
import { page } from '$app/stores';
|
||||
import { fly } from 'svelte/transition';
|
||||
import { getToastStore } from '@skeletonlabs/skeleton';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
@@ -14,9 +16,9 @@
|
||||
|
||||
onMount(() => {
|
||||
toastStore.trigger({
|
||||
message: "👋 Welcome to the site! I'm still working on it... ",
|
||||
message: "👋 Welcome to the site! Tell people about yourself and what you do.",
|
||||
background: 'variant-filled-primary',
|
||||
timeout: 15000,
|
||||
timeout: 3333,
|
||||
hoverable: true
|
||||
});
|
||||
});
|
||||
@@ -24,6 +26,7 @@
|
||||
|
||||
<Toast position="t" />
|
||||
|
||||
{#key $page.url.pathname}
|
||||
<AppShell class="relative">
|
||||
<div class="fixed inset-0 bg-gradient-to-br from-primary-500/20 via-tertiary-500/20 to-secondary-500/20 -z-10"></div>
|
||||
<svelte:fragment slot="header">
|
||||
@@ -31,13 +34,18 @@
|
||||
<div class="bg-gradient-to-b variant-gradient-primary-tertiary opacity-20 h-2 py-4">
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
<div
|
||||
in:fly={{ duration: 1000, delay: 300, y: 100 }}
|
||||
>
|
||||
<main class="mx-auto p-4">
|
||||
<slot />
|
||||
</main>
|
||||
</div>
|
||||
<svelte:fragment slot="footer">
|
||||
<Footer />
|
||||
</svelte:fragment>
|
||||
</AppShell>
|
||||
{/key}
|
||||
|
||||
<style>
|
||||
main {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import Fabric from './Fabric.svelte';
|
||||
</script>
|
||||
|
||||
<div class="">
|
||||
<div>
|
||||
<Terminal />
|
||||
<div class="absolute inset-0 -z-10 overflow-hidden h-96">
|
||||
<Fabric />
|
||||
@@ -11,3 +11,4 @@
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import { ParticleSystem } from './ParticleSystem';
|
||||
import { createParticleGradient } from '$lib/utils/canvas';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
export let particleCount = 100;
|
||||
export let particleSize = 3;
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
<footer class="border-t bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
||||
<div class="container flex h-14 items-center justify-between px-4">
|
||||
<p class="text-sm text-muted-foreground">
|
||||
Built in {year} by @johnconnor-sec
|
||||
Built in {year} by @Fabric <!-- Feel free to put your name here -->
|
||||
</p>
|
||||
|
||||
<nav class="flex items-center gap-4 ">
|
||||
<BuyMeCoffee url="https://www.buymeacoffee.com/johnconnor.sec" />
|
||||
<BuyMeCoffee url="https://www.buymeacoffee.com/johnconnor.sec" /> <!-- And here -->
|
||||
</nav>
|
||||
</div>
|
||||
</footer>
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { fade } from 'svelte/transition';
|
||||
// import { fade } from 'svelte/transition';
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
let mounted = false;
|
||||
@@ -98,7 +98,7 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="min-h-[calc(100vh-4rem)] pt-20 pb-8 px-4">
|
||||
<div class="pt-20 pb-8 px-4">
|
||||
<div class="container mx-auto max-w-4xl">
|
||||
<div class="terminal-window backdrop-blur-sm">
|
||||
<!-- Terminal header -->
|
||||
@@ -111,14 +111,12 @@
|
||||
<span class="text-sm text-gray-400 ml-2">me@localhost</span>
|
||||
</div>
|
||||
|
||||
<!-- Terminal content -->
|
||||
<div class="p-6">
|
||||
<!-- Terminal output -->
|
||||
<div class="mb-4 whitespace-pre-wrap terminal-text leading-relaxed">{terminalContent}</div>
|
||||
|
||||
<!-- Command input -->
|
||||
{#if mounted}
|
||||
<div class="flex items-center command-input" in:fade={{ duration: 200 }}>
|
||||
<div class="flex items-center command-input">
|
||||
<span class="mr-2 terminal-prompt font-bold">$</span>
|
||||
<!-- {#if showCursor}
|
||||
<span class="animate-blink terminal-text">▋</span>
|
||||
@@ -132,16 +130,6 @@
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Command history -->
|
||||
<!-- <div class="mt-6 space-y-1 text-sm text-gray-500">
|
||||
{#each commandHistory as cmd}
|
||||
<div class="terminal-text opacity-60">
|
||||
<span class="terminal-prompt font-bold mr-2">$</span>
|
||||
{cmd}
|
||||
</div>
|
||||
{/each}
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,20 +1,10 @@
|
||||
<script lang="ts">
|
||||
import ChatHeader from './ChatHeader.svelte';
|
||||
import ChatInput from "./ChatInput.svelte";
|
||||
import ChatMessages from "./ChatMessages.svelte";
|
||||
import ModelConfig from "./ModelConfig.svelte";
|
||||
|
||||
//import { Button } from "$lib/components/ui/button";
|
||||
//import { setSystemPrompt } from "$lib/store/chat";
|
||||
// import { Select } from "$lib/components/ui/select";
|
||||
// import { Save, Code, Share2, MoreHorizontal } from 'lucide-svelte';
|
||||
// import { onMount } from "svelte";
|
||||
|
||||
</script>
|
||||
|
||||
<ChatHeader />
|
||||
|
||||
<div class="flex-1 container mx-auto p-4">
|
||||
<div class="flex flex-align-vertical mx-auto p-4">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-[minmax(400px,_600px),minmax(400px,_600px),300px] gap-4">
|
||||
<div class="space-y-4 order-2 lg:order-1 h-full overflow-y flex-1">
|
||||
<ChatInput />
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
<!-- Moved to ModelConfig.svelte -->
|
||||
|
||||
<!-- <script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { availableModels, modelConfig, loadAvailableModels } from '$lib/store/model-config';
|
||||
|
||||
onMount(async () => {
|
||||
await loadAvailableModels();
|
||||
});
|
||||
</script>
|
||||
|
||||
<select
|
||||
class="flex h-9 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 appearance-none"
|
||||
bind:value={$modelConfig.model}
|
||||
>
|
||||
<option value="">Select a model...</option>
|
||||
{#each $availableModels as model}
|
||||
<option value={model.name}>
|
||||
{model.name} ({model.vendor})
|
||||
</option>
|
||||
{/each}
|
||||
</select> -->
|
||||
@@ -1,36 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { patterns } from "$lib/types/chat/patterns";
|
||||
import { patternAPI } from "$lib/types/chat/patterns";
|
||||
import { Select } from "$lib/components/ui/select";
|
||||
import { onMount } from "svelte";
|
||||
|
||||
let selectedPreset = "";
|
||||
|
||||
$: if (selectedPreset) {
|
||||
console.log('Pattern selected:', selectedPreset);
|
||||
patternAPI.selectPattern(selectedPreset);
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
await patternAPI.loadPatterns();
|
||||
});
|
||||
</script>
|
||||
|
||||
<header class="border-b">
|
||||
<div class="container mx-auto p-3">
|
||||
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
|
||||
<h1 class="text-xl font-semibold">Chat</h1>
|
||||
<div class="flex flex-wrap items-center gap-2 w-full sm:w-auto">
|
||||
<Select
|
||||
class="w-full font-bold sm:w-[200px]"
|
||||
bind:value={selectedPreset}
|
||||
>
|
||||
<option value="">Load a pattern...</option>
|
||||
{#each $patterns as pattern}
|
||||
<option value={pattern.Name}>{pattern.Description}</option>
|
||||
{/each}
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
@@ -2,6 +2,7 @@
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { Textarea } from "$lib/components/ui/textarea";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
// import { FileButton } from '@skeletonlabs/skeleton';
|
||||
import { currentSession, setSession, sendMessage } from '$lib/store/chat';
|
||||
import { systemPrompt } from '$lib/types/chat/patterns';
|
||||
import { onMount } from 'svelte';
|
||||
@@ -49,10 +50,11 @@
|
||||
|
||||
<div class="flex flex-col h-full">
|
||||
<div class="space-y-2 flex-none">
|
||||
<Label class="p-1 font-bold">System Prompt</Label>
|
||||
<Label
|
||||
class="p-1 font-bold">System Prompt</Label>
|
||||
<Textarea
|
||||
value={$systemPrompt}
|
||||
on:input={(e) => systemPrompt}
|
||||
on:input={(e) => $systemPrompt}
|
||||
placeholder="Enter system instructions..."
|
||||
class="min-h-[200px] resize-none bg-background"
|
||||
/>
|
||||
@@ -60,13 +62,14 @@
|
||||
|
||||
<div class="space-y-2 flex-1 py-1">
|
||||
<Label class="p-1 font-bold">User Input</Label>
|
||||
<Textarea
|
||||
bind:value={userInput}
|
||||
placeholder="Enter your message..."
|
||||
class="h-[calc(80vh-24rem)] resize-none bg-background"
|
||||
/>
|
||||
<Textarea
|
||||
bind:value={userInput}
|
||||
on:input={(e) => userInput}
|
||||
placeholder="Enter your message..."
|
||||
class="h-[calc(80vh-24rem)] resize-none bg-background"
|
||||
/>
|
||||
</div>
|
||||
<Button on:click={handleSubmit} variant="outline" size="sm" class="mt-2">Submit</Button>
|
||||
<Button on:click={handleSubmit} variant="outline" size="sm">Submit</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { RotateCcw, Trash2, Save } from 'lucide-svelte';
|
||||
import { RotateCcw, Trash2, Save, Copy } from 'lucide-svelte';
|
||||
import { chatState, clearMessages, revertLastMessage, currentSession } from '$lib/store/chat';
|
||||
import { afterUpdate } from 'svelte';
|
||||
|
||||
import { marked } from 'marked';
|
||||
import { getToastStore } from '@skeletonlabs/skeleton';
|
||||
import { Toast } from '@skeletonlabs/skeleton';
|
||||
|
||||
let sessionName: string | null = null;
|
||||
let messagesContainer: HTMLDivElement;
|
||||
|
||||
@@ -29,19 +32,55 @@
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
marked.setOptions({
|
||||
gfm: true,
|
||||
breaks: true,
|
||||
});
|
||||
|
||||
function renderMarkdown(content: string, isAssistant: boolean) {
|
||||
if (!isAssistant) return content;
|
||||
try {
|
||||
return marked.parse(content);
|
||||
} catch (error) {
|
||||
console.error('Error rendering markdown:', error);
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
||||
const toastStore = getToastStore();
|
||||
|
||||
async function copyToClipboard() {
|
||||
try {
|
||||
await navigator.clipboard.writeText($chatState.messages.map(m => m.content).join('\n'));
|
||||
toastStore.trigger({
|
||||
message: 'Chat copied to clipboard!',
|
||||
background: 'variant-filled-success'
|
||||
});
|
||||
} catch (err) {
|
||||
toastStore.trigger({
|
||||
message: 'Failed to copy transcript',
|
||||
background: 'variant-filled-error'
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="chat-messages-wrapper flex flex-col h-full">
|
||||
<div class="flex justify-between items-center mb-4 flex-none">
|
||||
<span class="text-sm font-medium">Chat History</span>
|
||||
<div class="flex gap-2">
|
||||
<Button variant="outline" size="icon" on:click={revertLastMessage}>
|
||||
<Button variant="outline" size="icon" aria-label="Revert Last Message" on:click={revertLastMessage}>
|
||||
<RotateCcw class="h-4 w-4" />
|
||||
</Button>
|
||||
<Button variant="outline" size="icon" on:click={clearMessages}>
|
||||
<Button variant="outline" size="icon" aria-label="Clear Chat" on:click={clearMessages}>
|
||||
<Trash2 class="h-4 w-4" />
|
||||
</Button>
|
||||
<Button variant="outline" size="icon" on:click={saveChat}>
|
||||
<Button variant="outline" size="icon" aria-label="Copy Chat" on:click={copyToClipboard}>
|
||||
<Copy class="h-4 w-4" />
|
||||
</Button>
|
||||
<Toast position="b" />
|
||||
<Button variant="outline" size="icon" aria-label="Save Chat" on:click={saveChat}>
|
||||
<Save class="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
@@ -50,9 +89,15 @@
|
||||
<div class="messages-container flex-1" bind:this={messagesContainer}>
|
||||
<div class="messages-content space-y-4">
|
||||
{#each $chatState.messages as message}
|
||||
<div class="message-item whitespace-pre-wrap text-sm {message.role === 'assistant' ? 'pl-4' : 'font-medium'} transition-all">
|
||||
<div class="message-item {message.role === 'assistant' ? 'pl-4' : 'font-medium'} transition-all">
|
||||
<span class="text-xs tertiary uppercase">{message.role}:</span>
|
||||
{message.content}
|
||||
{#if message.role === 'assistant'}
|
||||
{@html renderMarkdown(message.content, true)}
|
||||
{:else}
|
||||
<div class="whitespace-pre-wrap text-sm">
|
||||
{message.content}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
{#if $chatState.isStreaming}
|
||||
@@ -97,4 +142,50 @@
|
||||
background-color: var(--color-primary-500);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
/* Markdown content styles */
|
||||
:global(.message-item.pl-4) {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
:global(.message-item pre) {
|
||||
background-color: rgb(var(--color-secondary-50));
|
||||
padding: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
margin: 0.5rem 0;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
:global(.message-item code) {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
font-size: 0.875rem;
|
||||
padding: 0.2rem 0.4rem;
|
||||
border-radius: 0.25rem;
|
||||
background-color: rgb(var(--color-secondary-50));
|
||||
}
|
||||
|
||||
:global(.message-item p) {
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
:global(.message-item ul, .message-item ol) {
|
||||
margin: 0.5rem 0;
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
|
||||
:global(.message-item li) {
|
||||
margin: 0.25rem 0;
|
||||
}
|
||||
|
||||
:global(.message-item a) {
|
||||
color: rgb(var(--color-primary-600));
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
:global(.message-item blockquote) {
|
||||
border-left: 4px solid rgb(var(--color-secondary-200));
|
||||
margin: 0.5rem 0;
|
||||
padding-left: 1rem;
|
||||
font-style: italic;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,57 +5,87 @@
|
||||
import { Slider } from "$lib/components/ui/slider";
|
||||
import { modelConfig, availableModels, loadAvailableModels } from "$lib/store/model-config";
|
||||
import Transcripts from "./Transcripts.svelte";
|
||||
import { patterns } from "$lib/types/chat/patterns";
|
||||
import { patternAPI } from "$lib/types/chat/patterns";
|
||||
|
||||
let selectedPreset = "";
|
||||
|
||||
$: if (selectedPreset) {
|
||||
console.log('Pattern selected:', selectedPreset);
|
||||
patternAPI.selectPattern(selectedPreset);
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
await loadAvailableModels();
|
||||
await patternAPI.loadPatterns();
|
||||
});
|
||||
|
||||
//Debugging
|
||||
//$: console.log('Current available models:', $availableModels);
|
||||
//$: console.log('Current model config:', $modelConfig);
|
||||
</script>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<Label>Model</Label>
|
||||
<Select bind:value={$modelConfig.model}>
|
||||
<option value="">Default Model</option>
|
||||
{#each $availableModels as model (model.name)}
|
||||
<option value={model.name}>{model.vendor} - {model.name}</option>
|
||||
{/each}
|
||||
</Select>
|
||||
</div>
|
||||
<div class="flex gap-4">
|
||||
<div class="flex-1">
|
||||
<Select
|
||||
bind:value={selectedPreset}
|
||||
>
|
||||
<option value="">Load a pattern...</option>
|
||||
{#each $patterns as pattern}
|
||||
<option value={pattern.Name}>{pattern.Description}</option>
|
||||
{/each}
|
||||
</Select>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<Select
|
||||
bind:value={$modelConfig.model}
|
||||
>
|
||||
<option value="">Default Model</option>
|
||||
{#each $availableModels as model (model.name)}
|
||||
<option value={model.name}>{model.vendor} - {model.name}</option>
|
||||
{/each}
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label class="p-1 font-bold">Temperature ({$modelConfig.temperature.toFixed(1)})</Label>
|
||||
<Slider
|
||||
bind:value={$modelConfig.temperature}
|
||||
min={0}
|
||||
max={2}
|
||||
step={0.1}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label>Temperature ({$modelConfig.temperature.toFixed(1)})</Label>
|
||||
<Slider
|
||||
bind:value={$modelConfig.temperature}
|
||||
min={0}
|
||||
max={2}
|
||||
step={0.1}
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label class="p-1 font-bold">Maximum Length ({$modelConfig.maxLength})</Label>
|
||||
<Slider
|
||||
bind:value={$modelConfig.maxLength}
|
||||
min={1}
|
||||
max={4000}
|
||||
step={1}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label>Maximum Length ({$modelConfig.maxLength})</Label>
|
||||
<Slider
|
||||
bind:value={$modelConfig.maxLength}
|
||||
min={1}
|
||||
max={4000}
|
||||
step={1}
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label class="p-1 font-bold">Top P ({$modelConfig.top_p.toFixed(2)})</Label>
|
||||
<Slider
|
||||
bind:value={$modelConfig.top_p}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label>Top P ({$modelConfig.top_p.toFixed(2)})</Label>
|
||||
<Slider
|
||||
bind:value={$modelConfig.top_p}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Transcripts />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label class="p-1 font-bold">Frequency Penalty ({$modelConfig.frequency.toFixed(2)})</Label>
|
||||
<Slider
|
||||
bind:value={$modelConfig.frequency}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Transcripts />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
import { getToastStore } from '@skeletonlabs/skeleton';
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import Input from '$components/ui/input/input.svelte';
|
||||
import { getModalStore, type ModalSettings } from '@skeletonlabs/skeleton';
|
||||
|
||||
import { Toast } from '@skeletonlabs/skeleton';
|
||||
|
||||
let url = '';
|
||||
let transcript = '';
|
||||
let loading = false;
|
||||
@@ -11,13 +11,13 @@
|
||||
let title = '';
|
||||
|
||||
const toastStore = getToastStore();
|
||||
const modalStore = getModalStore();
|
||||
|
||||
// Event dispatcher to send transcript to parent
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
|
||||
async function fetchTranscript() {
|
||||
function isValidYouTubeUrl(url: string) {
|
||||
const pattern = /^(https?:\/\/)?(www\.)?(youtube\.com|youtu\.be)\/.+$/;
|
||||
return pattern.test(url);
|
||||
}
|
||||
|
||||
if (!isValidYouTubeUrl(url)) {
|
||||
error = 'Please enter a valid YouTube URL';
|
||||
toastStore.trigger({
|
||||
@@ -50,42 +50,12 @@
|
||||
|
||||
transcript = data.transcript;
|
||||
title = data.title;
|
||||
|
||||
// Show modal with transcript
|
||||
const modal: ModalSettings = {
|
||||
type: 'component',
|
||||
title: 'YouTube Transcript',
|
||||
body: transcript,
|
||||
buttonTextCancel: 'Close',
|
||||
buttonTextSubmit: 'Use in Chat',
|
||||
response: (r: boolean) => {
|
||||
if (r) {
|
||||
dispatch('transcript', transcript);
|
||||
}
|
||||
}
|
||||
};
|
||||
modalStore.trigger(modal);
|
||||
|
||||
toastStore.trigger({
|
||||
message: 'Transcript fetched successfully!',
|
||||
background: 'variant-filled-success'
|
||||
});
|
||||
} catch (err) {
|
||||
error = err.message;
|
||||
toastStore.trigger({
|
||||
message: error,
|
||||
background: 'variant-filled-error'
|
||||
});
|
||||
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
function isValidYouTubeUrl(url) {
|
||||
const pattern = /^(https?:\/\/)?(www\.)?(youtube\.com|youtu\.be)\/.+$/;
|
||||
return pattern.test(url);
|
||||
}
|
||||
|
||||
|
||||
async function copyToClipboard() {
|
||||
try {
|
||||
await navigator.clipboard.writeText(transcript);
|
||||
@@ -129,6 +99,7 @@
|
||||
{/if}
|
||||
|
||||
{#if transcript}
|
||||
<Toast position="b" />
|
||||
<div class="space-y-4 border rounded-lg p-4 bg-muted/50 h-96">
|
||||
<div class="flex justify-between items-center">
|
||||
<h3 class="text-xs font-semibold">{title || 'Transcript'}</h3>
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { formatDistance } from 'date-fns';
|
||||
import type { PageData } from './$types';
|
||||
import { Paginator } from '@skeletonlabs/skeleton'
|
||||
// import { Paginator } from '@skeletonlabs/skeleton'
|
||||
// import Spinner from '$lib/components/ui/spinner/spinner.svelte';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
$: posts = data.posts;
|
||||
let visible: boolean = true;
|
||||
let message: string = "No posts found";
|
||||
</script>
|
||||
|
||||
<div class="container py-12">
|
||||
|
||||
Reference in New Issue
Block a user