feat(kol): add SSE streaming for prompt assist
- Extract assist runtime (prompt building, response parsing, finalize) from the worker into kol_assist_runtime so the sync streaming path and the async task path share the same logic. - Add POST /kol/manage/assist/stream as a Server-Sent Events endpoint emitting start/delta/completed/error events, and wire it to a streamAssist helper on kolManageApi that reports ApiClientError with the server's error envelope. - Stream generated content live in KolPromptEditor and KolGenerateView, switching KolPromptEditArea to a boolean aiBusy flag and refining the generator page layout. - Add KolAssistStreamEvent to shared types. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,7 +6,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
amqp "github.com/rabbitmq/amqp091-go"
|
||||
@@ -18,8 +17,6 @@ import (
|
||||
"github.com/geo-platform/tenant-api/internal/tenant/repository"
|
||||
)
|
||||
|
||||
const kolAssistSystemPrompt = "You are a prompt designer for marketing content. Respond ONLY with JSON: {content: string, schema: {variables: [...]}}"
|
||||
|
||||
var errKolAssistConsumerClosed = errors.New("kol assist consumer channel closed")
|
||||
|
||||
type KolAssistWorker struct {
|
||||
@@ -191,24 +188,21 @@ func (w *KolAssistWorker) processTask(ctx context.Context, job tenantapp.KolAssi
|
||||
return fmt.Errorf("decode kol assist request: %w", err)
|
||||
}
|
||||
|
||||
userPrompt, err := buildKolAssistUserPrompt(req)
|
||||
generateReq, err := tenantapp.BuildKolAssistGenerateRequest(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result, err := w.llm.Generate(ctx, llm.GenerateRequest{
|
||||
Prompt: fmt.Sprintf("SYSTEM:\n%s\n\nUSER:\n%s", kolAssistSystemPrompt, userPrompt),
|
||||
ResponseFormat: &llm.ResponseFormat{
|
||||
Type: llm.ResponseFormatTypeJSONObject,
|
||||
Name: "kol_assist_response",
|
||||
Description: "JSON object with content:string and schema:{variables:[]}",
|
||||
},
|
||||
}, nil)
|
||||
result, err := w.llm.Generate(ctx, generateReq, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("generate kol assist content: %w", err)
|
||||
}
|
||||
|
||||
parsed, err := parseKolAssistResponse(result.Content)
|
||||
parsed, err := tenantapp.ParseKolAssistResponse(result.Content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
parsed, err = tenantapp.FinalizeKolAssistResult(req, parsed)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -223,44 +217,3 @@ func (w *KolAssistWorker) processTask(ctx context.Context, job tenantapp.KolAssi
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildKolAssistUserPrompt(req tenantapp.AssistRequest) (string, error) {
|
||||
switch strings.ToLower(strings.TrimSpace(req.Mode)) {
|
||||
case "generate":
|
||||
return req.Description, nil
|
||||
case "optimize":
|
||||
schemaJSON := "null"
|
||||
if req.Schema != nil {
|
||||
payload, err := json.Marshal(req.Schema)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("encode schema: %w", err)
|
||||
}
|
||||
schemaJSON = string(payload)
|
||||
}
|
||||
return fmt.Sprintf(
|
||||
"Optimize the following prompt. Keep variable placeholders intact. Current schema: %s\n\nPROMPT:\n%s",
|
||||
schemaJSON,
|
||||
req.CurrentContent,
|
||||
), nil
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported assist mode %q", req.Mode)
|
||||
}
|
||||
}
|
||||
|
||||
func parseKolAssistResponse(content string) (*tenantapp.KolAssistResult, error) {
|
||||
var result tenantapp.KolAssistResult
|
||||
if err := json.Unmarshal([]byte(content), &result); err != nil {
|
||||
return nil, fmt.Errorf("decode kol assist result: %w", err)
|
||||
}
|
||||
result.Content = strings.TrimSpace(result.Content)
|
||||
if result.Content == "" {
|
||||
return nil, fmt.Errorf("kol assist result content is empty")
|
||||
}
|
||||
if result.Schema.Variables == nil {
|
||||
result.Schema.Variables = []tenantapp.KolVariableDefinition{}
|
||||
}
|
||||
if err := tenantapp.ValidateSchema(result.Schema); err != nil {
|
||||
return nil, fmt.Errorf("invalid kol assist schema: %w", err)
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user