House Keeping: Fixing Indentation

This commit is contained in:
John
2024-12-30 09:52:07 -05:00
parent 5da749f994
commit 2d3ebcd09c
32 changed files with 919 additions and 713 deletions

View File

@@ -4,52 +4,25 @@
import ModelConfig from "./ModelConfig.svelte";
import Models from "./Models.svelte";
import Patterns from "./Patterns.svelte";
//import NoteDrawer from '$lib/components/ui/noteDrawer/NoteDrawer.svelte';
//import { getDrawerStore } from '@skeletonlabs/skeleton';
//import { Button } from '$lib/components/ui/button';
//import { page } from '$app/stores';
//import { beforeNavigate } from '$app/navigation';
//
//const drawerStore = getDrawerStore();
//function openDrawer() {
// drawerStore.open({});
//}
//
//beforeNavigate(() => {
// drawerStore.close();
//});
//
//$: isVisible = $page.url.pathname.startsWith('/chat');
</script>
<div content="width=device-width, height=device-height, initial-scale=1.0">
<div class="h-screen overflow-auto">
<div class="h-full flex flex-col">
<div class="flex-1 overflow-hidden mx-auto p-2">
<!-- <div class="flex flex-col columns-3 m-2 p-1 h-screen"> -->
<div class="h-full flex gap-2">
<aside class="w-1/5 overflow-y-auto">
<!-- <div class="space-y-2"> -->
<!-- <div class="flex flex-col gap-2"> -->
<Patterns />
<Models />
<ModelConfig />
<!-- </div> -->
<!-- </div> -->
</aside>
<div class="w-1/2">
<!-- <div class="flex flex-col space-y-4 order-2 lg:order-2 h-screen"> -->
<ChatInput />
</div>
<br>
<div class="w-1/2">
<!-- <div class="flex flex-col rounded-lg order-1 lg:order-3 h-screen w-full"> -->
<ChatMessages />
</div>
<div class="mt-2 flex h-screen w-full overflow-hidden">
<div class="flex gap-4 p-2 w-full">
<aside class="w-1/5">
<div class="flex flex-col gap-2">
<Patterns />
<Models />
<ModelConfig />
</div>
<!-- </div> -->
</aside>
<div class="w-1/2">
<ChatInput />
</div>
<div class="w-1/2">
<ChatMessages />
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,32 @@
<div class="flex flex-col h-screen" style="width=device-width, height=device-height, initial-scale=2.0">
<div class="h-screen overflow-auto">
<div class="h-full flex flex-col">
<div class="flex-1 overflow-hidden mx-auto p-2">
<!-- <div class="flex flex-col columns-3 m-2 p-1 h-screen"> -->
<div class="h-full flex gap-2">
<aside class="w-1/5 overflow-y-auto">
<!-- <div class="space-y-2"> -->
<!-- <div class="flex flex-col gap-2"> -->
<Patterns />
<Models />
<ModelConfig />
<!-- </div> -->
<!-- </div> -->
</aside>
<div class="w-1/2">
<!-- <div class="flex flex-col space-y-4 order-2 lg:order-2 h-screen"> -->
<ChatInput />
</div>
<br>
<div class="w-1/2">
<!-- <div class="flex flex-col rounded-lg order-1 lg:order-3 h-screen w-full"> -->
<ChatMessages />
</div>
</div>
<!-- </div> -->
</div>
</div>
</div>
</div>

View File

@@ -1,48 +1,97 @@
<script lang="ts">
import { Button } from "$lib/components/ui/button";
import { Textarea } from "$lib/components/ui/textarea";
import { sendMessage, messageStore } from '$lib/store/chat';
import { systemPrompt } from '$lib/store/pattern-store';
import { getToastStore } from '@skeletonlabs/skeleton';
import { FileButton } from '@skeletonlabs/skeleton';
import { Paperclip, Send } from 'lucide-svelte';
import { onMount } from 'svelte';
import { Button } from "$lib/components/ui/button";
import { Textarea } from "$lib/components/ui/textarea";
import { sendMessage, messageStore } from '$lib/store/chat-store';
import { systemPrompt } from '$lib/store/pattern-store';
import { getToastStore } from '@skeletonlabs/skeleton';
import { FileButton } from '@skeletonlabs/skeleton';
import { Paperclip, Send, FileCheck } from 'lucide-svelte';
import { onMount } from 'svelte';
let userInput = "";
let files: FileList;
const toastStore = getToastStore();
let userInput = "";
//let files: FileList;
const toastStore = getToastStore();
async function handleSubmit() {
if (!userInput.trim()) return;
let files: File[] = [];
let uploadedFiles: string[] = [];
let fileContents: string[] = [];
let isProcessingFiles = false;
try {
const trimmedInput = userInput.trim();
const trimmedSystemPrompt = $systemPrompt.trim();
// Clear input before sending to improve perceived performance
userInput = "";
await sendMessage(trimmedSystemPrompt + '\n' + trimmedInput);
} catch (error) {
console.error('Chat submission error:', error);
toastStore.trigger({
message: 'Failed to send message. Please try again.',
background: 'variant-filled-error'
});
}
async function handleFileUpload(e: Event) {
if (!files || files.length === 0) return;
if (uploadedFiles.length >= 5 || (uploadedFiles.length + files.length) > 5) {
toastStore.error('Maximum 5 files allowed');
return;
}
// Handle keyboard shortcuts
function handleKeydown(event: KeyboardEvent) {
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault();
handleSubmit();
}
isProcessingFiles = true;
try {
for (let i = 0; i < files.length && uploadedFiles.length < 5; i++) {
const file = files[i];
const content = await readFileContent(file);
fileContents.push(content);
uploadedFiles = [...uploadedFiles, file.name];
}
} catch (error) {
toastStore.error('Error processing files: ' + error.message);
} finally {
isProcessingFiles = false;
}
}
onMount(() => {
console.log('ChatInput mounted, current system prompt:', $systemPrompt);
function readFileContent(file: File): Promise<string> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (e) => resolve(e.target?.result as string);
reader.onerror = (e) => reject(new Error('Failed to read file'));
reader.readAsText(file);
});
}
async function handleSubmit() {
if (!userInput.trim()) return;
try {
let finalContent = "";
if (fileContents.length > 0) {
finalContent += '\n\nFile Contents:\n' + fileContents.map((content, index) =>
`[${uploadedFiles[index]}]:\n${content}`
).join('\n\n');
}
const trimmedInput = userInput.trim() + '\n' + (finalContent || '');
const trimmedSystemPrompt = $systemPrompt.trim();
let messageHistory = JSON.stringify($messageStore);
$systemPrompt = "";
userInput = "";
uploadedFiles = [];
fileContents = [];
// Place the messageHistory in the sendMessage(``) function to send the message history
// This is a WIP and temporarily disabled.
await sendMessage(`${trimmedSystemPrompt}\n${trimmedInput}\n${finalContent}`);
} catch (error) {
console.error('Chat submission error:', error);
toastStore.trigger({
message: 'Failed to send message. Please try again.',
background: 'variant-filled-error'
});
}
}
// Handle keyboard shortcuts
function handleKeydown(event: KeyboardEvent) {
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault();
handleSubmit();
}
}
onMount(() => {
console.log('ChatInput mounted, current system prompt:', $systemPrompt);
});
</script>
<div class="h-full">
@@ -68,17 +117,30 @@
<FileButton
name="file-upload"
button="btn btn-sm variant-soft-surface"
bind:files={files}
on:change={(e) => {
// Workin on the file selection
// Check out `https://www.skeleton.dev/components/file-buttons` for more info
// Check 24-12-08 for half-baked implementation
}}
button="btn variant-default"
bind:files
on:change={handleFileUpload}
disabled={isProcessingFiles || uploadedFiles.length >= 5}
>
<Paperclip class="w-4" />
{#if uploadedFiles.length > 0}
<FileCheck class="w-4 h-4" />
{:else}
<Paperclip class="w-4 h-4" />
{/if}
</FileButton>
<Button type="button" name="submit" variant="secondary" on:click={handleSubmit}>
{#if uploadedFiles.length > 0}
<span class="text-sm text-gray-500 space-x-2">
{uploadedFiles.length} file{uploadedFiles.length > 1 ? 's' : ''} attached
</span>
{/if}
<br>
<Button
type="button"
variant="default"
name="send"
on:click={handleSubmit}
disabled={isProcessingFiles || !userInput.trim()}
>
<Send class="w-4 h-4" />
</Button>
</div>
@@ -87,7 +149,7 @@
</div>
<style>
.flex-col {
min-height: 0;
}
.flex-col {
min-height: 0;
}
</style>

View File

@@ -1,33 +1,34 @@
<script lang="ts">
import { chatState, errorStore, streamingStore } from '$lib/store/chat';
import { afterUpdate } from 'svelte';
import { toastStore } from '$lib/store/toast-store';
import { marked } from 'marked';
import SessionManager from './SessionManager.svelte';
import { fade, slide } from 'svelte/transition';
import { chatState, errorStore, streamingStore } from '$lib/store/chat-store';
import { afterUpdate } from 'svelte';
import { toastStore } from '$lib/store/toast-store';
import { marked } from 'marked';
import SessionManager from './SessionManager.svelte';
import { fade, slide } from 'svelte/transition';
let messagesContainer: HTMLDivElement;
let messagesContainer: HTMLDivElement;
afterUpdate(() => {
if (messagesContainer) {
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
});
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;
}
afterUpdate(() => {
if (messagesContainer) {
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
});
marked.setOptions({
gfm: true,
breaks: true,
});
function renderMarkdown(content: string, isAssistant: boolean) {
content = content.replace(/\\n/g, '\n');
if (!isAssistant) return content;
try {
return marked.parse(content);
} catch (error) {
console.error('Error rendering markdown:', error);
return content;
}
}
</script>
<div class="bg-primary-800/30 rounded-lg flex flex-col h-full shadow-lg">
@@ -47,7 +48,7 @@
{/if}
<!-- Check 24-12-08 chat-interface for more updates here -->
<div class="messages-container p-4 flex-1 overflow-y-auto max-h-dvh" bind:this={messagesContainer}>
<div class="messages-content flex flex-col gap-4">
{#each $chatState.messages as message}

View File

@@ -0,0 +1,153 @@
<script lang="ts">
import { chatState, errorStore, streamingStore } from '$lib/store/chat-store';
import { afterUpdate } from 'svelte';
import { toastStore } from '$lib/store/toast-store';
import { marked } from 'marked';
import SessionManager from './SessionManager.svelte';
import { fade, slide } from 'svelte/transition';
let messagesContainer: HTMLDivElement;
afterUpdate(() => {
if (messagesContainer) {
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
});
marked.setOptions({
gfm: true,
breaks: true,
});
function renderMarkdown(content: string, isAssistant: boolean) {
content = content.replace(/\\n/g, '\n');
if (!isAssistant) return content;
try {
return marked.parse(content);
} catch (error) {
console.error('Error rendering markdown:', error);
return content;
}
}
</script>
<div class="bg-primary-800/30 rounded-lg flex flex-col h-full shadow-lg">
<div class="flex justify-between items-center mb-1 mt-1 flex-none">
<div class="flex items-center gap-2 pl-4">
<b class="text-sm text-muted-foreground font-bold">Chat History</b>
</div>
<SessionManager />
</div>
{#if $errorStore}
<div class="error-message" transition:slide>
<div class="bg-red-100 border-l-4 border-red-500 text-red-700 p-4 mb-4" role="alert">
<p>{$errorStore}</p>
</div>
</div>
{/if}
<!-- Check 24-12-08 chat-interface for more updates here -->
<div class="messages-container p-4 flex-1 overflow-y-auto max-h-dvh" bind:this={messagesContainer}>
<div class="messages-content flex flex-col gap-4">
{#each $chatState.messages as message}
<div
class="message-item {message.role === 'assistant' ? 'pl-4 bg-primary/5 rounded-lg p-2' : 'pr-4 ml-auto'}"
transition:fade
>
<div class="message-header flex items-center gap-2 mb-1 {message.role === 'assistant' ? '' : 'justify-end'}">
<span class="text-xs text-muted-foreground rounded-lg p-1 variant-glass-secondary font-bold uppercase">
{message.role === 'assistant' ? 'AI' : 'You'}
</span>
{#if message.role === 'assistant' && $streamingStore}
<span class="loading-indicator flex gap-1">
<span class="dot animate-bounce">.</span>
<span class="dot animate-bounce delay-100">.</span>
<span class="dot animate-bounce delay-200">.</span>
</span>
{/if}
</div>
{#if message.role === 'assistant'}
<div class="prose prose-slate dark:prose-invert text-inherit prose-headings:text-inherit prose-pre:bg-primary/10 prose-pre:text-inherit text-sm max-w-none">
{@html renderMarkdown(message.content, true)}
</div>
{:else}
<div class="whitespace-pre-wrap text-sm">
{message.content}
</div>
{/if}
</div>
{/each}
</div>
</div>
</div>
<style>
/*.chat-messages-wrapper {*/
/* display: flex;*/
/* flex-direction: column;*/
/* min-height: 0;*/
/*}*/
.messages-container {
flex: 1;
overflow-y: auto;
scrollbar-width: thin;
-ms-overflow-style: thin;
}
.messages-content {
display: flex;
flex-direction: column;
gap: 2rem;
}
.message-header {
display: flex;
gap: 0.5rem;
}
.message-item {
position: relative;
}
.loading-indicator {
display: inline-flex;
gap: 2px;
}
.dot {
animation: blink 1.4s infinite;
opacity: 0;
}
.dot:nth-child(2) {
animation-delay: 0.2s;
}
.dot:nth-child(3) {
animation-delay: 0.4s;
}
@keyframes blink {
0%, 100% { opacity: 0; }
50% { opacity: 1; }
}
:global(.prose pre) {
background-color: rgb(40, 44, 52);
color: rgb(171, 178, 191);
padding: 1rem;
border-radius: 0.375rem;
margin: 1rem 0;
}
:global(.prose code) {
color: rgb(171, 178, 191);
background-color: rgba(40, 44, 52, 0.1);
padding: 0.2em 0.4em;
border-radius: 0.25rem;
}
</style>

View File

@@ -89,6 +89,4 @@
<NoteDrawer />
{/if}
</div>
</div>

View File

@@ -1,20 +1,20 @@
<script lang="ts">
import { onMount } from 'svelte';
import { Select } from "$lib/components/ui/select";
import { modelConfig, availableModels, loadAvailableModels } from "$lib/store/model-store";
import { onMount } from 'svelte';
import { Select } from "$lib/components/ui/select";
import { modelConfig, availableModels, loadAvailableModels } from "$lib/store/model-store";
onMount(async () => {
await loadAvailableModels();
});
onMount(async () => {
await loadAvailableModels();
});
</script>
<div class="min-w-0">
<Select
bind:value={$modelConfig.model}
>
<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>
{#each $availableModels as model (model.name)}
<option value={model.name}>{model.vendor} - {model.name}</option>
{/each}
</Select>
</div>

View File

@@ -16,12 +16,12 @@
</script>
<div class="min-w-0">
<Select
bind:value={selectedPreset}
>
<Select
bind:value={selectedPreset}
>
<option value="">Load a pattern...</option>
{#each $patterns as pattern}
<option value={pattern.Name}>{pattern.Description}</option>
{/each}
</Select>
{#each $patterns as pattern}
<option value={pattern.Name}>{pattern.Description}</option>
{/each}
</Select>
</div>

View File

@@ -1,136 +1,136 @@
<script lang='ts'>
import { getToastStore } from '@skeletonlabs/skeleton';
import { Button } from "$lib/components/ui/button";
import Input from '$lib/components/ui/input/input.svelte';
import { Toast } from '@skeletonlabs/skeleton';
import { getToastStore } from '@skeletonlabs/skeleton';
import { Button } from "$lib/components/ui/button";
import Input from '$lib/components/ui/input/input.svelte';
import { Toast } from '@skeletonlabs/skeleton';
let url = '';
let transcript = '';
let loading = false;
let error = '';
let title = '';
let url = '';
let transcript = '';
let loading = false;
let error = '';
let title = '';
const toastStore = getToastStore();
const toastStore = getToastStore();
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({
message: error,
background: 'variant-filled-error'
});
return;
}
loading = true;
error = '';
try {
const response = await fetch('/chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({ url })
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || 'Failed to fetch transcript');
}
const data = await response.json();
console.log('Parsed response data:', data);
transcript = data.transcript;
title = data.title;
} finally {
loading = false;
}
async function fetchTranscript() {
function isValidYouTubeUrl(url: string) {
const pattern = /^(https?:\/\/)?(www\.)?(youtube\.com|youtu\.be)\/.+$/;
return pattern.test(url);
}
async function copyToClipboard() {
try {
await navigator.clipboard.writeText(transcript);
toastStore.trigger({
message: 'Transcript copied to clipboard!',
background: 'variant-filled-success'
});
} catch (err) {
toastStore.trigger({
message: 'Failed to copy transcript',
background: 'variant-filled-error'
});
}
if (!isValidYouTubeUrl(url)) {
error = 'Please enter a valid YouTube URL';
toastStore.trigger({
message: error,
background: 'variant-filled-error'
});
return;
}
loading = true;
error = '';
try {
const response = await fetch('/chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({ url })
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || 'Failed to fetch transcript');
}
const data = await response.json();
console.log('Parsed response data:', data);
transcript = data.transcript;
title = data.title;
} finally {
loading = false;
}
}
async function copyToClipboard() {
try {
await navigator.clipboard.writeText(transcript);
toastStore.trigger({
message: 'Transcript copied to clipboard!',
background: 'variant-filled-success'
});
} catch (err) {
toastStore.trigger({
message: 'Failed to copy transcript',
background: 'variant-filled-error'
});
}
}
</script>
<div class="flex gap-2">
<Input
type="text"
bind:value={url}
placeholder="YouTube URL"
class="flex-1 rounded-full border bg-background px-4"
disabled={loading}
/>
<Button
variant="secondary"
on:click={fetchTranscript}
disabled={loading || !url}
>
<Input
type="text"
bind:value={url}
placeholder="YouTube URL"
class="flex-1 rounded-full border bg-background px-4"
disabled={loading}
/>
<Button
variant="secondary"
on:click={fetchTranscript}
disabled={loading || !url}
>
{#if loading}
<div class="spinner-border" />
<div class="spinner-border" />
{:else}
Get
Get
{/if}
</Button>
</Button>
</div>
{#if error}
<div class="bg-destructive/15 text-destructive rounded-lg p-2">{error}</div>
<div class="bg-destructive/15 text-destructive rounded-lg p-2">{error}</div>
{/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>
<Button
variant="outline"
size="sm"
on:click={copyToClipboard}
>
Copy to Clipboard
</Button>
</div>
<textarea
class="w-full text-xs rounded-md border bg-background px-3 py-2 resize-none h-72"
readonly
value={transcript}
></textarea>
<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>
<Button
variant="outline"
size="sm"
on:click={copyToClipboard}
>
Copy to Clipboard
</Button>
</div>
<textarea
class="w-full text-xs rounded-md border bg-background px-3 py-2 resize-none h-72"
readonly
value={transcript}
></textarea>
</div>
{/if}
<style>
.spinner-border {
width: 1rem;
height: 1rem;
border: 2px solid currentColor;
border-right-color: transparent;
border-radius: 50%;
animation: spin 0.75s linear infinite;
}
<style>
.spinner-border {
width: 1rem;
height: 1rem;
border: 2px solid currentColor;
border-right-color: transparent;
border-radius: 50%;
animation: spin 0.75s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
</style>
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
</style>

View File

@@ -1,26 +1,26 @@
<script lang="ts">
import { Contact } from 'lucide-svelte';
import { Contact } from 'lucide-svelte';
</script>
<div class="form-control w-full m-auto p-4 rounded-lg bg-gradient-to-br variant-gradient-success-warning shadow-lg text-current" title="contact form">
<h2 class="font-bold pl-2">We'd love to hear from you</h2>
<p class="font-bold pl-2">Email</p>
<div class="input-group input-group-divider grid-cols-[1fr_auto]">
<input type="text" placeholder="Enter an email address where you can be reached..." />
</div>
<p class="font-bold pl-2">Website</p>
<div class="input-group input-group-divider grid-cols-[auto_1fr_auto]">
<div class="input-group-shim">https://</div>
<input type="text" placeholder="www.example.com" />
</div>
<p class="font-bold pl-2">Contact Information</p>
<div class="input-group input-group-divider grid-cols-[1fr_auto]">
<input type="text" placeholder="Enter a other contact information here..." />
</div>
<label class="label">
<span class="font-bold pl-2">Message</span>
<textarea class="textarea" rows="4" placeholder="Enter your message ..." />
</label>
<a href="/" title=""><button class="button variant-filled-secondary rounded-lg p-2"><Contact /></button></a>
<h2 class="font-bold pl-2">We'd love to hear from you</h2>
<p class="font-bold pl-2">Email</p>
<div class="input-group input-group-divider grid-cols-[1fr_auto]">
<input type="text" placeholder="Enter an email address where you can be reached..." />
</div>
<p class="font-bold pl-2">Website</p>
<div class="input-group input-group-divider grid-cols-[auto_1fr_auto]">
<div class="input-group-shim">https://</div>
<input type="text" placeholder="www.example.com" />
</div>
<p class="font-bold pl-2">Contact Information</p>
<div class="input-group input-group-divider grid-cols-[1fr_auto]">
<input type="text" placeholder="Enter a other contact information here..." />
</div>
<label class="label">
<span class="font-bold pl-2">Message</span>
<textarea class="textarea" rows="4" placeholder="Enter your message ..." />
</label>
<a href="/" title=""><button class="button variant-filled-secondary rounded-lg p-2"><Contact /></button></a>
</div>

View File

@@ -1,52 +1,52 @@
<script lang="ts">
import { formatDistance } from 'date-fns';
import type { Post } from '$lib/interfaces/post-interface';
import PostMeta from './PostMeta.svelte';
import Card from '$lib/components/ui/cards/card.svelte';
import { cn } from '$lib/utils/utils';
import { formatDistance } from 'date-fns';
import type { Post } from './post-interface';
import PostMeta from './PostMeta.svelte';
import Card from '$lib/components/ui/cards/card.svelte';
import { cn } from '$lib/utils/utils';
export let post: Post;
export let className: string = '';
export let post: Post;
export let className: string = '';
function parseDate(dateStr: string): Date {
// Handle both ISO strings and YYYY-MM-DD formats
return new Date(dateStr);
}
function parseDate(dateStr: string): Date {
// Handle both ISO strings and YYYY-MM-DD formats
return new Date(dateStr);
}
</script>
<article class="card card-hover group relative rounded-lg border p-6 hover:bg-primary-500/50 {className}">
<a
href="/posts/{post.slug}"
class="absolute inset-0"
data-sveltekit-preload-data="off"
>
<span class="sr-only">View {post.metadata?.title}</span>
</a>
<div class="flex flex-col justify-between space-y-4">
<div class="space-y-2">
<img src={post.metadata?.images?.[0]} alt="" class="rounded-lg" />
<h2 class="text-xl font-semibold tracking-tight">{post.metadata?.title}</h2>
<p class="text-muted-foreground">{post.metadata?.description}</p>
</div>
<div class="flex items-center space-x-4 text-sm text-muted-foreground">
<time datetime={post.metadata?.date}>
{#if post.metadata?.date}
{formatDistance(parseDate(post.metadata.date), new Date(), { addSuffix: false })}
{/if}
</time>
{#if post.metadata?.tags?.length > 0}
<span class="text-xs"></span>
<div class="flex flex-wrap gap-2">
{#each post.metadata?.tags as tag}
<a
href="/tags/{tag}"
class="inline-flex items-center rounded-md border px-2 py-0.5 text-xs font-semibold transition-colors hover:bg-secondary"
>
{tag}
</a>
{/each}
</div>
{/if}
</div>
<a
href="/posts/{post.slug}"
class="absolute inset-0"
data-sveltekit-preload-data="off"
>
<span class="sr-only">View {post.metadata?.title}</span>
</a>
<div class="flex flex-col justify-between space-y-4">
<div class="space-y-2">
<img src={post.metadata?.images?.[0]} alt="" class="rounded-lg" />
<h2 class="text-xl font-semibold tracking-tight">{post.metadata?.title}</h2>
<p class="text-muted-foreground">{post.metadata?.description}</p>
</div>
<div class="flex items-center space-x-4 text-sm text-muted-foreground">
<time datetime={post.metadata?.date}>
{#if post.metadata?.date}
{formatDistance(parseDate(post.metadata.date), new Date(), { addSuffix: false })}
{/if}
</time>
{#if post.metadata?.tags?.length > 0}
<span class="text-xs"></span>
<div class="flex flex-wrap gap-2">
{#each post.metadata?.tags as tag}
<a
href="/tags/{tag}"
class="inline-flex items-center rounded-md border px-2 py-0.5 text-xs font-semibold transition-colors hover:bg-secondary"
>
{tag}
</a>
{/each}
</div>
{/if}
</div>
</div>
</article>

View File

@@ -1,37 +1,37 @@
<script lang="ts">
import PostMeta from './PostMeta.svelte';
import type { Post } from '$lib/interfaces/post-interface'
import Spinner from '$lib/components/ui/spinner/spinner.svelte';
import SideNav from '$lib/components/ui/side-nav/SideNav.svelte';
import PostMeta from './PostMeta.svelte';
import type { Post } from './post-interface'
import Spinner from '$lib/components/ui/spinner/spinner.svelte';
import SideNav from '$lib/components/ui/side-nav/SideNav.svelte';
export let post: Post;
export let post: Post;
</script>
<article class="py-6">
{#if !post?.content || !post?.metadata}
<div class="flex min-h-[400px] items-center justify-center">
<div class="flex items-center gap-2">
<Spinner class="h-6 w-6" />
<span class="text-sm text-muted-foreground">Loading post...</span>
</div>
{#if !post?.content || !post?.metadata}
<div class="flex min-h-[400px] items-center justify-center">
<div class="flex items-center gap-2">
<Spinner class="h-6 w-6" />
<span class="text-sm text-muted-foreground">Loading post...</span>
</div>
</div>
{:else}
<div class="space-y-4 pl-8 ml-8">
<h1 class="inline-block text-4xl font-bold inherit-colors lg:text-5xl">{post.metadata.title}</h1>
<PostMeta data={post.metadata} />
</div>
<div class="items-center py-8 mx-auto gap-8 max-w-7xl relative prose prose-slate dark:prose-invert">
{#if typeof post.content === 'function'}
<SideNav />
<svelte:component this={post.content} />
{:else if typeof post.content === 'string'}
{post.content}
{:else}
<div class="flex gap-2">
<Spinner class="h-8 w-8" />
<span class="text-sm text-muted-foreground">Loading content...</span>
</div>
{:else}
<div class="space-y-4 pl-8 ml-8">
<h1 class="inline-block text-4xl font-bold inherit-colors lg:text-5xl">{post.metadata.title}</h1>
<PostMeta data={post.metadata} />
</div>
<div class="items-center py-8 mx-auto gap-8 max-w-7xl relative prose prose-slate dark:prose-invert">
{#if typeof post.content === 'function'}
<SideNav />
<svelte:component this={post.content} />
{:else if typeof post.content === 'string'}
{post.content}
{:else}
<div class="flex gap-2">
<Spinner class="h-8 w-8" />
<span class="text-sm text-muted-foreground">Loading content...</span>
</div>
{/if}
</div>
{/if}
{/if}
</div>
{/if}
</article>

View File

@@ -1,58 +1,58 @@
<script>
export let aliases
export let date
export let tags
export let title
export let description
export let author
export let updated
//export let content
export let aliases
export let date
export let tags
export let title
export let description
export let author
export let updated
//export let content
</script>
<article class="prose prose-slate dark:prose-invert max-w-5xl flex-1">
<h1 class="inline-block text-4xl font-bold inherit-colors lg:text-5xl">{aliases}</h1>
<slot />
<h1 class="inline-block text-4xl font-bold inherit-colors lg:text-5xl">{aliases}</h1>
<slot />
</article>
<style lang="postcss">
:global(textarea) {
@apply textarea;
}
:global(textarea) {
@apply textarea;
}
:global(h1) {
@apply h1;
}
:global(h1) {
@apply h1;
}
:global(h2) {
@apply h2;
}
:global(h2) {
@apply h2;
}
:global(p) {
@layer p;
}
:global(p) {
@layer p;
}
:global(ul) {
@layer ul;
}
:global(ul) {
@layer ul;
}
:global(li) {
@layer li;
}
:global(li) {
@layer li;
}
:global(a) {
@layer a;
}
:global(a) {
@layer a;
}
:global(img) {
@layer img;
}
:global(img) {
@layer img;
}
:global(blockquote) {
@layer blockquote;
}
:global(blockquote) {
@layer blockquote;
}
:global(code) {
@layer code;
}
:global(code) {
@layer code;
}
</style>

View File

@@ -1,34 +1,34 @@
<script lang="ts">
import { formatDistance } from 'date-fns';
import type { PostMetadata } from '$lib/interfaces/post-interface';
import { formatDistance } from 'date-fns';
import type { PostMetadata } from './post-interface';
export let data: PostMetadata;
export let showUpdated = true;
export let data: PostMetadata;
export let showUpdated = true;
</script>
<div class="flex flex-col gap-2 text-sm text-muted-foreground">
<div class="flex items-center gap-2">
<span><b>Published on: </b>{data.date}</span>
{#if showUpdated && data.updated !== data.date}
<span>· Updated on {data.updated}</span>
{/if}
<div class="flex items-center gap-2">
<span><b>Published on: </b>{data.date}</span>
{#if showUpdated && data.updated !== data.date}
<span>· Updated on {data.updated}</span>
{/if}
</div>
{#if data.author}
<div class="text-xs font-bold">By {data.author}</div>
{/if}
{#if data.description}
<p class="text-base font-bold">{data.description}</p>
{/if}
{#if data.tags && data.tags.length > 0}
<div class="flex flex-wrap gap-2">
{#each data.tags as tag}
<a
href="/tags/{tag}"
class="inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors hover:bg-muted/50"
>
{tag}
</a>
{/each}
</div>
{#if data.author}
<div class="text-xs font-bold">By {data.author}</div>
{/if}
{#if data.description}
<p class="text-base font-bold">{data.description}</p>
{/if}
{#if data.tags && data.tags.length > 0}
<div class="flex flex-wrap gap-2">
{#each data.tags as tag}
<a
href="/tags/{tag}"
class="inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors hover:bg-muted/50"
>
{tag}
</a>
{/each}
</div>
{/if}
{/if}
</div>

View File

@@ -1,7 +1,7 @@
---
title: Extract Wisdom
date: 2024-01-01
description: Something here
description: Pattern - Extract Wisdom
updated:
aliases: Extract Wisdom
tags:

View File

@@ -3,15 +3,15 @@ import { toastStore } from '$lib/store/toast-store';
const toastStoreInstance = toastStore;
export const toastService = {
success(message: string) {
toastStoreInstance.success(message);
},
success(message: string) {
toastStoreInstance.success(message);
},
error(message: string) {
toastStoreInstance.error(message);
},
error(message: string) {
toastStoreInstance.error(message);
},
info(message: string) {
toastStoreInstance.info(message);
}
info(message: string) {
toastStoreInstance.info(message);
}
};

View File

@@ -1,38 +1,38 @@
import { YoutubeTranscript } from 'youtube-transcript';
export interface TranscriptResponse {
transcript: string;
title: string;
transcript: string;
title: string;
}
export async function getTranscript(url: string): Promise<TranscriptResponse> {
try {
const videoId = extractVideoId(url);
if (!videoId) {
throw new Error('Invalid YouTube URL');
}
const transcriptItems = await YoutubeTranscript.fetchTranscript(videoId);
const transcript = transcriptItems
.map(item => item.text)
.join(' ');
const transcriptTitle = transcriptItems
.map(item => item.text)
.join('');
// TODO: Add title fetching
return {
transcript,
title: videoId // Just returning the video ID as title
};
} catch (error) {
console.error('Transcript fetch error:', error);
throw new Error('Failed to fetch transcript');
try {
const videoId = extractVideoId(url);
if (!videoId) {
throw new Error('Invalid YouTube URL');
}
const transcriptItems = await YoutubeTranscript.fetchTranscript(videoId);
const transcript = transcriptItems
.map(item => item.text)
.join(' ');
const transcriptTitle = transcriptItems
.map(item => item.text)
.join('');
// TODO: Add title fetching
return {
transcript,
title: videoId // Just returning the video ID as title
};
} catch (error) {
console.error('Transcript fetch error:', error);
throw new Error('Failed to fetch transcript');
}
}
function extractVideoId(url: string): string | null {
const match = url.match(/(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/);
return match ? match[1] : null;
}
const match = url.match(/(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/);
return match ? match[1] : null;
}

View File

@@ -2,23 +2,23 @@ import { writable } from 'svelte/store';
import type { ChatConfig } from '$lib/types/interfaces/chat-interface';
const defaultConfig: ChatConfig = {
temperature: 0.7,
top_p: 1,
frequency_penalty: 0,
presence_penalty: 0
temperature: 0.7,
top_p: 1,
frequency_penalty: 0,
presence_penalty: 0
};
export const chatConfig = writable<ChatConfig>(defaultConfig);
export function updateConfig(newConfig: Partial<ChatConfig>): void {
chatConfig.update(config => ({
...config,
...newConfig
}));
chatConfig.update(config => ({
...config,
...newConfig
}));
}
export function resetConfig(): void {
chatConfig.set(defaultConfig);
chatConfig.set(defaultConfig);
}
export { type ChatConfig };

View File

@@ -6,63 +6,63 @@ export const patterns = writable<Pattern[]>([]);
export const systemPrompt = writable<string>('');
export const setSystemPrompt = (prompt: string) => {
console.log('Setting system prompt:', prompt);
systemPrompt.set(prompt);
console.log('Current system prompt:', get(systemPrompt));
console.log('Setting system prompt:', prompt);
systemPrompt.set(prompt);
console.log('Current system prompt:', get(systemPrompt));
};
export const patternAPI = {
...createStorageAPI<Pattern>('patterns'),
...createStorageAPI<Pattern>('patterns'),
async loadPatterns() {
async loadPatterns() {
try {
const response = await fetch(`/api/patterns/names`);
const data = await response.json();
console.log("Load Patterns:", data);
// Create an array of promises to fetch all pattern contents
const patternsPromises = data.map(async (pattern: string) => {
try {
const response = await fetch(`/api/patterns/names`);
const data = await response.json();
console.log("Load Patterns:", data);
// Create an array of promises to fetch all pattern contents
const patternsPromises = data.map(async (pattern: string) => {
try {
const patternResponse = await fetch(`/api/patterns/${pattern}`);
const patternData = await patternResponse.json();
return {
Name: pattern,
Description: pattern.charAt(0).toUpperCase() + pattern.slice(1),
Pattern: patternData.Pattern
};
} catch (error) {
console.error(`Failed to load pattern ${pattern}:`, error);
return {
Name: pattern,
Description: pattern.charAt(0).toUpperCase() + pattern.slice(1),
Pattern: ""
};
}
});
// Wait for all pattern contents to be fetched
const loadedPatterns = await Promise.all(patternsPromises);
console.log("Patterns with content:", loadedPatterns);
patterns.set(loadedPatterns);
return loadedPatterns;
const patternResponse = await fetch(`/api/patterns/${pattern}`);
const patternData = await patternResponse.json();
return {
Name: pattern,
Description: pattern.charAt(0).toUpperCase() + pattern.slice(1),
Pattern: patternData.Pattern
};
} catch (error) {
console.error('Failed to load patterns:', error);
patterns.set([]);
return [];
console.error(`Failed to load pattern ${pattern}:`, error);
return {
Name: pattern,
Description: pattern.charAt(0).toUpperCase() + pattern.slice(1),
Pattern: ""
};
}
},
});
selectPattern(patternName: string) {
const allPatterns = get(patterns);
console.log('Selecting pattern:', patternName);
const selectedPattern = allPatterns.find(p => p.Name === patternName);
if (selectedPattern) {
console.log('Found pattern content:', selectedPattern.Pattern);
setSystemPrompt(selectedPattern.Pattern.trim());
} else {
console.log('No pattern found for name:', patternName);
setSystemPrompt('');
}
console.log('System prompt store value after setting:', get(systemPrompt));
// Wait for all pattern contents to be fetched
const loadedPatterns = await Promise.all(patternsPromises);
console.log("Patterns with content:", loadedPatterns);
patterns.set(loadedPatterns);
return loadedPatterns;
} catch (error) {
console.error('Failed to load patterns:', error);
patterns.set([]);
return [];
}
};
},
selectPattern(patternName: string) {
const allPatterns = get(patterns);
console.log('Selecting pattern:', patternName);
const selectedPattern = allPatterns.find(p => p.Name === patternName);
if (selectedPattern) {
console.log('Found pattern content:', selectedPattern.Pattern);
setSystemPrompt(selectedPattern.Pattern.trim());
} else {
console.log('No pattern found for name:', patternName);
setSystemPrompt('');
}
console.log('System prompt store value after setting:', get(systemPrompt));
}
};

View File

@@ -8,79 +8,79 @@ import { toastService } from '../services/toast-service';
export const sessions = writable<Session[]>([]);
export const sessionAPI = {
...createStorageAPI<Session>('sessions'),
...createStorageAPI<Session>('sessions'),
async loadSessions() {
async loadSessions() {
try {
const response = await fetch(`/api/sessions/names`);
const sessionNames: string[] = await response.json();
const sessionPromises = sessionNames.map(async (name: string) => {
try {
const response = await fetch(`/api/sessions/names`);
const sessionNames: string[] = await response.json();
const sessionPromises = sessionNames.map(async (name: string) => {
try {
const response = await fetch(`/api/sessions/${name}`);
const data = await response.json();
return {
Name: name,
Message: Array.isArray(data.Message) ? data.Message : [],
Session: data.Session
};
} catch (error) {
console.error(`Error loading session ${name}:`, error);
return {
Name: name,
Message: [],
Session: ""
};
}
});
const sessionsData = await Promise.all(sessionPromises);
sessions.set(sessionsData);
return sessionsData;
const response = await fetch(`/api/sessions/${name}`);
const data = await response.json();
return {
Name: name,
Message: Array.isArray(data.Message) ? data.Message : [],
Session: data.Session
};
} catch (error) {
console.error('Error loading sessions:', error);
sessions.set([]);
return [];
console.error(`Error loading session ${name}:`, error);
return {
Name: name,
Message: [],
Session: ""
};
}
},
});
selectSession(sessionName: string) {
const allSessions = get(sessions);
const selectedSession = allSessions.find(session => session.Name === sessionName);
if (selectedSession) {
sessions.set([selectedSession]);
} else {
sessions.set([]);
}
},
async exportToFile(messages: Message[]) {
try {
await saveToFile(messages, 'session-history.json');
toastService.success('Session exported successfully');
} catch (error) {
toastService.error('Failed to export session');
throw error;
}
},
async importFromFile(): Promise<Message[]> {
try {
const file = await openFileDialog('.json');
if (!file) {
throw new Error('No file selected');
}
const content = await readFileAsJson<Message[]>(file);
if (!Array.isArray(content)) {
throw new Error('Invalid session file format');
}
toastService.success('Session imported successfully');
return content;
} catch (error) {
toastService.error(error instanceof Error ? error.message : 'Failed to import session');
throw error;
}
const sessionsData = await Promise.all(sessionPromises);
sessions.set(sessionsData);
return sessionsData;
} catch (error) {
console.error('Error loading sessions:', error);
sessions.set([]);
return [];
}
},
selectSession(sessionName: string) {
const allSessions = get(sessions);
const selectedSession = allSessions.find(session => session.Name === sessionName);
if (selectedSession) {
sessions.set([selectedSession]);
} else {
sessions.set([]);
}
},
async exportToFile(messages: Message[]) {
try {
await saveToFile(messages, 'session-history.json');
toastService.success('Session exported successfully');
} catch (error) {
toastService.error('Failed to export session');
throw error;
}
},
async importFromFile(): Promise<Message[]> {
try {
const file = await openFileDialog('.json');
if (!file) {
throw new Error('No file selected');
}
const content = await readFileAsJson<Message[]>(file);
if (!Array.isArray(content)) {
throw new Error('Invalid session file format');
}
toastService.success('Session imported successfully');
return content;
} catch (error) {
toastService.error(error instanceof Error ? error.message : 'Failed to import session');
throw error;
}
}
};

View File

@@ -1,30 +1,30 @@
import { writable } from 'svelte/store';
export interface ToastMessage {
message: string;
type: 'success' | 'error' | 'info';
id: number;
message: string;
type: 'success' | 'error' | 'info';
id: number;
}
function createToastStore() {
const { subscribe, update } = writable<ToastMessage[]>([]);
let nextId = 1;
const { subscribe, update } = writable<ToastMessage[]>([]);
let nextId = 1;
return {
subscribe,
success: (message: string) => {
update(toasts => [...toasts, { message, type: 'success', id: nextId++ }]);
},
error: (message: string) => {
update(toasts => [...toasts, { message, type: 'error', id: nextId++ }]);
},
info: (message: string) => {
update(toasts => [...toasts, { message, type: 'info', id: nextId++ }]);
},
remove: (id: number) => {
update(toasts => toasts.filter(t => t.id !== id));
}
};
return {
subscribe,
success: (message: string) => {
update(toasts => [...toasts, { message, type: 'success', id: nextId++ }]);
},
error: (message: string) => {
update(toasts => [...toasts, { message, type: 'error', id: nextId++ }]);
},
info: (message: string) => {
update(toasts => [...toasts, { message, type: 'info', id: nextId++ }]);
},
remove: (id: number) => {
update(toasts => toasts.filter(t => t.id !== id));
}
};
}
export const toastStore = createToastStore();

View File

@@ -3,46 +3,46 @@ export type ResponseFormat = 'markdown' | 'mermaid' | 'plain';
export type ResponseType = 'content' | 'error' | 'complete';
export interface ChatPrompt {
userInput: string;
systemPrompt: string;
model: string;
patternName: string;
userInput: string;
systemPrompt: string;
model: string;
patternName: string;
}
export interface ChatConfig {
temperature: number;
top_p: number;
frequency_penalty: number;
presence_penalty: number;
temperature: number;
top_p: number;
frequency_penalty: number;
presence_penalty: number;
}
export interface ChatRequest {
prompts: ChatPrompt[];
messages: Message[];
temperature: number;
top_p: number;
frequency_penalty: number;
presence_penalty: number;
prompts: ChatPrompt[];
messages: Message[];
temperature: number;
top_p: number;
frequency_penalty: number;
presence_penalty: number;
}
export interface Message {
role: MessageRole;
content: string;
role: MessageRole;
content: string;
}
export interface ChatState {
messages: Message[];
isStreaming: boolean;
messages: Message[];
isStreaming: boolean;
}
export interface StreamResponse {
type: ResponseType;
format: ResponseFormat;
content: string;
type: ResponseType;
format: ResponseFormat;
content: string;
}
export interface ChatError {
code: string;
message: string;
details?: unknown;
}
code: string;
message: string;
details?: unknown;
}

View File

@@ -1,4 +1,4 @@
export interface Context {
name: string;
content: string;
}
name: string;
content: string;
}

View File

@@ -1,8 +1,8 @@
export interface VendorModel {
name: string;
vendor: string;
name: string;
vendor: string;
}
export interface ModelsResponse {
models: string[];
vendors: Record<string, string[]>;
@@ -10,10 +10,10 @@ export interface ModelsResponse {
export interface ModelConfig {
model: string;
temperature: number;
top_p: number;
maxLength: number;
frequency: number;
presence: number;
model: string;
temperature: number;
top_p: number;
maxLength: number;
frequency: number;
presence: number;
}

View File

@@ -1,5 +1,5 @@
export interface Pattern {
Name: string;
Description: string;
Pattern: string; // | object
}
Name: string;
Description: string;
Pattern: string; // | object
}

View File

@@ -1,7 +1,7 @@
import type { Message } from "./chat-interface";
export interface Session {
Name: string;
Message: string | Message[];
Session: string | object;
Name: string;
Message: string | Message[];
Session: string | object;
}

View File

@@ -1,7 +1,7 @@
export interface StorageEntity {
Name: string;
Description?: string;
Pattern?: string | object;
Session?: string | object;
Context?: string | object;
}
Name: string;
Description?: string;
Pattern?: string | object;
Session?: string | object;
Context?: string | object;
}

View File

@@ -1,44 +1,44 @@
export async function openFileDialog(accept: string): Promise<File | null> {
return new Promise((resolve) => {
const input = document.createElement('input');
input.type = 'file';
input.accept = accept;
input.onchange = (event) => {
const file = (event.target as HTMLInputElement).files?.[0];
resolve(file || null);
};
input.click();
});
return new Promise((resolve) => {
const input = document.createElement('input');
input.type = 'file';
input.accept = accept;
input.onchange = (event) => {
const file = (event.target as HTMLInputElement).files?.[0];
resolve(file || null);
};
input.click();
});
}
export async function readFileAsJson<T>(file: File): Promise<T> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
try {
const data = JSON.parse(reader.result as string);
resolve(data);
} catch (error) {
reject(new Error('Invalid JSON format in file'));
}
};
reader.onerror = () => reject(new Error('Failed to read file'));
reader.readAsText(file);
});
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
try {
const data = JSON.parse(reader.result as string);
resolve(data);
} catch (error) {
reject(new Error('Invalid JSON format in file'));
}
};
reader.onerror = () => reject(new Error('Failed to read file'));
reader.readAsText(file);
});
}
export async function saveToFile(data: any, filename: string): Promise<void> {
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}

View File

@@ -1,4 +1,4 @@
export function validateYouTubeUrl(url:string) {
const pattern = /^(https?:\/\/)?(www\.)?(youtube\.com|youtu\.be)\/.+$/;
return pattern.test(url);
}
const pattern = /^(https?:\/\/)?(www\.)?(youtube\.com|youtu\.be)\/.+$/;
return pattern.test(url);
}

View File

@@ -1,20 +1,20 @@
<script>
import SideNav from '$lib/components/ui/side-nav/SideNav.svelte';
import Content from './README.md';
import SideNav from '$lib/components/ui/side-nav/SideNav.svelte';
import Content from './README.md';
</script>
{#if Content}
<div class="items-center mx-auto pt-8 grid-cols-[80%_20%] grid gap-8 max-w-7xl relative">
<svelte:component this={Content} />
<SideNav />
</div>
<div class="items-center mx-auto pt-8 grid-cols-[80%_20%] grid gap-8 max-w-7xl relative">
<svelte:component this={Content} />
<SideNav />
</div>
{:else}
<div class="container py-12">
<div class="container py-12">
<h1 class="mb-8 text-3xl font-bold">Sorry</h1>
<div class="flex min-h-[400px] items-center justify-center text-center">
<p class="text-lg font-medium">Nothing found</p>
<p class="mt-2 text-sm text-muted-foreground">Check back later for new content.</p>
<p class="text-lg font-medium">Nothing found</p>
<p class="mt-2 text-sm text-muted-foreground">Check back later for new content.</p>
</div>
</div>
</div>
{/if}

View File

@@ -3,19 +3,6 @@ import type { PageLoad } from './$types';
import type { Frontmatter } from '$lib/types/markdown';
const posts = import.meta.glob<{ metadata: Frontmatter, default: unknown }>('/src/lib/content/posts/*.{md,svx}', { eager: true });
// Refractor to use the Frontmatter type from $lib/types/markdown
//const posts = import.meta.glob<{
// metadata: {
// title: string;
// description: string;
// date: string;
// tags: string[];
// updated?: string;
// author?: string;
// aliases?: string[];
// };
// default: unknown;
//}>('/src/lib/content/posts/*.{md,svx}', { eager: true });
export const load: PageLoad = async ({ params }) => {
const post = Object.entries(posts).find(([path]) =>