set up services section, get projects working again, set up netlify config

This commit is contained in:
Silas 2024-06-03 23:42:28 -04:00
parent 2faf292aab
commit ddda98996b
Failed to generate hash of commit
18 changed files with 434 additions and 3423 deletions

1
.gitignore vendored
View File

@ -8,3 +8,4 @@ node_modules
!.env.example !.env.example
vite.config.js.timestamp-* vite.config.js.timestamp-*
vite.config.ts.timestamp-* vite.config.ts.timestamp-*
.vercel

View File

@ -36,3 +36,13 @@ npm run build
You can preview the production build with `npm run preview`. You can preview the production build with `npm run preview`.
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment. > To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
## Generate Embeddings
To create vector embeddings for searching through Markdown posts, simply run:
```
npm run generate-embeddings
```
It will traverse through every `*.md` under `src/posts/poetry` and generate the embeddings. You would then place the `embeddings.json` in the `src/lib/utils/poetry` to let the site run semantic search queries against them.

3
netlify.toml Normal file
View File

@ -0,0 +1,3 @@
[build]
command = "npm run build"
publish = "build"

3427
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -16,10 +16,12 @@
}, },
"devDependencies": { "devDependencies": {
"@sveltejs/adapter-auto": "^3.0.0", "@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/adapter-netlify": "^4.2.0",
"@sveltejs/kit": "^2.0.0", "@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^3.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0",
"@tailwindcss/typography": "^0.5.13", "@tailwindcss/typography": "^0.5.13",
"@tensorflow-models/universal-sentence-encoder": "^1.3.3", "@tensorflow-models/universal-sentence-encoder": "^1.3.3",
"@tensorflow/tfjs": "^4.19.0",
"@tensorflow/tfjs-node": "^4.19.0", "@tensorflow/tfjs-node": "^4.19.0",
"@theatre/core": "^0.7.1", "@theatre/core": "^0.7.1",
"@theatre/studio": "^0.7.1", "@theatre/studio": "^0.7.1",
@ -35,6 +37,7 @@
"eslint-plugin-svelte": "^2.35.1", "eslint-plugin-svelte": "^2.35.1",
"fs-extra": "^11.2.0", "fs-extra": "^11.2.0",
"marked": "^12.0.2", "marked": "^12.0.2",
"mdsvex": "^0.11.0",
"postcss": "^8.4.38", "postcss": "^8.4.38",
"prettier": "^3.1.1", "prettier": "^3.1.1",
"prettier-plugin-svelte": "^3.1.2", "prettier-plugin-svelte": "^3.1.2",
@ -49,12 +52,9 @@
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@dimforge/rapier3d-compat": "^0.11.2", "@dimforge/rapier3d-compat": "^0.11.2",
"@tensorflow/tfjs": "^4.19.0",
"@threlte/core": "^7.3.0", "@threlte/core": "^7.3.0",
"@threlte/extras": "^8.11.2", "@threlte/extras": "^8.11.2",
"@threlte/rapier": "^2.0.0", "@threlte/rapier": "^2.0.0",
"mdsvex": "^0.11.0", "three": "^0.159.0"
"three": "^0.159.0",
"three-inspect": "^0.4.5"
} }
} }

View File

@ -0,0 +1,83 @@
<script lang="ts">
import type { SearchResult } from '$lib/utils/search';
import { searchResults } from '$lib/store';
let searchQuery = '';
async function handleSearch() {
// const section = window.location.pathname.split('/')[1];
const response = await fetch(`/api/poetry/search?q=${encodeURIComponent(searchQuery)}`);
if (response.ok) {
const data: SearchResult[] = await response.json();
searchResults.set(data);
} else {
console.error('Failed to fetch search results');
searchResults.set([]);
}
}
</script>
<div class="flex flex-col h-screen">
<div class="navbar bg-base-300 sticky top-0 z-50">
<div class="navbar-start">
<div class="dropdown">
<div tabindex="-1" class="btn btn-ghost text-primary lg:hidden">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 6h16M4 12h8m-8 6h16"
/>
</svg>
</div>
<ul
tabindex="-1"
class="menu menu-sm dropdown-content mt-3 z-10 p-2 shadow bg-base-300 rounded-box w-52"
>
<li><a href="/thoughts">Thoughts</a></li>
<li><a href="/poetry">Poetry</a></li>
<li><a href="/projects">Projects</a></li>
<li><a href="/services">Services</a></li>
</ul>
</div>
<a class="link-primary text-xl" href="/">silentsilas</a>
</div>
<div class="navbar-end lg:hidden">
<div class="form-control">
<input
type="text"
placeholder="Search"
class="input input-bordered md:w-auto"
bind:value={searchQuery}
on:input={handleSearch}
/>
</div>
</div>
<div class="navbar-end hidden lg:flex">
<div class="form-control">
<input
type="text"
placeholder="Search"
class="input input-bordered md:w-auto"
bind:value={searchQuery}
on:input={handleSearch}
/>
</div>
<ul class="menu menu-horizontal px-1">
<li><a href="/thoughts">Thoughts</a></li>
<li><a href="/poetry">Poetry</a></li>
<li><a href="/projects">Projects</a></li>
<li><a href="/services">Services</a></li>
</ul>
</div>
</div>
<slot />
</div>

View File

@ -1,7 +1,6 @@
<script lang="ts"> <script lang="ts">
import { T, useTask, useThrelte } from '@threlte/core'; import { T, useTask, useThrelte } from '@threlte/core';
import { World } from '@threlte/rapier'; import { World } from '@threlte/rapier';
import { Inspector } from 'three-inspect';
import { dev } from '$app/environment'; import { dev } from '$app/environment';
import SpaceSkysphere from './SpaceSkysphere.svelte'; import SpaceSkysphere from './SpaceSkysphere.svelte';
import { Group, type Object3DEventMap } from 'three'; import { Group, type Object3DEventMap } from 'three';
@ -35,7 +34,3 @@
<slot /> <slot />
</T.Group> </T.Group>
</World> </World>
{#if false}
<Inspector />
{/if}

View File

@ -17,6 +17,8 @@
clickHandler(); clickHandler();
} }
}; };
const isExternal = href.startsWith('http');
</script> </script>
<HTML position.x={position[0]} position.y={position[1]} position.z={position[2]}> <HTML position.x={position[0]} position.y={position[1]} position.z={position[2]}>
@ -33,6 +35,7 @@
isPointerDown = false; isPointerDown = false;
isHovering = false; isHovering = false;
}} }}
target={isExternal ? '_blank' : ''}
on:click={onClick} on:click={onClick}
class="bg-green-700 px-3 py-3 text-white opacity-50 hover:opacity-90 active:opacity-100" class="bg-green-700 px-3 py-3 text-white opacity-50 hover:opacity-90 active:opacity-100"
style="transform: translate(-50%, 50%); display: block;" style="transform: translate(-50%, 50%); display: block;"

View File

@ -0,0 +1,17 @@
<div class="overlay container" id="overlay">
<slot name="overlay" />
</div>
<style>
.overlay {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background: rgba(16, 56, 30, 0.9);
}
</style>

View File

@ -1,90 +1,12 @@
<script lang="ts"> <script lang="ts">
import '../../app.css'; import '../../app.css';
import { searchResults } from '$lib/store';
import type { SearchResult } from '$lib/utils/search';
import SearchResults from '$lib/components/SearchResults.svelte'; import SearchResults from '$lib/components/SearchResults.svelte';
import NavBar from '$lib/components/NavBar.svelte';
let searchQuery = '';
async function handleSearch() {
const section = window.location.pathname.split('/')[1];
const response = await fetch(`/api/${section}/search?q=${encodeURIComponent(searchQuery)}`);
if (response.ok) {
const data: SearchResult[] = await response.json();
searchResults.set(data);
} else {
console.error('Failed to fetch search results');
searchResults.set([]);
}
}
</script> </script>
<div class="flex flex-col h-screen"> <NavBar>
<div class="navbar bg-base-300 sticky top-0 z-50">
<div class="navbar-start">
<div class="dropdown">
<div tabindex="-1" class="btn btn-ghost text-primary lg:hidden">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 6h16M4 12h8m-8 6h16"
/>
</svg>
</div>
<ul
tabindex="-1"
class="menu menu-sm dropdown-content mt-3 z-10 p-2 shadow bg-base-300 rounded-box w-52"
>
<li><a href="/thoughts">Thoughts</a></li>
<li><a href="/poetry">Poetry</a></li>
<li><a href="/projects">Projects</a></li>
<li><a href="/services">Services</a></li>
<li><a href="/experiments">Experiments</a></li>
</ul>
</div>
<a class="link-primary text-xl" href="/">silentsilas</a>
</div>
<div class="navbar-end lg:hidden">
<div class="form-control">
<input
type="text"
placeholder="Search"
class="input input-bordered md:w-auto"
bind:value={searchQuery}
on:input={handleSearch}
/>
</div>
</div>
<div class="navbar-end hidden lg:flex">
<div class="form-control">
<input
type="text"
placeholder="Search"
class="input input-bordered md:w-auto"
bind:value={searchQuery}
on:input={handleSearch}
/>
</div>
<ul class="menu menu-horizontal px-1">
<li><a href="/thoughts">Thoughts</a></li>
<li><a href="/poetry">Poetry</a></li>
<li><a href="/projects">Projects</a></li>
<li><a href="/services">Services</a></li>
<li><a href="/experiments">Experiments</a></li>
</ul>
</div>
</div>
<div class="flex flex-col items-center flex-1 overflow-auto"> <div class="flex flex-col items-center flex-1 overflow-auto">
<slot /> <slot />
<SearchResults /> <SearchResults />
</div> </div>
</div> </NavBar>

View File

@ -0,0 +1,75 @@
<script lang="ts">
import type { SearchResult } from '$lib/utils/search';
import { searchResults } from '$lib/store';
let results: SearchResult[] = [];
searchResults.subscribe((value: SearchResult[]) => {
results = value ? value : [];
});
const selfhostedServices = [
{
title: 'Nextcloud',
url: 'https://box.silentsilas.com',
description:
"A file hosting service that I use as an alternative to Dropbox and Google/Apple photos. It's a bit slow as it's running on a cheap DigitalOcean VPS, but I can provision an account with 10GB of storage for you if you'd like."
},
{
title: 'PrivateBin',
url: 'https://bin.silentsilas.com',
description:
"A pastebin service, useful for sending quick text snippets or sharing sensitive data with Burn After Reading. It doesn't require an account, but I do log the IP addresses of visitors for security purposes. None of the data entered is stored on the server thanks to E2E encryption."
},
{
title: 'FreshRSS',
url: 'https://rss.silentsilas.com',
description:
"A self-hosted RSS reader that I use to keep up with blogs, podcasts, and news sites. I can provision an account for you if you want to try it out. You would use an RSS client on your devices and have them subscribe to your FreshRSS URL. You would then add all of the RSS feeds you'd like to follow via the FreshRSS web interface and all of your RSS clients will then pull the articles for you to read."
},
{
title: 'The Lounge',
url: 'https://irc.silentsilas.com',
description:
"An IRC client that I use to connect to the Libera network and chat with other developers. Holler if you'd like an account provisioned for you. Logs are kept for preserving chat history, but I do not check the logs unless I have reason to believe that the service is being abused (or local authorities ask me to)."
},
{
title: 'Gitea',
url: 'https://git.silentsilas.com',
description:
"A self-hosted Git service that I use to host my personal projects. I can provision an account for you if you'd like to host your own projects."
},
{
title: 'Jellyfin',
url: 'https://jellyfin.silentsilas.com',
description:
'A self-hosted media server that I use to stream movies, shows, and music to my devices. It may or may not be available, as its dependent on the availability of my home PC.'
}
];
</script>
{#if results.length <= 0}
<div class="container mx-auto flex flex-col items-center">
<div class="prose">
<h1 class="py-6 pt-10">Services</h1>
<p>
I self-host a lot of services I find useful, and to rely less on third-party cloud services.
None of them run any analytics or log your activity, but the software/servers may be
outdated, so use at your own risk.
</p>
<ul>
{#each selfhostedServices as service}
<li>
<h3>
<a class="link" href={service.url} target="_blank">
{service.title}
</a>
</h3>
<p class="text-sm">{service.description}</p>
</li>
{/each}
</ul>
</div>
</div>
{/if}

View File

@ -1,2 +1,2 @@
export const prerender = true export const prerender = false;
export const ssr = true export const ssr = false;

View File

@ -1,42 +1,9 @@
<script lang="ts"> <script lang="ts">
import App from '$lib/components/scenes/app/App.svelte'; import NavBar from '$lib/components/NavBar.svelte';
import CanvasContainer from '$lib/components/scenes/app/CanvasContainer.svelte'; import SearchResults from '$lib/components/SearchResults.svelte';
import '../../app.css'
</script> </script>
<CanvasContainer> <NavBar>
<App>
<slot /> <slot />
</App> <SearchResults />
</NavBar>
</CanvasContainer>
<div class="overlay container" id="overlay"></div>
<style>
:global(body) {
margin: 0;
}
.canvas {
width: 100%;
height: 100%;
background: rgb(0, 36, 6);
background: linear-gradient(180deg, rgba(0, 36, 6, 1) 0%, rgba(0, 0, 0, 1) 100%);
position: absolute;
justify-content: center;
align-items: center;
}
.overlay {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background: rgba(16, 56, 30, 0.9);
}
</style>

View File

@ -1,2 +1,2 @@
export const prerender = true export const prerender = false;
export const ssr = false export const ssr = false;

View File

@ -1,37 +1,60 @@
<script lang="ts"> <script lang="ts">
import MenuItem from '$lib/components/scenes/app/MenuItem.svelte'; import MenuItem from '$lib/components/scenes/app/MenuItem.svelte';
import type { PageData } from './$types'; import App from '$lib/components/scenes/app/App.svelte';
export let data: PageData; import CanvasContainer from '$lib/components/scenes/app/CanvasContainer.svelte';
import '../../app.css';
const xMin = -2.5, import type { SearchResult } from '$lib/utils/search';
xMax = 2.5; import { searchResults } from '$lib/store';
const yMin = -7.5,
yMax = 7.5;
const zMin = -2.5,
zMax = 2.5;
const calculatePositions = (numPosts: number): Array<[number, number, number]> => { let results: SearchResult[] = [];
const positions: Array<[number, number, number]> = [];
const numRows = Math.ceil(Math.sqrt(numPosts));
const numCols = Math.ceil(numPosts / numRows);
for (let i = 0; i < numPosts; i++) { searchResults.subscribe((value: SearchResult[]) => {
const row = Math.floor(i / numCols); results = value ? value : [];
const col = i % numCols; });
const x = xMin + (xMax - xMin) * (col / (numCols - 1)); type Project = {
const y = yMin + (yMax - yMin) * (row / (numRows - 1)); title: string;
const z = zMin; path: string;
position: [number, number, number];
positions.push([x, y, z]);
}
return positions;
}; };
const positions = calculatePositions(data.posts.length); const projects: Project[] = [
{
title: 'Visions',
path: 'https://visions.silentsilas.com',
position: [0, -4, 0]
},
{
title: 'Animation Editor',
path: '/editor',
position: [0, 4, 0]
}
];
</script> </script>
{#each data.posts as post, i} {#if results.length <= 0}
<MenuItem position={positions[i]} htmlContent={post.meta.title} href={post.path} /> <CanvasContainer>
{/each} <App>
{#each projects as project, i}
<MenuItem position={project.position} htmlContent={project.title} href={project.path} />
{/each}
</App>
</CanvasContainer>
{/if}
<style>
:global(body) {
margin: 0;
}
.canvas {
width: 100%;
height: 100%;
background: rgb(0, 36, 6);
background: linear-gradient(180deg, rgba(0, 36, 6, 1) 0%, rgba(0, 0, 0, 1) 100%);
position: absolute;
justify-content: center;
align-items: center;
}
</style>

View File

@ -1,8 +0,0 @@
export const load = async ({ fetch }) => {
const response = await fetch(`/api/poetry?limit=5&offset=0`);
const posts = await response.json();
return {
posts
};
};

Binary file not shown.

View File

@ -1,6 +1,7 @@
import adapter from '@sveltejs/adapter-auto'; // import adapter from '@sveltejs/adapter-auto';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
import { mdsvex } from 'mdsvex'; import { mdsvex } from 'mdsvex';
import adapter from '@sveltejs/adapter-netlify';
/** @type {import('@sveltejs/kit').Config} */ /** @type {import('@sveltejs/kit').Config} */
const config = { const config = {