feat: add admin publishing workflow and yar theme
Add Go/Postgres admin APIs, Angular admin UI, manual build flow, asset uploads, markdown import/export, configurable slug generation, and the Yar reading theme. Exclude local docs and generated development artifacts from version control.
This commit is contained in:
parent
b78f4b39c9
commit
f0b50d13ea
121 changed files with 27139 additions and 550 deletions
|
|
@ -24,6 +24,14 @@ type Config struct {
|
|||
Model string
|
||||
}
|
||||
|
||||
type LocalConfig struct {
|
||||
URL string
|
||||
Model string
|
||||
Temperature float64
|
||||
TopP float64
|
||||
NumPredict int
|
||||
}
|
||||
|
||||
type deepSeekMessage struct {
|
||||
Role string `json:"role"`
|
||||
Content string `json:"content"`
|
||||
|
|
@ -58,6 +66,24 @@ type slugResponse struct {
|
|||
Alternatives []string `json:"alternatives"`
|
||||
}
|
||||
|
||||
type localGenerateRequest struct {
|
||||
Model string `json:"model"`
|
||||
Stream bool `json:"stream"`
|
||||
Options localOptions `json:"options"`
|
||||
Prompt string `json:"prompt"`
|
||||
}
|
||||
|
||||
type localOptions struct {
|
||||
Temperature float64 `json:"temperature"`
|
||||
TopP float64 `json:"top_p"`
|
||||
NumPredict int `json:"num_predict"`
|
||||
}
|
||||
|
||||
type localGenerateResponse struct {
|
||||
Response string `json:"response"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func GenerateSlug(ctx context.Context, config Config, title string, summary string) (string, error) {
|
||||
apiKey := strings.TrimSpace(config.APIKey)
|
||||
if apiKey == "" {
|
||||
|
|
@ -168,6 +194,85 @@ JSON format: {"slug":"example-slug","alternatives":["another-slug"]}`,
|
|||
return slug, nil
|
||||
}
|
||||
|
||||
func GenerateLocalSlug(ctx context.Context, config LocalConfig, title string, summary string) (string, error) {
|
||||
url := strings.TrimSpace(config.URL)
|
||||
if url == "" {
|
||||
return "", errors.New("local LLM URL is empty")
|
||||
}
|
||||
model := strings.TrimSpace(config.Model)
|
||||
if model == "" {
|
||||
return "", errors.New("local LLM model is empty")
|
||||
}
|
||||
temperature := config.Temperature
|
||||
if temperature == 0 {
|
||||
temperature = 0.1
|
||||
}
|
||||
topP := config.TopP
|
||||
if topP == 0 {
|
||||
topP = 0.8
|
||||
}
|
||||
numPredict := config.NumPredict
|
||||
if numPredict == 0 {
|
||||
numPredict = 32
|
||||
}
|
||||
|
||||
prompt := fmt.Sprintf(`Convert the following Chinese blog title into a concise English URL slug.
|
||||
Output only the slug. Lowercase only. Use hyphens. Max 8 words.
|
||||
Title: %s`, title)
|
||||
if strings.TrimSpace(summary) != "" {
|
||||
prompt += "\nSummary: " + summary
|
||||
}
|
||||
|
||||
payload := localGenerateRequest{
|
||||
Model: model,
|
||||
Stream: false,
|
||||
Options: localOptions{
|
||||
Temperature: temperature,
|
||||
TopP: topP,
|
||||
NumPredict: numPredict,
|
||||
},
|
||||
Prompt: prompt,
|
||||
}
|
||||
body, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
client := &http.Client{Timeout: 30 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
return "", fmt.Errorf("local LLM returned %s: %s", resp.Status, strings.TrimSpace(string(respBody)))
|
||||
}
|
||||
|
||||
var generated localGenerateResponse
|
||||
if err := json.Unmarshal(respBody, &generated); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if generated.Error != "" {
|
||||
return "", fmt.Errorf("local LLM error: %s", generated.Error)
|
||||
}
|
||||
slug := sanitizeSlug(generated.Response)
|
||||
if slug == "" {
|
||||
return "", errors.New("local LLM returned an empty slug")
|
||||
}
|
||||
return slug, nil
|
||||
}
|
||||
|
||||
func sanitizeSlug(slug string) string {
|
||||
slug = strings.ToLower(strings.TrimSpace(slug))
|
||||
re := regexp.MustCompile(`[^a-z0-9]+`)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue