repos
/ orchard main

orchard

mirror

Every site I host, in one repo, along with the Cloudflare Tunnel and Caddy that front them. It's all Go, Vite, and SQLite, and it runs on a desktop at home with nothing listening on an inbound port.

blogbuncaddycloudflare-tunneldockergogolanghomelabhtml-templatemonorepoself-hostedseosqlitestatic-sitetypstuptime-monitoringviteweb-analytics

9.5 KB · 202 lines · HTML Raw History
  1<!doctype html>
  2<html lang="en" data-theme="dark">
  3<head>
  4<meta charset="utf-8">
  5<!-- interactive-widget makes the on screen keyboard shrink the layout viewport
  6     rather than sit on top of it, which is what keeps the composer visible. -->
  7<meta name="viewport" content="width=device-width, initial-scale=1, interactive-widget=resizes-content">
  8<title>chat</title>
  9<meta name="description" content="A conversation with a small model running on hardware you own.">
 10<link rel="stylesheet" href="{{asset "static/app.css"}}">
 11<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><rect width='16' height='16' fill='%230e0d0a'/><rect x='3' y='7' width='7' height='2' fill='%236b9e78'/></svg>">
 12  <!-- Analytics, first party and on this estate. The id is identity rather than
 13       a credential, so it is a constant here like every other site's. -->
 14  <script>
 15    (function (m, e, t, r, i, c, s) {
 16      m.collectorQueue = m.collectorQueue || r;
 17      m.collectorServer = c;
 18      m.collectorId = s;
 19      m.collectorScript = e.createElement(t);
 20      m.collectorScript.src = c + i;
 21      e.head.appendChild(m.collectorScript);
 22    })(window, document, "script", [], "/static/collector.js",
 23       "https://analytics.bythewood.me", "73f89553-a3fa-4c4a-b567-9a9a0550240b");
 24  </script>
 25</head>
 26<body>
 27<div class="scan" aria-hidden="true"></div>
 28<div class="vignette" aria-hidden="true"></div>
 29
 30<div class="shell">
 31  <div class="scrim" id="scrim" hidden></div>
 32
 33  <aside class="side" id="side">
 34    <div class="side-head">
 35      <a class="brand" href="/">
 36        <span class="key">C<i class="cur"></i></span>
 37        <span class="brand-name">chat</span>
 38      </a>
 39      <button class="icon-btn" id="side-close" title="Hide history" aria-label="Hide history">&#10005;</button>
 40    </div>
 41
 42    <button class="new-chat" id="new-chat">
 43      <span class="plus">+</span> New conversation
 44    </button>
 45
 46    <div class="side-label">// History</div>
 47    <input class="filter" id="filter" type="search" placeholder="Filter" aria-label="Filter history" hidden>
 48    <nav class="convs" id="convs">
 49      {{range .Conversations}}
 50      <a class="conv" data-id="{{.ID}}" href="/c/{{.ID}}">
 51        <span class="conv-title">{{.Title}}</span>
 52        <span class="conv-when">{{when .Updated}}</span>
 53        <button class="conv-del" data-del="{{.ID}}" title="Delete" aria-label="Delete conversation">&#10005;</button>
 54      </a>
 55      {{else}}
 56      <p class="empty">Nothing yet. Ask something.</p>
 57      {{end}}
 58    </nav>
 59
 60    <div class="side-foot">
 61      <div class="side-label">// Session</div>
 62      <label class="incog" for="incognito">
 63        <input type="checkbox" id="incognito">
 64        <span class="incog-box" aria-hidden="true"></span>
 65        <span class="incog-text">
 66          <b>Incognito</b>
 67          <small>Nothing is written to disk</small>
 68        </span>
 69      </label>
 70      <div class="side-stats">
 71        <span><b id="stat-convs">{{.Stats.Conversations}}</b> saved</span>
 72        <span><b id="stat-msgs">{{.Stats.Messages}}</b> messages</span>
 73      </div>
 74      <button class="wipe" id="wipe">Delete all history</button>
 75    </div>
 76  </aside>
 77
 78  <main class="main">
 79    <header class="bar">
 80      <button class="icon-btn" id="side-open" title="Show history" aria-label="Show history">&#9776;</button>
 81      <div class="bar-title" id="bar-title">New conversation</div>
 82      <div class="bar-right">
 83        <span class="incog-flag" id="incog-flag" hidden>INCOGNITO</span>
 84        <span class="warn-flag" id="search-down" hidden title="DuckDuckGo is rate limiting this address, so nothing can be looked up until it clears">NO SEARCH</span>
 85        <span class="meter" id="meter" title="Context used by the last turn" hidden>
 86          <span class="meter-num" id="meter-num">0</span>
 87          <span class="meter-track"><i class="meter-fill" id="meter-fill"></i></span>
 88          <span class="meter-cap" id="meter-cap">{{.Ctx}}</span>
 89        </span>
 90        <span class="tps" id="tps" title="Generation speed of the last turn" hidden></span>
 91        <span class="model" title="The model answering">
 92          <i class="dot" id="model-dot"></i>{{.Model}}
 93        </span>
 94      </div>
 95    </header>
 96
 97    <div class="thread" id="thread">
 98      <section class="welcome" id="welcome">
 99        <div class="eyebrow"><i class="dot on"></i> on this machine <span class="sep">/</span> self-hosted <span class="sep">/</span> no telemetry</div>
100        <h1>A conversation with a model on <span class="hl">hardware you own</span>.</h1>
101        <p>It searches the web, reads pages, checks the weather and the markets, looks up scores and odds, and verifies a song exists before it names one. Nothing leaves this machine except the pages it fetches for you.</p>
102        <p class="welcome-hint">Press <kbd>Ctrl</kbd><kbd>/</kbd> for keyboard shortcuts.</p>
103        <div class="chips">
104          <button class="chip" data-ask="What is the weather looking like this weekend, and how should I pack for camping in it?">Weather this weekend</button>
105          <button class="chip" data-ask="Give me a read on the market right now: the S&amp;P, the Nasdaq, gold and bitcoin.">Market read</button>
106          <button class="chip" data-ask="Build me a 10 song playlist off Turnstile - Blackout, and check every song is real.">Build a playlist</button>
107          <button class="chip" data-ask="Write a Go function that wraps an http.Handler and logs each request as one JSON line, keeping http.Flusher working.">Write some Go</button>
108        </div>
109      </section>
110    </div>
111
112    <div class="composer">
113      <div class="tray" id="tray" hidden></div>
114      <form class="ask" id="ask">
115        <button type="button" class="attach" id="attach" title="Attach files" aria-label="Attach files">&#43;</button>
116        <textarea id="input" rows="1" placeholder="Ask anything" autocomplete="off" spellcheck="true"></textarea>
117        <button type="submit" id="send" title="Send" aria-label="Send">&#9654;</button>
118        <button type="button" id="stop" title="Stop" aria-label="Stop" hidden>&#9632;</button>
119      </form>
120      <input type="file" id="picker" multiple hidden>
121      <p class="foot-note">
122        <span id="foot-state">Enter to send, shift and enter for a new line.</span>
123        <span class="foot-right">
124          <button type="button" class="linkish keys-hint" id="keys-open" title="Keyboard shortcuts">
125            Shortcuts <kbd>Ctrl</kbd><kbd>/</kbd>
126          </button>
127          <button type="button" class="linkish" id="mem-open" title="What it remembers about you">Memory</button>
128          <a href="{{.Source}}" target="_blank" rel="noopener">View source</a>
129        </span>
130      </p>
131    </div>
132  </main>
133</div>
134
135<div class="sheet" id="mem-sheet" hidden>
136  <div class="sheet-card mem-card" role="dialog" aria-modal="true" aria-labelledby="mem-title">
137    <div class="sheet-head">
138      <span class="side-label" id="mem-title" style="padding:0">// Memory</span>
139      <button class="icon-btn" id="mem-close" aria-label="Close">&#10005;</button>
140    </div>
141
142    <p class="mem-lede">One sentence each, carried into a turn only when your question touches
143      them. It picks things up as you talk. Deleting is yours, writing goes through the model.</p>
144
145    <div class="mem-msg" id="mem-msg" hidden></div>
146
147    <form class="teach" id="mem-form">
148      <textarea id="mem-said" rows="2" placeholder="Remember that&hellip; or forget that&hellip;"></textarea>
149      <button type="submit" id="mem-send">Tell it</button>
150    </form>
151
152    <div class="mem-list" id="mem-list"></div>
153
154    <div class="mem-foot">
155      <span id="mem-count"></span>
156      <button type="button" class="danger" id="mem-forget">Forget everything</button>
157    </div>
158  </div>
159</div>
160
161<div class="sheet" id="keys-sheet" hidden>
162  <div class="sheet-card" role="dialog" aria-modal="true" aria-labelledby="keys-title">
163    <div class="sheet-head">
164      <span class="side-label" id="keys-title" style="padding:0">// Keyboard</span>
165      <button class="icon-btn" id="keys-close" aria-label="Close">&#10005;</button>
166    </div>
167    <dl class="keys">
168      <dt><kbd>Ctrl</kbd><kbd>Shift</kbd><kbd>O</kbd></dt><dd>New conversation</dd>
169      <dt><kbd>Ctrl</kbd><kbd>Shift</kbd><kbd>I</kbd></dt><dd>Toggle incognito</dd>
170      <dt><kbd>Ctrl</kbd><kbd>Shift</kbd><kbd>S</kbd></dt><dd>Show or hide history</dd>
171      <dt><kbd>Ctrl</kbd><kbd>K</kbd></dt><dd>Filter history</dd>
172      <dt><kbd>Ctrl</kbd><kbd>Shift</kbd><kbd>&#9003;</kbd></dt><dd>Delete this conversation</dd>
173      <dt><kbd>Shift</kbd><kbd>Esc</kbd></dt><dd>Focus the message box</dd>
174      <dt><kbd>Esc</kbd></dt><dd>Stop generating</dd>
175      <dt><kbd>Enter</kbd></dt><dd>Send</dd>
176      <dt><kbd>Shift</kbd><kbd>Enter</kbd></dt><dd>New line</dd>
177      <dt><kbd>Ctrl</kbd><kbd>Shift</kbd><kbd>M</kbd></dt><dd>What it remembers</dd>
178      <dt><kbd>Ctrl</kbd><kbd>/</kbd></dt><dd>This list</dd>
179    </dl>
180    <p class="keys-note">On a Mac use <kbd>&#8984;</kbd> in place of <kbd>Ctrl</kbd>. Chrome keeps <kbd>Ctrl</kbd><kbd>Shift</kbd><kbd>I</kbd> for its developer tools, so incognito is also on <kbd>Ctrl</kbd><kbd>Shift</kbd><kbd>P</kbd>.</p>
181  </div>
182</div>
183
184<div class="drop" id="drop" hidden><span>Drop files to attach</span></div>
185
186<template id="tpl-msg">
187  <article class="msg">
188    <div class="who"></div>
189    <div class="tools" hidden></div>
190    <div class="work" hidden></div>
191    <div class="files" hidden></div>
192    <div class="widgets" hidden></div>
193    <div class="body"></div>
194    <div class="sources" hidden></div>
195  </article>
196</template>
197
198<script src="{{asset "static/widgets.js"}}" defer></script>
199<script src="{{asset "static/app.js"}}" defer></script>
200</body>
201</html>