🪻 distributed transcription service thistle.dunkirk.sh
TypeScript 89.3%
HTML 9.9%
CSS 0.8%
52 1 1

Clone this repository

https://tangled.org/dunkirk.sh/thistle
git@knot.dunkirk.sh:dunkirk.sh/thistle

For self-hosted knots, clone URLs may differ based on your setup.

README.md

Thistle#

IMPORTANT

This is crazy pre-alpha and is changing really rapidly. Stuff should stabilize eventually but probably not for a month or two.

.
├── public
├── src
│   ├── components
│   ├── pages
│   └── styles
└── whisper-server
    ├── main.py
    ├── requirements.txt
    └── README.md

9 directories, 3 files

What's this?#

Thistle is a transcription service I'm building for Cedarville's startup competition! I'm also using it as an opportunity to become more familar with web components and full stack applications.

How do I hack on it?#

Development#

I'm just running this locally for now but getting started is super straightforward.

bun install
bun dev

Your server will be running at http://localhost:3000 with hot module reloading. Just edit any .ts, .html, or .css file and watch it update in the browser.

Transcription Service#

Thistle requires a separate Whisper transcription server for audio processing. Set it up in the whisper-server/ directory:

cd whisper-server
./run.sh

Or manually:

cd whisper-server
pip install -r requirements.txt
python main.py

The Whisper server will run on http://localhost:8000. Make sure it's running before using transcription features.

Environment Setup#

Copy .env.example to .env and configure:

cp .env.example .env
# Edit .env to set WHISPER_SERVICE_URL=http://localhost:8000

The tech stack is pretty minimal on purpose. Lit components (~8-10KB gzipped) for things that need reactivity, vanilla JS for simple stuff, and CSS variables for theming. The goal is to keep the total JS bundle as small as possible.

How does it work?#

The development flow is really nice in my opinion. The server imports HTML files as route handlers. Those HTML files import TypeScript components using <script type="module">. The components are just Lit web components that self-register as custom elements. Bun sees all this and bundles everything automatically including linked images or assets from the public directory.

// src/index.ts - Server imports HTML as routes
import indexHTML from "./pages/index.html";

Bun.serve({
  port: 3000,
  routes: {
    "/": indexHTML,
  },
  development: {
    hmr: true,
    console: true,
  },
});
<!-- src/pages/index.html -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <link rel="stylesheet" href="../styles/main.css" />
  </head>
  <body>
    <counter-component></counter-component>
    <script type="module" src="../components/counter.ts"></script>
  </body>
</html>
// src/components/counter.ts
import { LitElement, html, css } from "lit";
import { customElement, property } from "lit/decorators.js";

@customElement("counter-component")
export class CounterComponent extends LitElement {
  @property({ type: Number }) count = 0;

  static styles = css`
    :host {
      display: block;
      padding: 1rem;
    }
  `;

  render() {
    return html`
      <div>${this.count}</div>
      <button @click=${() => this.count++}>+</button>
    `;
  }
}

Oh last two points. Please please please use standard commits for my sanity and report any issues to the tangled repo

© 2025-present Kieran Klukas