🪻 distributed transcription service
thistle.dunkirk.sh
1# Thistle
2
3> [!IMPORTANT]
4> This is crazy pre-alpha and is changing really rapidly. Stuff should stabilize eventually but probably not for a month or two.
5
6```bash
7.
8├── public
9├── src
10│ ├── components
11│ ├── pages
12│ └── styles
13└── whisper-server
14 ├── main.py
15 ├── requirements.txt
16 └── README.md
17
189 directories, 3 files
19```
20
21## What's this?
22
23Thistle 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.
24
25## How do I hack on it?
26
27### Development
28
29I'm just running this locally for now but getting started is super straightforward.
30
31```bash
32bun install
33bun dev
34```
35
36Your 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.
37
38### Transcription Service
39
40Thistle requires a separate Whisper transcription server for audio processing. Set it up in the `whisper-server/` directory:
41
42```bash
43cd whisper-server
44./run.sh
45```
46
47Or manually:
48```bash
49cd whisper-server
50pip install -r requirements.txt
51python main.py
52```
53
54The Whisper server will run on `http://localhost:8000`. Make sure it's running before using transcription features.
55
56### Environment Setup
57
58Copy `.env.example` to `.env` and configure:
59
60```bash
61cp .env.example .env
62# Edit .env to set WHISPER_SERVICE_URL=http://localhost:8000
63```
64
65The 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.
66
67## How does it work?
68
69The 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.
70
71```typescript
72// src/index.ts - Server imports HTML as routes
73import indexHTML from "./pages/index.html";
74
75Bun.serve({
76 port: 3000,
77 routes: {
78 "/": indexHTML,
79 },
80 development: {
81 hmr: true,
82 console: true,
83 },
84});
85```
86
87```html
88<!-- src/pages/index.html -->
89<!DOCTYPE html>
90<html lang="en">
91 <head>
92 <link rel="stylesheet" href="../styles/main.css" />
93 </head>
94 <body>
95 <counter-component></counter-component>
96 <script type="module" src="../components/counter.ts"></script>
97 </body>
98</html>
99```
100
101```typescript
102// src/components/counter.ts
103import { LitElement, html, css } from "lit";
104import { customElement, property } from "lit/decorators.js";
105
106@customElement("counter-component")
107export class CounterComponent extends LitElement {
108 @property({ type: Number }) count = 0;
109
110 static styles = css`
111 :host {
112 display: block;
113 padding: 1rem;
114 }
115 `;
116
117 render() {
118 return html`
119 <div>${this.count}</div>
120 <button @click=${() => this.count++}>+</button>
121 `;
122 }
123}
124```
125
126Oh last two points. Please please please use standard commits for my sanity and report any issues to [the tangled repo](https://tangled.org/dunkirk.sh/thistle)
127
128<p align="center">
129 <img src="https://raw.githubusercontent.com/taciturnaxolotl/carriage/master/.github/images/line-break.svg" />
130</p>
131
132<p align="center">
133 © 2025-present <a href="https://github.com/taciturnaxolotl">Kieran Klukas</a>
134</p>
135
136<p align="center">
137 <a href="https://github.com/taciturnaxolotl/thistle/blob/main/LICENSE.md"><img src="https://img.shields.io/static/v1.svg?style=for-the-badge&label=License&message=MIT&logoColor=d9e0ee&colorA=363a4f&colorB=b7bdf8"/></a>
138</p>