321 lines
12 KiB
Go
321 lines
12 KiB
Go
package site
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"encoding/json"
|
|
"fmt"
|
|
"html/template"
|
|
"log"
|
|
"regexp"
|
|
"strings"
|
|
"sync"
|
|
"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{}
|
|
|
|
ctx := context.TODO()
|
|
|
|
occupationPrompt := 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:
|
|
`)
|
|
|
|
log.Printf("Parsing service %s…", rawservice)
|
|
site.Occupation, err = cp.Complete(ctx, fmt.Sprintf(occupationPrompt, rawservice), nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parsing service: %w", err)
|
|
}
|
|
log.Printf("Done parsing service %s.", site.Occupation)
|
|
site.Theme = Theme{ // TODO
|
|
AccentColor: Color{
|
|
Hex: "#FF5733",
|
|
},
|
|
SecondaryColor: Color{
|
|
Hex: "#33C1FF",
|
|
},
|
|
BackgroundColor: Color{
|
|
// Creamy off-white
|
|
Hex: "#FFF8E7",
|
|
},
|
|
}
|
|
|
|
taglinePrompt := 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.
|
|
`)
|
|
whatWeDoPrompt := 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.
|
|
`)
|
|
chandlerPrompt := 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.
|
|
`)
|
|
ericPrompt := 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.
|
|
`)
|
|
isaacPrompt := 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.
|
|
`)
|
|
testimonialsPrompt := norm(`
|
|
Generate three satirical testimonials from satisfied clients of SVS, a %v company.
|
|
Testimonials should be from well-known figures, possibly historical (say, a former US President).
|
|
When possible, the person quoted should have some tenuous connection to the services provided by SVS.
|
|
This may be heavily ironic, say Abe Lincoln giving a testimonial for SVS's web design services, or Helen Keller praising their audio engineering.
|
|
Other possible sources include George Washington, Thomas Edison, Nikola Tesla, Marie Curie, Albert Einstein, Winston Churchill, Franklin D. Roosevelt, John F. Kennedy, Ronald Reagan, Margaret Thatcher, and Martin Luther King Jr.
|
|
Each testimonial should be 1-2 sentences long, and include an author and their position.
|
|
For example, if SVS did web design, a testimonial might be from "Barack Obama", a "Poolhall acquaintance":
|
|
"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!"
|
|
Do not include quotes around the testimonial text.
|
|
Return the testimonials in JSON format, as an array of objects with "name", "position", and "quote" properties.
|
|
`)
|
|
schema := map[string]any{
|
|
"name": "testimonials",
|
|
"strict": true,
|
|
"schema": map[string]any{
|
|
"type": "array",
|
|
"minItems": 3,
|
|
"maxItems": 3,
|
|
"items": map[string]any{
|
|
"type": "object",
|
|
"properties": map[string]any{
|
|
"Name": map[string]any{
|
|
"type": "string",
|
|
"description": "Name of the person giving the testimonial",
|
|
},
|
|
"Position": map[string]any{
|
|
"type": "string",
|
|
"description": "Position or title of the person giving the testimonial",
|
|
},
|
|
"Quote": map[string]any{
|
|
"type": "string",
|
|
"description": "Testimonial text",
|
|
},
|
|
},
|
|
"required": []string{"Name", "Position", "Quote"},
|
|
"additionalProperties": false,
|
|
},
|
|
},
|
|
}
|
|
|
|
var (
|
|
taglineResult string
|
|
whatWeDoResult string
|
|
chandlerBioResult string
|
|
ericBioResult string
|
|
isaacBioResult string
|
|
rawTestimonials string
|
|
)
|
|
|
|
errCh := make(chan error, 6)
|
|
var wg sync.WaitGroup
|
|
wg.Add(6)
|
|
|
|
go func(p string) {
|
|
defer wg.Done()
|
|
var e error
|
|
log.Println("Generating tagline...")
|
|
taglineResult, e = cp.Complete(ctx, fmt.Sprintf(p, site.Occupation), nil)
|
|
if e != nil {
|
|
errCh <- fmt.Errorf("generating tagline: %w", e)
|
|
}
|
|
log.Printf("Generated tagline: %s", taglineResult)
|
|
}(taglinePrompt)
|
|
|
|
go func(p string) {
|
|
defer wg.Done()
|
|
var e error
|
|
log.Println("Generating what we do...")
|
|
whatWeDoResult, e = cp.Complete(ctx, fmt.Sprintf(p, site.Occupation), nil)
|
|
if e != nil {
|
|
errCh <- fmt.Errorf("generating what we do: %w", e)
|
|
}
|
|
}(whatWeDoPrompt)
|
|
|
|
go func(p string) {
|
|
defer wg.Done()
|
|
var e error
|
|
log.Println("Generating Chandler bio...")
|
|
chandlerBioResult, e = cp.Complete(ctx, fmt.Sprintf(p, site.Occupation), nil)
|
|
if e != nil {
|
|
errCh <- fmt.Errorf("generating Chandler bio: %w", e)
|
|
}
|
|
}(chandlerPrompt)
|
|
|
|
go func(p string) {
|
|
defer wg.Done()
|
|
var e error
|
|
log.Println("Generating Eric bio...")
|
|
ericBioResult, e = cp.Complete(ctx, fmt.Sprintf(p, site.Occupation), nil)
|
|
if e != nil {
|
|
errCh <- fmt.Errorf("generating Eric bio: %w", e)
|
|
}
|
|
}(ericPrompt)
|
|
|
|
go func(p string) {
|
|
defer wg.Done()
|
|
var e error
|
|
log.Println("Generating Isaac bio...")
|
|
isaacBioResult, e = cp.Complete(ctx, fmt.Sprintf(p, site.Occupation), nil)
|
|
if e != nil {
|
|
errCh <- fmt.Errorf("generating Isaac bio: %w", e)
|
|
}
|
|
}(isaacPrompt)
|
|
|
|
go func(p string) {
|
|
defer wg.Done()
|
|
var e error
|
|
log.Println("Generating testimonials...")
|
|
rawTestimonials, e = cp.Complete(ctx, fmt.Sprintf(p, site.Occupation), schema)
|
|
if e != nil {
|
|
errCh <- fmt.Errorf("generating testimonials: %w", e)
|
|
}
|
|
}(testimonialsPrompt)
|
|
|
|
wg.Wait()
|
|
close(errCh)
|
|
|
|
var firstErr error
|
|
for e := range errCh {
|
|
if firstErr == nil && e != nil {
|
|
firstErr = e
|
|
}
|
|
}
|
|
if firstErr != nil {
|
|
return nil, firstErr
|
|
}
|
|
|
|
site.Tagline = taglineResult
|
|
site.WhatWeDo = template.HTML(whatWeDoResult)
|
|
site.ChandlerBio = template.HTML(chandlerBioResult)
|
|
site.EricBio = template.HTML(ericBioResult)
|
|
site.IsaacBio = template.HTML(isaacBioResult)
|
|
|
|
fmt.Println("Raw testimonials: %s\n", rawTestimonials)
|
|
err = json.Unmarshal([]byte(rawTestimonials), &site.Testimonials)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unmarshaling testimonials: %w", err)
|
|
}
|
|
|
|
site.CurrentYear = time.Now().Year()
|
|
return
|
|
}
|