Initial working copy

This commit is contained in:
Chandler Swift 2025-12-19 23:14:28 -06:00
parent 2a335176e6
commit 2c876cef42
19 changed files with 783 additions and 126 deletions

1
.gitignore vendored
View file

@ -1,2 +1,3 @@
*.gguf
svs-services-server
svs-services.db*

3
README2.md Normal file
View file

@ -0,0 +1,3 @@
Parse this subdomain for a company providing services into English words, if possible. For example, the subdomain "webdesign" should be parsed into the string "Web Design". If the string can't be parsed, just return the original string. Return nothing other than the string, without quotes. The string is "lawnmowing". The parsed string is:
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

View file

@ -1,4 +1,4 @@
package llama
package gollamacpp
import (
"flag"
@ -9,7 +9,14 @@ import (
"github.com/go-skynet/go-llama.cpp"
)
func main() {
var (
threads = 4
tokens = 128
gpulayers = 0
seed = -1
)
func Run() {
var model string
flags := flag.NewFlagSet(os.Args[0], flag.ExitOnError)

View file

@ -0,0 +1,116 @@
package llamaserver
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"time"
)
type LlamaServerProvider struct {
Host string // http://localhost:8080/
Model string
}
type Message struct {
Role string `json:"role"`
ReasoningContent string `json:"reasoning_content,omitempty"`
Content string `json:"content"`
}
type Request struct {
Messages []Message `json:"messages"`
Model string `json:"model"`
ChatTemplateKwargs map[string]interface{} `json:"chat_template_kwargs,omitempty"`
}
type Response struct {
Choices []struct {
Index int `json:"index"`
Message Message `json:"message"`
FinishReason string `json:"finish_reason"`
} `json:"choices"`
Created int64 `json:"created"` // unix timestamp; TODO: decode into time.Time
Model string `json:"model"`
SystemFingerprint string `json:"system_fingerprint"`
Object string `json:"object"`
Usage struct {
PromptTokens int `json:"prompt_tokens"`
CompletionTokens int `json:"completion_tokens"`
TotalTokens int `json:"total_tokens"`
} `json:"usage"`
ID string `json:"id"`
Timings struct {
CacheN int `json:"cache_n"`
PromptN int `json:"prompt_n"`
PromptMS float64 `json:"prompt_ms"`
PromptPerTokenMS float64 `json:"prompt_per_token_ms"`
PromptPerSecond float64 `json:"prompt_per_second"`
PredictedN int `json:"predicted_n"`
PredictedMS float64 `json:"predicted_ms"`
PredictedPerTokenMS float64 `json:"predicted_per_token_ms"`
PredictedPerSecond float64 `json:"predicted_per_second"`
} `json:"timings"`
}
func (p LlamaServerProvider) Health() (err error) {
client := http.Client{
Timeout: 100 * time.Millisecond,
}
res, err := client.Get(p.Host + "health")
if err != nil {
return err
}
if res.StatusCode != 200 {
return fmt.Errorf("llama-server health check returned status %v (%v)", res.StatusCode, res.Status)
}
return nil
}
func (p LlamaServerProvider) Complete(ctx context.Context, prompt string) (response string, err error) {
req := Request{
Messages: []Message{
{
Role: "user",
Content: prompt,
},
},
Model: p.Model,
ChatTemplateKwargs: map[string]interface{}{
"reasoning_effort": "low",
},
}
encReq, err := json.Marshal(req)
if err != nil {
return "", fmt.Errorf("marshaling json: %w", err)
}
res, err := http.Post(p.Host+"/v1/chat/completions", "application/json", bytes.NewReader(encReq))
if err != nil {
return "", err
}
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
if err != nil {
return "", fmt.Errorf("reading response body: %w", err)
}
log.Println(string(body))
resData := Response{}
dec := json.NewDecoder(bytes.NewReader(body))
if err := dec.Decode(&resData); err != nil {
return "", fmt.Errorf("decoding response: %w", err)
}
if len(resData.Choices) == 0 {
log.Println(resData)
return "", fmt.Errorf("no choices in response")
}
log.Printf("Generated %v (%v) tokens in %v ms (%v T/s)", resData.Usage.CompletionTokens, resData.Timings.PredictedN, resData.Timings.PredictedMS, resData.Timings.PredictedPerSecond)
return resData.Choices[0].Message.Content, nil
}

View file

View file

@ -0,0 +1,17 @@
curl 'http://localhost:8080/v1/chat/completions' \
-X POST \
-H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:146.0) Gecko/20100101 Firefox/146.0' \
-H 'Accept: */*' \
-H 'Accept-Language: en-US,en;q=0.5' \
-H 'Accept-Encoding: gzip, deflate, br, zstd' \
-H 'Referer: http://localhost:8080/' \
-H 'Content-Type: application/json' \
-H 'Origin: http://localhost:8080' \
-H 'Connection: keep-alive' \
-H 'Sec-Fetch-Dest: empty' \
-H 'Sec-Fetch-Mode: cors' \
-H 'Sec-Fetch-Site: same-origin' \
-H 'Priority: u=4' \
-H 'Pragma: no-cache' \
-H 'Cache-Control: no-cache' \
--data @req.json

View file

@ -0,0 +1,117 @@
#include "llama.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
void null_log_callback(enum ggml_log_level level, const char *message, void *user_data) {}
int64_t time_us(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (int64_t)ts.tv_sec*1000000 + (int64_t)ts.tv_nsec/1000;
}
void chandlerscustomllama(char* prompt) {
int n_predict = 100;
printf("Prompt: %s\n", prompt);
struct llama_model_params model_params = llama_model_default_params();
// printf("model_params.n_gpu_layers: %d\n", model_params.n_gpu_layers);
llama_log_set(null_log_callback, NULL); // Disable logging
struct llama_model *model = llama_model_load_from_file("/home/chandler/llms/gpt-oss-20b-Q4_K_M.gguf", model_params);
if (model == NULL) {
fprintf(stderr, "Failed to load model\n");
return;
}
const struct llama_vocab * vocab = llama_model_get_vocab(model);
const int n_prompt = -llama_tokenize(vocab, prompt, strlen(prompt), NULL, 0, true, true);
llama_token * prompt_tokens = malloc(sizeof(llama_token) * n_prompt);
if (llama_tokenize(vocab, prompt, strlen(prompt), prompt_tokens, n_prompt, true, true) < 0) {
fprintf(stderr, "%s: error: failed to tokenize prompt\n", __func__);
return;
}
struct llama_context_params ctx_params = llama_context_default_params();
ctx_params.n_ctx = n_prompt + n_predict - 1;
ctx_params.n_batch = n_prompt;
ctx_params.no_perf = false; // TODO: true
struct llama_context * ctx = llama_init_from_model(model, ctx_params);
if (ctx == NULL) {
fprintf(stderr, "%s: error: failed to create llama_context\n", __func__);
return;
}
// initialize the sampler
struct llama_sampler_chain_params sparams = llama_sampler_chain_default_params();
struct llama_sampler * smpl = llama_sampler_chain_init(sparams);
llama_sampler_chain_add(smpl, llama_sampler_init_greedy());
// prepare a batch for the prompt
struct llama_batch batch = llama_batch_get_one(prompt_tokens, n_prompt);
if (llama_model_has_encoder(model)) {
if (llama_encode(ctx, batch)) {
fprintf(stderr, "%s : failed to eval\n", __func__);
}
llama_token decoder_start_token_id = llama_model_decoder_start_token(model);
if (decoder_start_token_id == LLAMA_TOKEN_NULL) {
decoder_start_token_id = llama_vocab_bos(vocab);
}
batch = llama_batch_get_one(&decoder_start_token_id, 1);
}
int64_t start = time_us();
int n_decode = 0;
llama_token new_token_id;
for (int n_pos = 0; n_pos + batch.n_tokens < n_prompt + n_predict; ) {
// evaluate the current batch with the transformer model
if (llama_decode(ctx, batch)) {
fprintf(stderr, "%s : failed to eval\n", __func__);
}
n_pos += batch.n_tokens;
// sample the next token
{
new_token_id = llama_sampler_sample(smpl, ctx, -1);
// is it an end of generation?
if (llama_vocab_is_eog(vocab, new_token_id)) {
break;
}
char buf[128]; // TODO: how do we know that this is enough?
int n = llama_token_to_piece(vocab, new_token_id, buf, sizeof(buf), 0, true); // TODO: do I want special tokens?
if (n < 0) {
fprintf(stderr, "%s: error: failed to convert token to piece\n", __func__);
return;
}
buf[n] = 0;
printf("%s", buf); // TODO: null terminator?
// prepare the next batch with the sampled token
batch = llama_batch_get_one(&new_token_id, 1);
n_decode += 1;
}
}
int64_t end = time_us();
fprintf(stderr, "%s: decoded %d tokens in %.2f s, speed: %.2f t/s\n",
__func__, n_decode, (end - start) / 1000000.0f, n_decode / ((end - start) / 1000000.0f));
llama_sampler_free(smpl);
llama_free(ctx);
llama_model_free(model);
}

View file

@ -0,0 +1,15 @@
package llamacpp
/*
#include "llamacpp.h"
#include <stdlib.h>
*/
import "C"
import "unsafe"
func Run() {
prompt := C.CString("Here is a very short story about a brave knight. One day, the knight")
C.chandlerscustomllama(prompt)
C.free(unsafe.Pointer(prompt))
}

View file

@ -0,0 +1 @@
void chandlerscustomllama(char* prompt);

View file

@ -5,10 +5,17 @@ import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"time"
)
type OpenRouterProvider struct {
// Endpoint string
Token string
Model string // "openai/gpt-oss-20b:free"
}
type Message struct {
Role string `json:"role"` // "system" | "user" | "assistant"
Content string `json:"content"`
@ -21,6 +28,10 @@ type ChatCompletionRequest struct {
MaxTokens *int `json:"max_tokens,omitempty"`
TopP *float64 `json:"top_p,omitempty"`
Stop json.RawMessage `json:"stop,omitempty"` // string or []string; keep flexible
Provider struct {
Sort string `json:"sort,omitempty"`
} `json:"provider,omitempty"`
}
type ChatCompletionResponse struct {
@ -35,60 +46,52 @@ type ChatCompletionResponse struct {
} `json:"choices"`
}
func ChatCompletion(ctx context.Context, req ChatCompletionRequest) (ChatCompletionResponse, error) {
httpClient := http.Client{Timeout: 10 * time.Second}
func (p OpenRouterProvider) Complete(ctx context.Context, prompt string) (string, error) {
req := ChatCompletionRequest{
Model: p.Model,
Messages: []Message{
{
Role: "user",
Content: prompt,
},
},
Provider: struct {
Sort string `json:"sort,omitempty"`
}{
Sort: "throughput",
},
}
httpClient := http.Client{Timeout: 10 * time.Second}
body, err := json.Marshal(req)
if err != nil {
return ChatCompletionResponse{}, err
return "", err
}
httpReq, err := http.NewRequestWithContext(ctx, "POST", "https://openrouter.ai/api/v1/chat/completions", bytes.NewReader(body))
if err != nil {
return ChatCompletionResponse{}, err
return "", err
}
httpReq.Header.Set("Authorization", "Bearer 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")
httpReq.Header.Set("Authorization", fmt.Sprintf("Bearer %v", p.Token))
httpReq.Header.Set("Content-Type", "application/json")
resp, err := httpClient.Do(httpReq)
if err != nil {
return ChatCompletionResponse{}, err
return "", err
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
// You may want to decode OpenRouter's error JSON here for better messages
return ChatCompletionResponse{}, fmt.Errorf("openrouter status %d", resp.StatusCode)
return "", fmt.Errorf("openrouter status %d", resp.StatusCode)
}
var out ChatCompletionResponse
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
return ChatCompletionResponse{}, err
}
return out, nil
}
func doLLM() {
req := ChatCompletionRequest{
Model: "openai/gpt-oss-20b:free",
Messages: []Message{
{
Role: "user",
Content: "Write a short poem about software development.",
},
},
}
ctx := context.Background()
resp, err := client.ChatCompletion(ctx, req)
if err != nil {
fmt.Println("Error:", err)
return
}
for _, choice := range resp.Choices {
fmt.Printf("Response: %s\n", choice.Message.Content)
log.Println(err)
log.Println(out)
return "", err
}
return out.Choices[0].Message.Content, nil
}

View file

@ -49,4 +49,6 @@ func Complete(ctx context.Context, prompt string) (string, error) {
}
fmt.Println()
return "", nil
}

39
db/db.go Normal file
View file

@ -0,0 +1,39 @@
package db
import (
"database/sql"
"log"
_ "modernc.org/sqlite"
)
var DB *sql.DB
func InitDatabase(dbPath string) error {
var err error
DB, err = sql.Open("sqlite", dbPath)
if err != nil {
return err
}
_, err = DB.Exec(`
PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;
`)
if err != nil {
log.Fatal(err)
}
_, err = DB.Exec(
`CREATE TABLE IF NOT EXISTS sites (
subdomain TEXT PRIMARY KEY,
data BLOB NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
view_count INTEGER DEFAULT 1
)`,
)
if err != nil {
return err
}
return nil
}

10
go.mod
View file

@ -5,10 +5,20 @@ go 1.25.4
require (
github.com/go-skynet/go-llama.cpp v0.0.0-20240314183750-6a8041ef6b46
github.com/hybridgroup/yzma v1.3.0
modernc.org/sqlite v1.41.0
)
require (
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/ebitengine/purego v0.9.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/jupiterrider/ffi v0.5.1 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39 // indirect
golang.org/x/sys v0.38.0 // indirect
modernc.org/libc v1.66.10 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
)

51
go.sum
View file

@ -1,3 +1,5 @@
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
@ -8,23 +10,64 @@ github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEe
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hybridgroup/yzma v1.3.0 h1:5dw9qEcFEGEJq+tA12Ooa6D/e0PROqv7Ix6VfSR9MQI=
github.com/hybridgroup/yzma v1.3.0/go.mod h1:UUYw+DLlrgtBYm+B+9XD3boB1ZcDpfbAnYHKW3VKKZ4=
github.com/jupiterrider/ffi v0.5.1 h1:l7ANXU+Ex33LilVa283HNaf/sTzCrrht7D05k6T6nlc=
github.com/jupiterrider/ffi v0.5.1/go.mod h1:x7xdNKo8h0AmLuXfswDUBxUsd2OqUP4ekC8sCnsmbvo=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4=
github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
github.com/onsi/gomega v1.28.0 h1:i2rg/p9n/UqIDAMFUJ6qIUUMcsqOuUHgbpbu235Vr1c=
github.com/onsi/gomega v1.28.0/go.mod h1:A1H2JE76sI14WIP57LMKj7FVfCHx3g3BcZVjJG8bjX8=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39 h1:DHNhtq3sNNzrvduZZIiFyXWOL9IWaDPHqTnLJp+rCBY=
golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0=
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss=
golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/cc/v4 v4.26.5 h1:xM3bX7Mve6G8K8b+T11ReenJOT+BmVqQj0FY5T4+5Y4=
modernc.org/cc/v4 v4.26.5/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.28.1 h1:wPKYn5EC/mYTqBO373jKjvX2n+3+aK7+sICCv4Fjy1A=
modernc.org/ccgo/v4 v4.28.1/go.mod h1:uD+4RnfrVgE6ec9NGguUNdhqzNIeeomeXf6CL0GTE5Q=
modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA=
modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
modernc.org/libc v1.66.10 h1:yZkb3YeLx4oynyR+iUsXsybsX4Ubx7MQlSYEw4yj59A=
modernc.org/libc v1.66.10/go.mod h1:8vGSEwvoUoltr4dlywvHqjtAqHBaw0j1jI7iFBTAr2I=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.41.0 h1:bJXddp4ZpsqMsNN1vS0jWo4IJTZzb8nWpcgvyCFG9Ck=
modernc.org/sqlite v1.41.0/go.mod h1:9fjQZ0mB1LLP0GYrp39oOJXx/I2sxEnZtzCmEQIKvGE=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=

86
main.go
View file

@ -2,10 +2,19 @@ package main
import (
"embed"
"flag"
"fmt"
"html/template"
"log"
"net/http"
"os"
"strings"
"git.chandlerswift.com/chandlerswift/svs-services-server/page"
"git.chandlerswift.com/chandlerswift/svs-services-server/completion"
llamaserver "git.chandlerswift.com/chandlerswift/svs-services-server/completion/llama-server"
"git.chandlerswift.com/chandlerswift/svs-services-server/completion/openrouter"
"git.chandlerswift.com/chandlerswift/svs-services-server/db"
"git.chandlerswift.com/chandlerswift/svs-services-server/site"
)
//go:embed templates/index.html
@ -20,20 +29,73 @@ var headshots embed.FS
var tmpl = template.Must(template.New("index").Parse(indexTemplate))
var notFoundTmpl = template.Must(template.New("404").Parse(notFoundTemplate))
var completionProviders map[string]completion.CompletionProvider = map[string]completion.CompletionProvider{
"openrouter": openrouter.OpenRouterProvider{
Token: os.Getenv("OPENROUTER_API_KEY"),
Model: "openai/gpt-oss-120b",
},
"llama-server": llamaserver.LlamaServerProvider{
Host: "http://localhost:8080",
Model: "gpt-oss-20b-Q4_K_M",
},
}
func parseService(host string) (h string, err error) {
parts := strings.Split(host, ".")
if len(parts) != 3 {
return "", fmt.Errorf("Unexpected number of parts in hostname; expected 3, got %v", len(parts))
}
return parts[0], nil
}
func main() {
http.HandleFunc("GET /{$}", func(w http.ResponseWriter, r *http.Request) {
host := r.Host
pageData := page.GenerateDummyData(host)
tmpl.Execute(w, pageData)
// use flag package to parse args:
port := flag.String("port", "64434", "Port to listen on") // echo "svs" | base64 -d | od -An -t u2
address := flag.String("address", "", "Address to listen on")
completionProviderName := flag.String("completion-provider", "llama-server", "Completion provider to use (openrouter, llama-server, …)")
dbpath := flag.String("database", "svs-services.db", "Path to SQLite database file")
flag.Parse()
err := db.InitDatabase(*dbpath)
if err != nil {
log.Fatalf("Failed to initialize database: %v", err)
}
addr := fmt.Sprintf("%s:%s", *address, *port)
fmt.Println("Using completion provider", *completionProviderName)
defaultCompletionProvider := completionProviders[*completionProviderName]
// for err := defaultCompletionProvider.Health(); err != nil; err = defaultCompletionProvider.Health() {
// fmt.Println("Waiting for Llama server to be healthy... (%v)", err)
// time.Sleep(1 * time.Second)
// }
http.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) {
service, err := parseService(r.Host)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
siteData, err := site.GetSiteData(service, defaultCompletionProvider)
if err != nil {
http.Error(w, "Could not generate site", 500)
log.Printf("Getting site data for service %v: %v", service, err)
return
}
if r.URL.Path != "/" {
w.WriteHeader(http.StatusNotFound)
notFoundTmpl.Execute(w, siteData)
return
}
tmpl.Execute(w, siteData)
})
// Serve embedded headshots file
http.Handle("/headshots/", http.FileServer(http.FS(headshots)))
http.Handle("GET /headshots/", http.FileServer(http.FS(headshots)))
// 404 handler
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
notFoundTmpl.Execute(w, nil)
})
fmt.Println("Starting server on", addr)
panic(http.ListenAndServe(addr, nil))
http.ListenAndServe(":8080", nil) // TODO: configurable port
}

View file

@ -1,74 +0,0 @@
package page
import "time"
type Color struct { // may have .rgba() and .hex() and similar
Hex string
// something that can encode color
}
type Theme struct {
AccentColor Color
SecondaryColor Color
BackgroundColor Color
// possibly more?
}
type Testimonial struct {
Name string
Position string
Quote string
}
type Page struct {
Theme Theme
Occupation string
Tagline string
WhatWeDo string
ChandlerBio string
EricBio string
IsaacBio string
Testimonials []Testimonial
CurrentYear int
}
func GenerateDummyData(host string) Page {
return Page{
Theme: Theme{
AccentColor: Color{
Hex: "#FF5733",
},
SecondaryColor: Color{
Hex: "#33C1FF",
},
BackgroundColor: Color{
// Creamy off-white
Hex: "#FFF8E7",
},
},
Occupation: "Web Design",
Tagline: "Custom websites cooked to order",
WhatWeDo: "Lorem Ipsum…",
ChandlerBio: "Chandler has years of experience in the [...] sector",
EricBio: "Eric is a seasoned professional in [...]",
IsaacBio: "Isaac specializes in [...]",
Testimonials: []Testimonial{
{
Name: "Barack Obama",
Position: "Poolhall acquaintance",
Quote: "After the fiasco of the ACA Website, I know how important it is to get a website right on the first try, and boy do these gentlemen deliver!",
},
{
Name: "Ada Lovelace",
Position: "Pioneer of Computing",
Quote: "The elegance and functionality of the websites created by SVS Web Design are truly ahead of their time.",
},
{
Name: "Steve Jobs",
Position: "Visionary Entrepreneur",
Quote: "Design is not just what it looks like and feels like. Design is how it works. SVS Web Design understands this principle deeply.",
},
},
CurrentYear: time.Now().Year(),
}
}

225
site/site.go Normal file
View file

@ -0,0 +1,225 @@
package site
import (
"context"
"database/sql"
"encoding/json"
"fmt"
"html/template"
"regexp"
"strings"
"time"
"git.chandlerswift.com/chandlerswift/svs-services-server/completion"
"git.chandlerswift.com/chandlerswift/svs-services-server/db"
)
type Color struct { // may have .rgba() and .hex() and similar
Hex string
// something that can encode color
}
type Theme struct {
AccentColor Color
SecondaryColor Color
BackgroundColor Color
// possibly more?
}
type Testimonial struct {
Name string
Position string
Quote string
}
type Site struct {
Theme Theme
Occupation string
Tagline string
WhatWeDo template.HTML
ChandlerBio template.HTML
EricBio template.HTML
IsaacBio template.HTML
Testimonials []Testimonial
CurrentYear int
}
var whitespace = regexp.MustCompile(`\s+`)
func norm(s string) string {
return strings.TrimSpace(whitespace.ReplaceAllString(s, " "))
}
func GetSiteData(subdomain string, cp completion.CompletionProvider) (site *Site, err error) {
var raw []byte
err = db.DB.QueryRow(`UPDATE sites SET view_count = view_count + 1 WHERE subdomain = ? RETURNING data`, subdomain).Scan(&raw)
if err == nil {
site = &Site{}
err = json.Unmarshal(raw, site)
if err != nil {
return nil, fmt.Errorf("unmarshaling site data: %w", err)
}
return site, nil
} else if err == sql.ErrNoRows {
site, err = GenerateSiteData(subdomain, cp)
if err != nil {
return nil, fmt.Errorf("generating site: %w", err)
}
data, err := json.Marshal(site)
if err != nil {
return nil, fmt.Errorf("marshaling generated site: %w", err)
}
_, err = db.DB.Exec(`INSERT INTO sites (subdomain, data) VALUES (?, ?)`, subdomain, data)
if err != nil {
return nil, fmt.Errorf("inserting generated site: %w", err)
}
return site, nil
} else {
return nil, fmt.Errorf("querying site data: %w", err)
}
}
func GenerateSiteData(rawservice string, cp completion.CompletionProvider) (site *Site, err error) {
site = &Site{}
prompt := norm(`
Parse this subdomain for a company providing services into English words, if possible.
For example, the subdomain "webdesign" should be parsed into the string "Web Design".
If the string can't be parsed, just return the original string.
Return nothing other than the string, without quotes.
The string is "%s".
The parsed string is:
`)
prompt = strings.TrimSpace(prompt)
prompt = whitespace.ReplaceAllString(prompt, " ")
site.Occupation, err = cp.Complete(context.TODO(), fmt.Sprintf(prompt, rawservice))
if err != nil {
return nil, fmt.Errorf("parsing service: %w", err)
}
prompt = norm(`
Generate a short, catchy tagline for a %v company.
The tagline should be no more than 7 words long, and should be slightly silly;
e.g. for a web design company, something like "Custom websites cooked to order".
Do not include quotes.
`)
site.Tagline, err = cp.Complete(context.TODO(), fmt.Sprintf(prompt, site.Occupation))
if err != nil {
return nil, fmt.Errorf("generating tagline: %w", err)
}
site.Theme = Theme{ // TODO
AccentColor: Color{
Hex: "#FF5733",
},
SecondaryColor: Color{
Hex: "#33C1FF",
},
BackgroundColor: Color{
// Creamy off-white
Hex: "#FFF8E7",
},
}
prompt = norm(`
Generate a few short paragraphs (3-4 sentences each) describing what the offices of Swift, Villnow & Swift
(colloquially \"SVS\"), a %v company, has to offer, in an over-the-top, slightly irreverent tone.
The paragraphs will go on the company's homesite to inform visitors about their services.
Markup should be in HTML, including paragraph breaks.
Do not wrap in any additional markup.
`)
whatWeDo, err := cp.Complete(context.TODO(), fmt.Sprintf(prompt, site.Occupation))
site.WhatWeDo = template.HTML(whatWeDo)
if err != nil {
return nil, fmt.Errorf("generating what we do: %w", err)
}
prompt = norm(`
Generate a short bio (2-4 sentences) for Chandler Swift, a co-founder of SVS, a %v company.
Chandler went to high school in Glencoe, Minnesota with Eric Villnow and Isaac Swift, the other two co-founders.
Chandler has always enjoyed tinkering with projects.
Chandler has a degree in Computer Science from the University of Minnesota, Duluth.
Chandler spent years with the Boy Scouts (including a summer working at Many Point Scout Camp) and is an Eagle Scout.
Chandler plays keyboard instruments, especially the piano (jazz and classical), and the organ.
Chandler enjoys long-distance bicycling.
Chandler has a cocker spaniel-poodle mix Mabel.
Feel free to make up relevant work history, as long as it's over the top;
for example, for SVS Space Sciences division, you might say that he spent 6 months on the International Space Station and/or was the Administrator of NASA.
Feel free to make up awards, again humorous and over the top, e.g. "In 2019 he swept the Nobel ceremony, taking home prizes in Chemistry, Physics _and_ Peace (the Peace Prize was awarded for _not_ releasing the technology behind the Physics Prize!).
Emphasize the relevance of all the above information to SVS's line of work.
Do not shoehorn all the above information; only include each piece if relevant.
`)
bio, err := cp.Complete(context.TODO(), fmt.Sprintf(prompt, site.Occupation))
if err != nil {
return nil, fmt.Errorf("generating Chandler bio: %w", err)
}
site.ChandlerBio = template.HTML(bio)
prompt = norm(`
Generate a short, satirical/irreverent bio (2-4 sentences) for Eric Villnow, a co-founder of SVS, a %v company.
Eric went to high school in Glencoe, Minnesota with Isaac Swift and Chandler Swift, the other two co-founders.
Eric has a degree in Computer Science from Bemidji State University with a minor in Business Administration.
Eric drives a 2023 Toyota Tacoma TRD Off-Road.
Eric works as a UPS driver west of the Twin Cities, and has a spotless safety record.
Eric drinks vast quantities of Mountain Dew and 1919 Root Beer.
Eric's hobbies include farming, where he works on his family's farm near Plato, Minnesota.
Eric enjoys woodworking and 3D printing.
Feel free to make up relevant work history, as long as it's over the top;
for example, saying that he delivered two hundred boxes in his first day as a UPS driver -- after his truck broke down five miles into the route!
Also, make up awards, again humorous and over the top, e.g. "In 2023 he was naed the fastest UPS driver, with a record showing he averaged package delivery 15 minutes before they were even shipped."
Emphasize the relevance of all the above information to SVS's line of work.
Do not include all the above information; only mention each piece if relevant.
`)
bio, err = cp.Complete(context.TODO(), fmt.Sprintf(prompt, site.Occupation))
if err != nil {
return nil, fmt.Errorf("generating Isaac bio: %w", err)
}
site.EricBio = template.HTML(fmt.Sprintf("Eric is a seasoned professional in the %v industry, known for his expertise and dedication.", site.Occupation))
prompt = norm(`
Generate a short, satirical/irreverent bio (2-4 sentences) for Isaac Swift, a co-founder of SVS, a %v company.
Isaac went to high school in Glencoe, Minnesota with Eric Villnow and Chandler Swift, the other two co-founders.
Isaac studied Computer Science for a year at North Dakota State University.
Isaac enjoys mountain biking and camping.
Isaac drives a 2024 Toyota Tacoma TRD Off-Road, and won't shut up about it.
Isaac is an avid adventure motorcyclist.
Isaac worked eight summers at Many Point Scout Camp, occupying positions from program counselor to camp director.
While there, he designed and ran a new mountain biking program.
Isaac works Emergency Medical Services as an EMT for Allina Ambulance Service in Hutchinson, Minnesota.
If relevant, mention that "Isaac is not only cute enough to stop your heart, but skilled enough to restart it!".
Heavily emphasize Isaac's coffee consumption, and possibly mention his role as lead coffee engineer at SVS.
Feel free to make up relevant work history, as long as it's over the top;
for example, saying that he saved several thousand lives his first week as an EMT.
Also, make up awards, again humorous and over the top, e.g. "In 2022 he was named EMT of the Year for resuscitating a patient who had been dead for nearly six months."
Emphasize the relevance of all the above information to SVS's line of work.
Do not include all the above information; only mention each piece if relevant.
`)
bio, err = cp.Complete(context.TODO(), fmt.Sprintf(prompt, site.Occupation))
if err != nil {
return nil, fmt.Errorf("generating Isaac bio: %w", err)
}
site.IsaacBio = template.HTML(bio)
site.Testimonials = []Testimonial{
{
Name: "Barack Obama",
Position: "Poolhall acquaintance",
Quote: "After the fiasco of the ACA Website, I know how important it is to get a website right on the first try, and boy do these gentlemen deliver!",
},
{
Name: "Ada Lovelace",
Position: "Pioneer of Computing",
Quote: "The elegance and functionality of the websites created by SVS Web Design are truly ahead of their time.",
},
{
Name: "Steve Jobs",
Position: "Visionary Entrepreneur",
Quote: "Design is not just what it looks like and feels like. Design is how it works. SVS Web Design understands this principle deeply.",
},
}
site.CurrentYear = time.Now().Year()
return
}

View file

@ -0,0 +1,70 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>404 — Page not found</title>
<style>
:root{--bg:#f7fafc;--card:#fff;--muted:#6b7280;--accent:#2563eb}
html,body{height:100%}
body{
margin:0;
font-family:Inter, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial;
background:linear-gradient(180deg,var(--bg),#fff);
color:#111827;
display:flex;
align-items:center;
justify-content:center;
padding:2rem;
}
.card{
background:var(--card);
border-radius:12px;
box-shadow:0 6px 24px rgba(16,24,40,.08);
padding:2.5rem;
max-width:720px;
width:100%;
text-align:center;
}
.code{
font-weight:700;
font-size:3.5rem;
color:var(--accent);
margin:0;
letter-spacing:-0.02em;
}
h2{margin:.25rem 0 .5rem;font-size:1.25rem}
p{margin:0 0 1.25rem;color:var(--muted)}
.actions{display:flex;gap:.75rem;justify-content:center;flex-wrap:wrap}
a.button{
text-decoration:none;
padding:.6rem 1rem;
border-radius:8px;
background:var(--accent);
color:white;
font-weight:600;
box-shadow:0 4px 12px rgba(37,99,235,.12);
}
a.secondary{
background:transparent;
color:var(--muted);
border:1px solid rgba(15,23,42,.06);
padding:.5rem .9rem;
border-radius:8px;
text-decoration:none;
}
@media (max-width:420px){ .code{font-size:2.5rem} }
</style>
</head>
<body>
<div class="card" role="alert" aria-labelledby="title">
<p class="code" aria-hidden="true">404</p>
<h2 id="title">Page not found</h2>
<p>We couldn't find the page you're looking for. It may have been moved or removed.</p>
<div class="actions">
<a class="button" href="/">Go to homepage</a>
<a class="secondary" href="javascript:history.back()">Go back</a>
</div>
</div>
</body>
</html>

View file

@ -4,7 +4,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Swift, Villnow & Swift Industries: {{.Occupation}}</title>
<title>SVS {{.Occupation}}</title>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<style>