diff --git a/api/api_llm.go b/api/api_llm.go index 32f99a6..8ea40c6 100644 --- a/api/api_llm.go +++ b/api/api_llm.go @@ -51,18 +51,18 @@ func loadLLMConfig() { log.Println("Warning: Gemini API key not found.") } - // Initialize prompts directory + // Initialize trackers directory exe, _ := os.Executable() - promptsDir := filepath.Join(filepath.Dir(exe), "..", "api", "prompts") + promptsDir := filepath.Join(filepath.Dir(exe), "..", "api", "trackers") if _, err := os.Stat(promptsDir); os.IsNotExist(err) { - promptsDir = "prompts" // Dev fallback + promptsDir = "trackers" // Dev fallback } lib.InitPrompts(promptsDir) - log.Printf("Prompts directory set to: %s", lib.PromptsDir()) + log.Printf("Prompts directory set to: %s", lib.TrackerPromptsDir()) } -// callLLMForPrompt is the main entry point for turning user text into a structured prompt. -func callLLMForPrompt(userInput string, dossierID string) (*ExtractionResult, error) { +// callLLMForTracker is the main entry point for turning user text into a structured prompt. +func callLLMForTracker(userInput string, dossierID string) (*ExtractionResult, error) { triage, err := runTriage(userInput, dossierID) if err != nil { return nil, err @@ -71,7 +71,7 @@ func callLLMForPrompt(userInput string, dossierID string) (*ExtractionResult, er return &ExtractionResult{Error: triage.Error}, nil } - existingTypes := getExistingPromptTypes(dossierID) // Assuming db is accessible in api/main + existingTypes := getExistingTrackerTypes(dossierID) // Assuming db is accessible in api/main return runExtraction(userInput, triage.Category, triage.Language, dossierID, existingTypes) } @@ -79,7 +79,7 @@ func callLLMForPrompt(userInput string, dossierID string) (*ExtractionResult, er // --- Local Prompt Handling & DB Functions --- func loadPrompt(name string) (string, error) { - path := filepath.Join(lib.PromptsDir(), name+".md") + path := filepath.Join(lib.TrackerPromptsDir(), name+".md") data, err := os.ReadFile(path) if err != nil { return "", err @@ -233,10 +233,10 @@ func runExtraction(userInput, category, language, dossierID string, existingType } -func getExistingPromptTypes(dossierID string) map[string][]string { - result, err := lib.PromptDistinctTypes(dossierID) +func getExistingTrackerTypes(dossierID string) map[string][]string { + result, err := lib.TrackerDistinctTypes(dossierID) if err != nil { - log.Printf("Failed to get existing prompt types: %v", err) + log.Printf("Failed to get existing tracker types: %v", err) return make(map[string][]string) } return result @@ -252,6 +252,6 @@ func callSonnet(prompt string) (string, error) { } func callSonnetWithRetry(prompt string, maxRetries int, baseDelay time.Duration) (string, error) { - // ... implementation remains the same, but is not called by the main prompt generation logic. + // ... implementation remains the same, but is not called by the main tracker generation logic. return "", fmt.Errorf("callSonnet is deprecated") } \ No newline at end of file diff --git a/api/api_prompts.go b/api/api_trackers.go similarity index 79% rename from api/api_prompts.go rename to api/api_trackers.go index 59a5f41..ea075ad 100644 --- a/api/api_prompts.go +++ b/api/api_trackers.go @@ -11,7 +11,7 @@ import ( ) // PromptResponse is the API representation of a prompt, including dynamic data. -type PromptResponse struct { +type TrackerResponse struct { ID string `json:"id"` Category string `json:"category"` Type string `json:"type"` @@ -24,21 +24,21 @@ type PromptResponse struct { Active bool `json:"active"` IsDue bool `json:"is_due"` - // Last response (for pre-filling) - restored from lib.Prompt + // Last response (for pre-filling) - restored from lib.Tracker LastResponse json.RawMessage `json:"last_response,omitempty"` LastResponseRaw string `json:"last_response_raw,omitempty"` LastResponseAt int64 `json:"last_response_at,omitempty"` } -type PromptRespondRequest struct { - PromptID string `json:"prompt_id"` +type TrackerRespondRequest struct { + TrackerID string `json:"tracker_id"` Response string `json:"response"` // JSON string ResponseRaw string `json:"response_raw"` // what they typed Action string `json:"action"` // "respond", "skip", "dismiss" } // GET /api/prompts?dossier=X -func handlePrompts(w http.ResponseWriter, r *http.Request) { +func handleTrackers(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") dossierHex := r.URL.Query().Get("dossier") @@ -48,13 +48,13 @@ func handlePrompts(w http.ResponseWriter, r *http.Request) { } dossierID := dossierHex - var prompts []*lib.Prompt + var trackers []*lib.Tracker var err error if r.URL.Query().Get("all") == "1" { - prompts, err = lib.PromptQueryAll(dossierID) + trackers, err = lib.TrackerQueryAll(dossierID) } else { - prompts, err = lib.PromptQueryActive(dossierID) + trackers, err = lib.TrackerQueryActive(dossierID) } if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) @@ -63,14 +63,14 @@ func handlePrompts(w http.ResponseWriter, r *http.Request) { // Ensure there's always a freeform prompt hasFreeform := false - for _, p := range prompts { + for _, p := range trackers { if p.InputType == "freeform" && p.Active { hasFreeform = true break } } if !hasFreeform { - freeform := &lib.Prompt{ + freeform := &lib.Tracker{ DossierID: dossierID, Category: "note", Type: "freeform", @@ -78,18 +78,18 @@ func handlePrompts(w http.ResponseWriter, r *http.Request) { InputType: "freeform", Active: true, } - if err := lib.PromptAdd(freeform); err == nil { - prompts = append(prompts, freeform) + if err := lib.TrackerAdd(freeform); err == nil { + trackers = append(trackers, freeform) } } - result := make([]PromptResponse, 0, len(prompts)) + result := make([]TrackerResponse, 0, len(trackers)) now := time.Now().Unix() - for _, p := range prompts { + for _, p := range trackers { isDue := p.NextAsk <= now || p.NextAsk == 0 || p.InputType == "freeform" - pr := PromptResponse{ - ID: p.PromptID, + pr := TrackerResponse{ + ID: p.TrackerID, Category: p.Category, Type: p.Type, Question: p.Question, @@ -117,7 +117,7 @@ func handlePrompts(w http.ResponseWriter, r *http.Request) { // POST /api/prompts/respond -func handlePromptRespond(w http.ResponseWriter, r *http.Request) { +func handleTrackerRespond(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") if r.Method != "POST" { @@ -125,32 +125,32 @@ func handlePromptRespond(w http.ResponseWriter, r *http.Request) { return } - var req PromptRespondRequest + var req TrackerRespondRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "invalid request", http.StatusBadRequest) return } - promptID := req.PromptID - if promptID == "" { - http.Error(w, "prompt_id required", http.StatusBadRequest) + trackerID := req.TrackerID + if trackerID == "" { + http.Error(w, "tracker_id required", http.StatusBadRequest) return } var err error - var newPrompt *lib.Prompt + var newTracker *lib.Tracker switch req.Action { case "respond", "": - err = lib.PromptRespond(promptID, req.Response, req.ResponseRaw) - // Check if this is a freeform prompt - if so, generate new prompt from input + err = lib.TrackerRespond(trackerID, req.Response, req.ResponseRaw) + // Check if this is a freeform tracker - if so, generate new tracker from input if err == nil && req.ResponseRaw != "" { - newPrompt, _ = tryGeneratePromptFromFreeform(promptID, req.ResponseRaw) + newTracker, _ = tryGenerateTrackerFromFreeform(trackerID, req.ResponseRaw) } case "skip": - err = lib.PromptSkip(promptID) + err = lib.TrackerSkip(trackerID) case "dismiss": - err = lib.PromptDismiss(promptID) + err = lib.TrackerDismiss(trackerID) default: http.Error(w, "invalid action", http.StatusBadRequest) return @@ -163,35 +163,35 @@ func handlePromptRespond(w http.ResponseWriter, r *http.Request) { result := map[string]interface{}{ "ok": true, - "prompt_id": req.PromptID, + "tracker_id": req.TrackerID, "action": req.Action, } - if newPrompt != nil { - np := promptToAPI(newPrompt) + if newTracker != nil { + np := promptToAPI(newTracker) result["new_prompt"] = np } json.NewEncoder(w).Encode(result) } // Router for /api/prompts and /api/prompts/* -func handlePromptsRouter(w http.ResponseWriter, r *http.Request) { +func handleTrackersRouter(w http.ResponseWriter, r *http.Request) { path := r.URL.Path switch { case path == "/api/prompts" && r.Method == "GET": - handlePrompts(w, r) + handleTrackers(w, r) case path == "/api/prompts" && r.Method == "POST": - handlePromptCreate(w, r) + handleTrackerCreate(w, r) case path == "/api/prompts/respond": - handlePromptRespond(w, r) + handleTrackerRespond(w, r) default: http.NotFound(w, r) } } -// tryGeneratePromptFromFreeform checks if the prompt is freeform and generates a new prompt from user input -func tryGeneratePromptFromFreeform(promptID string, userInput string) (*lib.Prompt, string) { - p, err := lib.PromptGet(promptID) +// tryGenerateTrackerFromFreeform checks if the tracker is freeform and generates a new tracker from user input +func tryGenerateTrackerFromFreeform(trackerID string, userInput string) (*lib.Tracker, string) { + p, err := lib.TrackerGet(trackerID) if err != nil || p == nil { return nil, "" } @@ -200,9 +200,9 @@ func tryGeneratePromptFromFreeform(promptID string, userInput string) (*lib.Prom return nil, "" } - generated, err := callLLMForPrompt(userInput, p.DossierID) + generated, err := callLLMForTracker(userInput, p.DossierID) if err != nil { - log.Printf("Failed to generate prompt from freeform: %v", err) + log.Printf("Failed to generate tracker from freeform: %v", err) return nil, "" } @@ -254,7 +254,7 @@ func tryGeneratePromptFromFreeform(promptID string, userInput string) (*lib.Prom scheduleJSON, _ := json.Marshal(generated.Schedule) inputConfigJSON, _ := json.Marshal(generated.InputConfig) - newPrompt := &lib.Prompt{ + newTracker := &lib.Tracker{ DossierID: p.DossierID, Category: generated.Category, Type: generated.Type, @@ -270,25 +270,25 @@ func tryGeneratePromptFromFreeform(promptID string, userInput string) (*lib.Prom // NOTE: Don't set LastResponse here - backfilled entries are historical. // LastResponse will be set when user actually submits a response for "today". - if err := lib.PromptAdd(newPrompt); err != nil { + if err := lib.TrackerAdd(newTracker); err != nil { log.Printf("Failed to create prompt: %v", err) return nil, "" } - // Update the entries we just created to link them to this prompt via SearchKey + // Update the entries we just created to link them to this tracker via SearchKey for _, entry := range createdEntries { - entry.SearchKey = newPrompt.PromptID + entry.SearchKey = newTracker.TrackerID if err := lib.EntryAdd(entry); err != nil { log.Printf("Failed to update entry search_key: %v", err) } } - log.Printf("Created prompt from freeform: %s (%s/%s) and linked %d entries", newPrompt.Question, newPrompt.Category, newPrompt.Type, len(createdEntries)) - return newPrompt, primaryEntryValue + log.Printf("Created tracker from freeform: %s (%s/%s) and linked %d entries", newTracker.Question, newTracker.Category, newTracker.Type, len(createdEntries)) + return newTracker, primaryEntryValue } // promptToAPI converts a Prompt to API response format, now much simpler -func promptToAPI(p *lib.Prompt) map[string]interface{} { +func promptToAPI(p *lib.Tracker) map[string]interface{} { now := time.Now().Unix() isDue := p.NextAsk <= now || p.NextAsk == 0 || p.InputType == "freeform" @@ -296,7 +296,7 @@ func promptToAPI(p *lib.Prompt) map[string]interface{} { json.Unmarshal([]byte(p.InputConfig), &inputConfig) result := map[string]interface{}{ - "id": p.PromptID, + "id": p.TrackerID, "category": p.Category, "type": p.Type, "question": p.Question, @@ -311,7 +311,7 @@ func promptToAPI(p *lib.Prompt) map[string]interface{} { } -func handlePromptCreate(w http.ResponseWriter, r *http.Request) { +func handleTrackerCreate(w http.ResponseWriter, r *http.Request) { // This function needs to be updated to use the new Prompt struct http.Error(w, "Not implemented", http.StatusNotImplemented) } diff --git a/api/api_v1.go b/api/api_v1.go index 6013aac..7dd9078 100644 --- a/api/api_v1.go +++ b/api/api_v1.go @@ -414,7 +414,7 @@ type ParseEntryResponse struct { Timestamp int64 `json:"timestamp"` } -type ParsePromptResponse struct { +type ParseTrackerResponse struct { ID string `json:"id"` Category string `json:"category"` Type string `json:"type"` @@ -428,7 +428,7 @@ type ParseResponse struct { Category string `json:"category"` Type string `json:"type"` Entries []ParseEntryResponse `json:"entries"` - Prompt *ParsePromptResponse `json:"prompt,omitempty"` + Tracker *ParseTrackerResponse `json:"prompt,omitempty"` Error string `json:"error,omitempty"` } @@ -460,7 +460,7 @@ func v1Parse(w http.ResponseWriter, r *http.Request, dossierID string) { } // Run triage + extraction - generated, err := callLLMForPrompt(req.Input, dossierID) + generated, err := callLLMForTracker(req.Input, dossierID) if err != nil { v1Error(w, err.Error(), http.StatusInternalServerError) return @@ -505,13 +505,13 @@ func v1Parse(w http.ResponseWriter, r *http.Request, dossierID string) { }) } - // Create prompt if there's a follow-up question with schedule + // Create tracker if there's a follow-up question with schedule if generated.Question != "" && len(generated.Schedule) > 0 { nextAsk := calculateNextAskFromSchedule(generated.Schedule, now) scheduleJSON, _ := json.Marshal(generated.Schedule) inputConfigJSON, _ := json.Marshal(generated.InputConfig) - prompt := &lib.Prompt{ + tracker := &lib.Tracker{ DossierID: dossierID, Category: generated.Category, Type: generated.Type, @@ -527,23 +527,23 @@ func v1Parse(w http.ResponseWriter, r *http.Request, dossierID string) { // Pre-fill last response from initial extracted data if len(generated.Entries) > 0 && generated.Entries[0].Data != nil { initialData, _ := json.Marshal(generated.Entries[0].Data) - prompt.LastResponse = string(initialData) - prompt.LastResponseRaw = generated.Entries[0].Value - prompt.LastResponseAt = now.Unix() + tracker.LastResponse = string(initialData) + tracker.LastResponseRaw = generated.Entries[0].Value + tracker.LastResponseAt = now.Unix() } - if err := lib.PromptAdd(prompt); err == nil { + if err := lib.TrackerAdd(tracker); err == nil { var inputConfig any - json.Unmarshal([]byte(prompt.InputConfig), &inputConfig) + json.Unmarshal([]byte(tracker.InputConfig), &inputConfig) - resp.Prompt = &ParsePromptResponse{ - ID: prompt.PromptID, - Category: prompt.Category, - Type: prompt.Type, - Question: prompt.Question, - InputType: prompt.InputType, + resp.Tracker = &ParseTrackerResponse{ + ID: tracker.TrackerID, + Category: tracker.Category, + Type: tracker.Type, + Question: tracker.Question, + InputType: tracker.InputType, InputConfig: inputConfig, - NextAsk: prompt.NextAsk, + NextAsk: tracker.NextAsk, } } } @@ -564,7 +564,7 @@ func v1Prompts(w http.ResponseWriter, r *http.Request, dossierID string) { } q := r.URL.Query() - filter := &lib.PromptFilter{DossierID: dossierID} + filter := &lib.TrackerFilter{DossierID: dossierID} if cat := q.Get("category"); cat != "" { filter.Category = cat } @@ -575,16 +575,16 @@ func v1Prompts(w http.ResponseWriter, r *http.Request, dossierID string) { filter.ActiveOnly = true } - prompts, err := lib.PromptList(filter) + trackers, err := lib.TrackerList(filter) if err != nil { v1Error(w, err.Error(), http.StatusInternalServerError) return } var result []map[string]any - for _, p := range prompts { + for _, p := range trackers { result = append(result, map[string]any{ - "id": p.PromptID, + "id": p.TrackerID, "category": p.Category, "type": p.Type, "question": p.Question, @@ -766,7 +766,7 @@ func v1Router(w http.ResponseWriter, r *http.Request) { v1Audit(w, r, parts[1]) // GET /dossiers/{id}/prompts - case len(parts) == 3 && parts[0] == "dossiers" && parts[2] == "prompts" && r.Method == "GET": + case len(parts) == 3 && parts[0] == "dossiers" && parts[2] == "trackers" && r.Method == "GET": v1Prompts(w, r, parts[1]) // POST /dossiers/{id}/parse diff --git a/api/llm_types.go b/api/llm_types.go index 78facda..82cad48 100644 --- a/api/llm_types.go +++ b/api/llm_types.go @@ -46,7 +46,7 @@ type FormField struct { Options []string `json:"options,omitempty"` } -// ScheduleSlot defines when a prompt should be shown. +// ScheduleSlot defines when a tracker should be shown. // Supports both old format (Time string) and new format (Times []string). type ScheduleSlot struct { Days []string `json:"days"` diff --git a/api/main.go b/api/main.go index 9056208..f2f8ba2 100644 --- a/api/main.go +++ b/api/main.go @@ -52,8 +52,8 @@ func main() { http.HandleFunc("/api/access", handleAccess) http.HandleFunc("/api/audit", handleAudit) http.HandleFunc("/api/entries", handleEntries) - http.HandleFunc("/api/prompts", handlePromptsRouter) - http.HandleFunc("/api/prompts/", handlePromptsRouter) + http.HandleFunc("/api/prompts", handleTrackersRouter) + http.HandleFunc("/api/prompts/", handleTrackersRouter) // http.HandleFunc("/api/prompt/generate", handlePromptGenerate) // REMOVED: Deprecated // Add the missing freeform handler diff --git a/api/prompts/consultation.md b/api/tracker_prompts/consultation.md similarity index 100% rename from api/prompts/consultation.md rename to api/tracker_prompts/consultation.md diff --git a/api/prompts/default.md b/api/tracker_prompts/default.md similarity index 100% rename from api/prompts/default.md rename to api/tracker_prompts/default.md diff --git a/api/prompts/exercise.md b/api/tracker_prompts/exercise.md similarity index 100% rename from api/prompts/exercise.md rename to api/tracker_prompts/exercise.md diff --git a/api/prompts/family_history.md b/api/tracker_prompts/family_history.md similarity index 100% rename from api/prompts/family_history.md rename to api/tracker_prompts/family_history.md diff --git a/api/prompts/fertility.md b/api/tracker_prompts/fertility.md similarity index 100% rename from api/prompts/fertility.md rename to api/tracker_prompts/fertility.md diff --git a/api/prompts/history.md b/api/tracker_prompts/history.md similarity index 100% rename from api/prompts/history.md rename to api/tracker_prompts/history.md diff --git a/api/prompts/medication.md b/api/tracker_prompts/medication.md similarity index 100% rename from api/prompts/medication.md rename to api/tracker_prompts/medication.md diff --git a/api/prompts/nutrition.md b/api/tracker_prompts/nutrition.md similarity index 100% rename from api/prompts/nutrition.md rename to api/tracker_prompts/nutrition.md diff --git a/api/prompts/supplement.md b/api/tracker_prompts/supplement.md similarity index 100% rename from api/prompts/supplement.md rename to api/tracker_prompts/supplement.md diff --git a/api/prompts/symptom.md b/api/tracker_prompts/symptom.md similarity index 100% rename from api/prompts/symptom.md rename to api/tracker_prompts/symptom.md diff --git a/api/prompts/therapy.md b/api/tracker_prompts/therapy.md similarity index 100% rename from api/prompts/therapy.md rename to api/tracker_prompts/therapy.md diff --git a/api/prompts/triage.md b/api/tracker_prompts/triage.md similarity index 100% rename from api/prompts/triage.md rename to api/tracker_prompts/triage.md diff --git a/api/prompts/vital.md b/api/tracker_prompts/vital.md similarity index 100% rename from api/prompts/vital.md rename to api/tracker_prompts/vital.md diff --git a/lib/llm.go b/lib/llm.go index 79eb3d2..14fddf2 100644 --- a/lib/llm.go +++ b/lib/llm.go @@ -11,15 +11,15 @@ import ( var promptsDir string -// InitPrompts sets the directory where prompt files are located. +// InitPrompts sets the directory where tracker files are located. // This must be called by the main application at startup. func InitPrompts(path string) { promptsDir = path } -// PromptsDir returns the configured prompts directory. -// This is used by local prompt loading functions in consumer packages. -func PromptsDir() string { +// TrackerPromptsDir returns the configured trackers directory. +// This is used by local tracker loading functions in consumer packages. +func TrackerPromptsDir() string { return promptsDir } diff --git a/lib/prompt.go b/lib/tracker.go similarity index 57% rename from lib/prompt.go rename to lib/tracker.go index e4f6906..a85185b 100644 --- a/lib/prompt.go +++ b/lib/tracker.go @@ -7,10 +7,10 @@ import ( "time" ) -// PromptAdd inserts a new prompt. Generates PromptID if empty. -func PromptAdd(p *Prompt) error { - if p.PromptID == "" { - p.PromptID = NewID() +// TrackerAdd inserts a new prompt. Generates TrackerID if empty. +func TrackerAdd(p *Tracker) error { + if p.TrackerID == "" { + p.TrackerID = NewID() } now := time.Now().Unix() if p.CreatedAt == 0 { @@ -20,31 +20,31 @@ func PromptAdd(p *Prompt) error { if p.Active == false && p.Dismissed == false { p.Active = true // default to active } - return Save("prompts", p) + return Save("trackers", p) } -// PromptModify updates an existing prompt -func PromptModify(p *Prompt) error { +// TrackerModify updates an existing prompt +func TrackerModify(p *Tracker) error { p.UpdatedAt = time.Now().Unix() - return Save("prompts", p) + return Save("trackers", p) } -// PromptDelete removes a prompt -func PromptDelete(promptID string) error { - return Delete("prompts", "prompt_id", promptID) +// TrackerDelete removes a prompt +func TrackerDelete(trackerID string) error { + return Delete("trackers", "tracker_id", trackerID) } -// PromptGet retrieves a single prompt by ID -func PromptGet(promptID string) (*Prompt, error) { - p := &Prompt{} - return p, Load("prompts", promptID, p) +// TrackerGet retrieves a single tracker by ID +func TrackerGet(trackerID string) (*Tracker, error) { + p := &Tracker{} + return p, Load("trackers", trackerID, p) } -// PromptQueryActive retrieves active prompts due for a dossier -func PromptQueryActive(dossierID string) ([]*Prompt, error) { +// TrackerQueryActive retrieves active trackers due for a dossier +func TrackerQueryActive(dossierID string) ([]*Tracker, error) { now := time.Now().Unix() - var result []*Prompt - err := Query(`SELECT * FROM prompts + var result []*Tracker + err := Query(`SELECT * FROM trackers WHERE dossier_id = ? AND active = 1 AND dismissed = 0 AND (expires_at = 0 OR expires_at > ?) ORDER BY @@ -53,20 +53,20 @@ func PromptQueryActive(dossierID string) ([]*Prompt, error) { return result, err } -// PromptQueryAll retrieves all prompts for a dossier (including inactive) -func PromptQueryAll(dossierID string) ([]*Prompt, error) { - var result []*Prompt - err := Query(`SELECT * FROM prompts WHERE dossier_id = ? ORDER BY active DESC, time_of_day, created_at`, +// TrackerQueryAll retrieves all trackers for a dossier (including inactive) +func TrackerQueryAll(dossierID string) ([]*Tracker, error) { + var result []*Tracker + err := Query(`SELECT * FROM trackers WHERE dossier_id = ? ORDER BY active DESC, time_of_day, created_at`, []any{dossierID}, &result) return result, err } -// PromptRespond records a response and advances next_ask -func PromptRespond(promptID string, response, responseRaw string) error { +// TrackerRespond records a response and advances next_ask +func TrackerRespond(trackerID string, response, responseRaw string) error { now := time.Now().Unix() - // Get current prompt to calculate next_ask - p, err := PromptGet(promptID) + // Get current tracker to calculate next_ask + p, err := TrackerGet(trackerID) if err != nil { return err } @@ -77,22 +77,22 @@ func PromptRespond(promptID string, response, responseRaw string) error { p.NextAsk = calculateNextAsk(p.Frequency, p.TimeOfDay, now) p.UpdatedAt = now - if err := Save("prompts", p); err != nil { + if err := Save("trackers", p); err != nil { return err } - // Create entry for certain prompt types - if err := promptCreateEntry(p, response, now); err != nil { + // Create entry for certain tracker types + if err := trackerCreateEntry(p, response, now); err != nil { // Log but don't fail the response - log.Printf("Failed to create entry for prompt %s: %v", promptID, err) + log.Printf("Failed to create entry for tracker %s: %v", trackerID, err) } return nil } -// promptCreateEntry creates an entry from a prompt response +// trackerCreateEntry creates an entry from a tracker response // Uses the prompt's category/type directly - no hardcoded mappings -func promptCreateEntry(p *Prompt, response string, timestamp int64) error { +func trackerCreateEntry(p *Tracker, response string, timestamp int64) error { // Skip freeform/note types for now if p.InputType == "freeform" { return nil @@ -105,8 +105,8 @@ func promptCreateEntry(p *Prompt, response string, timestamp int64) error { Type: p.Type, Value: responseToValue(response), Timestamp: timestamp, - Data: fmt.Sprintf(`{"response":%s,"source":"prompt","prompt_id":"%s"}`, response, p.PromptID), - SearchKey: p.PromptID, // Foreign key to link entry back to its prompt + Data: fmt.Sprintf(`{"response":%s,"source":"prompt","tracker_id":"%s"}`, response, p.TrackerID), + SearchKey: p.TrackerID, // Foreign key to link entry back to its prompt } return EntryAdd(e) } @@ -141,27 +141,27 @@ func responseToValue(response string) string { return response } -// PromptDismiss marks a prompt as dismissed -func PromptDismiss(promptID string) error { - p, err := PromptGet(promptID) +// TrackerDismiss marks a tracker as dismissed +func TrackerDismiss(trackerID string) error { + p, err := TrackerGet(trackerID) if err != nil { return err } p.Dismissed = true p.UpdatedAt = time.Now().Unix() - return Save("prompts", p) + return Save("trackers", p) } -// PromptSkip advances next_ask to tomorrow without recording a response -func PromptSkip(promptID string) error { - p, err := PromptGet(promptID) +// TrackerSkip advances next_ask to tomorrow without recording a response +func TrackerSkip(trackerID string) error { + p, err := TrackerGet(trackerID) if err != nil { return err } now := time.Now().Unix() p.NextAsk = now + 24*60*60 p.UpdatedAt = now - return Save("prompts", p) + return Save("trackers", p) } // calculateNextAsk determines when to ask again based on frequency diff --git a/lib/types.go b/lib/types.go index 5020310..7f01261 100644 --- a/lib/types.go +++ b/lib/types.go @@ -344,8 +344,8 @@ type AuditEntry struct { } // Prompt represents a scheduled question or tracker (decrypted) -type Prompt struct { - PromptID string `db:"prompt_id,pk"` +type Tracker struct { + TrackerID string `db:"tracker_id,pk"` DossierID string `db:"dossier_id"` Category string `db:"category"` Type string `db:"type"` diff --git a/lib/v2.go b/lib/v2.go index a9eaa66..7e348c8 100644 --- a/lib/v2.go +++ b/lib/v2.go @@ -481,7 +481,7 @@ func AuditList(f *AuditFilter) ([]*AuditEntry, error) { // --- PROMPT --- -type PromptFilter struct { +type TrackerFilter struct { DossierID string Category string Type string @@ -489,27 +489,27 @@ type PromptFilter struct { Limit int } -func PromptWrite(prompts ...*Prompt) error { - if len(prompts) == 0 { +func TrackerWrite(trackers ...*Tracker) error { + if len(trackers) == 0 { return nil } - for _, p := range prompts { - if p.PromptID == "" { - p.PromptID = NewID() + for _, p := range trackers { + if p.TrackerID == "" { + p.TrackerID = NewID() } } - if len(prompts) == 1 { - return Save("prompts", prompts[0]) + if len(trackers) == 1 { + return Save("trackers", trackers[0]) } - return Save("prompts", prompts) + return Save("trackers", trackers) } -func PromptRemove(ids ...string) error { - return deleteByIDs("prompts", "prompt_id", ids) +func TrackerRemove(ids ...string) error { + return deleteByIDs("trackers", "tracker_id", ids) } -func PromptList(f *PromptFilter) ([]*Prompt, error) { - q := "SELECT * FROM prompts WHERE 1=1" +func TrackerList(f *TrackerFilter) ([]*Tracker, error) { + q := "SELECT * FROM trackers WHERE 1=1" args := []any{} if f != nil { @@ -536,7 +536,7 @@ func PromptList(f *PromptFilter) ([]*Prompt, error) { q += fmt.Sprintf(" LIMIT %d", f.Limit) } - var result []*Prompt + var result []*Tracker err := Query(q, args, &result) return result, err } @@ -747,17 +747,17 @@ func AccessListByTargetWithNames(targetID string) ([]map[string]interface{}, err return result, nil } -// PromptDistinctTypes returns distinct category/type pairs for a dossier's active prompts -func PromptDistinctTypes(dossierID string) (map[string][]string, error) { - var prompts []*Prompt - if err := Query("SELECT * FROM prompts WHERE dossier_id = ? AND active = 1", []any{dossierID}, &prompts); err != nil { +// TrackerDistinctTypes returns distinct category/type pairs for a dossier's active trackers +func TrackerDistinctTypes(dossierID string) (map[string][]string, error) { + var trackers []*Tracker + if err := Query("SELECT * FROM trackers WHERE dossier_id = ? AND active = 1", []any{dossierID}, &trackers); err != nil { return nil, err } // Extract distinct category/type pairs seen := make(map[string]bool) result := make(map[string][]string) - for _, p := range prompts { + for _, p := range trackers { key := p.Category + "|" + p.Type if !seen[key] && p.Category != "" && p.Type != "" { seen[key] = true diff --git a/portal/api_mobile.go b/portal/api_mobile.go index b07bb07..59038f6 100644 --- a/portal/api_mobile.go +++ b/portal/api_mobile.go @@ -33,7 +33,7 @@ func initMobileAPI(mux *http.ServeMux) { mux.HandleFunc("/api/v1/auth/verify", handleAPIVerify) mux.HandleFunc("/api/v1/dashboard", handleAPIDashboard) mux.HandleFunc("/api/v1/prompts", handleAPIPrompts) - mux.HandleFunc("/api/v1/prompts/respond", handleAPIPromptRespond) + mux.HandleFunc("/api/v1/prompts/respond", handleAPITrackerRespond) } // --- Auth --- @@ -221,10 +221,10 @@ func handleAPIPrompts(w http.ResponseWriter, r *http.Request) { return } // TODO: Implement per spec - jsonOK(w, map[string]interface{}{"prompts": []interface{}{}}) + jsonOK(w, map[string]interface{}{"trackers": []interface{}{}}) } -func handleAPIPromptRespond(w http.ResponseWriter, r *http.Request) { +func handleAPITrackerRespond(w http.ResponseWriter, r *http.Request) { if cors(w, r) { return } d := getAPIAuth(r) if d == nil { diff --git a/portal/dossier_sections.go b/portal/dossier_sections.go index e61d6ea..76d98b9 100644 --- a/portal/dossier_sections.go +++ b/portal/dossier_sections.go @@ -32,11 +32,11 @@ type DossierSection struct { // Checkin-specific: show "build your profile" prompt ShowBuildPrompt bool // true if trackable categories are empty TrackableStats map[string]int // counts for trackable categories - PromptButtons []PromptButton // buttons for empty trackable categories + TrackerButtons []TrackerButton // buttons for empty trackable categories } -// PromptButton for the "build your profile" section -type PromptButton struct { +// TrackerButton for the "build your profile" section +type TrackerButton struct { Label string Icon string URL string @@ -150,7 +150,7 @@ func BuildDossierSections(targetID, targetHex string, target *lib.Dossier, p *li section.ShowBuildPrompt = true section.Summary = T("checkin_build_profile") promptsURL := fmt.Sprintf("/dossier/%s/prompts", targetHex) - section.PromptButtons = []PromptButton{ + section.TrackerButtons = []TrackerButton{ {Label: T("btn_vitals"), URL: promptsURL + "?add=vital"}, {Label: T("btn_medications"), URL: promptsURL + "?add=medication"}, {Label: T("btn_supplements"), URL: promptsURL + "?add=supplement"}, diff --git a/portal/main.go b/portal/main.go index 1329b47..3e881a5 100644 --- a/portal/main.go +++ b/portal/main.go @@ -2005,9 +2005,9 @@ func setupMux() http.Handler { } else if strings.HasSuffix(path, "/permissions") { handlePermissions(w, r) } else if strings.Contains(path, "/rbac/") { handleEditRBAC(w, r) } else if strings.Contains(path, "/access/") { handleEditAccess(w, r) - } else if strings.HasSuffix(path, "/prompts") { handlePrompts(w, r) - } else if strings.Contains(path, "/prompts/card/") { handleRenderPromptCard(w, r) - } else if strings.HasSuffix(path, "/prompts/respond") { handlePromptRespond(w, r) + } else if strings.HasSuffix(path, "/prompts") { handleTrackers(w, r) + } else if strings.Contains(path, "/prompts/card/") { handleRenderTrackerCard(w, r) + } else if strings.HasSuffix(path, "/prompts/respond") { handleTrackerRespond(w, r) } else if strings.HasSuffix(path, "/upload") { if r.Method == "POST" { handleUploadPost(w, r) } else { handleUploadPage(w, r) } } else if strings.Contains(path, "/files/") && strings.HasSuffix(path, "/delete") { handleDeleteFile(w, r) } else if strings.Contains(path, "/files/") && strings.HasSuffix(path, "/update") { handleUpdateFile(w, r) @@ -2028,7 +2028,7 @@ func setupMux() http.Handler { mux.HandleFunc("/api/v1/auth/verify", handleAPIVerify) mux.HandleFunc("/api/v1/dashboard", handleAPIDashboard) mux.HandleFunc("/api/v1/prompts", handleAPIPrompts) - mux.HandleFunc("/api/v1/prompts/respond", handleAPIPromptRespond) + mux.HandleFunc("/api/v1/prompts/respond", handleAPITrackerRespond) mux.HandleFunc("/api", handleAPI) mux.HandleFunc("/api/token/generate", handleAPITokenGenerate) diff --git a/portal/mcp_http.go b/portal/mcp_http.go index 5e10205..c04bf2c 100644 --- a/portal/mcp_http.go +++ b/portal/mcp_http.go @@ -279,7 +279,7 @@ func handleMCPInitialize(w http.ResponseWriter, req mcpRequest) { "protocolVersion": mcpProtocolVersion, "capabilities": map[string]interface{}{ "tools": map[string]interface{}{}, - "prompts": map[string]interface{}{}, + "trackers": map[string]interface{}{}, }, "serverInfo": map[string]interface{}{ "name": mcpServerName, @@ -617,7 +617,7 @@ func handleMCPPromptsList(w http.ResponseWriter, req mcpRequest) { }, } - sendMCPResult(w, req.ID, map[string]interface{}{"prompts": prompts}) + sendMCPResult(w, req.ID, map[string]interface{}{"trackers": prompts}) } func handleMCPPromptsGet(w http.ResponseWriter, req mcpRequest, accessToken, dossierID string) { diff --git a/portal/templates/base.tmpl b/portal/templates/base.tmpl index 980d785..2cc4989 100644 --- a/portal/templates/base.tmpl +++ b/portal/templates/base.tmpl @@ -109,7 +109,7 @@ {{else if eq .Page "styleguide"}}{{template "styleguide" .}} {{else if eq .Page "pricing"}}{{template "pricing" .}} {{else if eq .Page "faq"}}{{template "faq" .}} - {{else if eq .Page "prompts"}}{{template "prompts" .}} + {{else if eq .Page "trackers"}}{{template "trackers" .}} {{else if eq .Page "permissions"}}{{template "permissions" .}} {{else if eq .Page "edit_access"}}{{template "edit_access" .}} {{else if eq .Page "edit_rbac"}}{{template "edit_rbac" .}} diff --git a/portal/templates/connect.tmpl b/portal/templates/connect.tmpl index 1a0e5fe..27f951f 100644 --- a/portal/templates/connect.tmpl +++ b/portal/templates/connect.tmpl @@ -86,7 +86,7 @@
  • In the text area, add:
  • -
    At the start of health-related conversations, use the family_health_context prompt from the Inou Health connector to understand what health data is available.
    +
    At the start of health-related conversations, use the family_health_context tracker from the Inou Health connector to understand what health data is available.
    {{end}} {{else}} -
    +
    Sign in to generate your API token and get personalized setup instructions.
    {{end}} diff --git a/portal/templates/connect_nl.tmpl b/portal/templates/connect_nl.tmpl index c7998fc..70d1e7a 100644 --- a/portal/templates/connect_nl.tmpl +++ b/portal/templates/connect_nl.tmpl @@ -10,7 +10,7 @@
    {{if not (and .Dossier .Dossier.DossierID)}} -
    +
    Let op: Log in om gepersonaliseerde instructies te zien met je account-token al ingevuld.
    {{end}} diff --git a/portal/templates/connect_ru.tmpl b/portal/templates/connect_ru.tmpl index 4aa72be..94edd87 100644 --- a/portal/templates/connect_ru.tmpl +++ b/portal/templates/connect_ru.tmpl @@ -10,7 +10,7 @@
    {{if not (and .Dossier .Dossier.DossierID)}} -
    +
    Примечание: Войдите, чтобы увидеть персонализированные инструкции с вашим токеном учётной записи.
    {{end}} diff --git a/portal/templates/dossier.tmpl b/portal/templates/dossier.tmpl index cd37842..402d757 100644 --- a/portal/templates/dossier.tmpl +++ b/portal/templates/dossier.tmpl @@ -728,10 +728,10 @@ loadGeneticsCategories(); {{end}}
    - {{if .ShowBuildPrompt}} -
    + {{if .ShowBuildTracker}} +
    - {{range .PromptButtons}} + {{range .TrackerButtons}} {{.Label}} diff --git a/portal/templates/install_public.tmpl b/portal/templates/install_public.tmpl index 3d6d85a..a91d5ec 100644 --- a/portal/templates/install_public.tmpl +++ b/portal/templates/install_public.tmpl @@ -22,8 +22,8 @@ .quick-start h3 { margin: 0 0 1rem 0; font-size: 1.1rem; } .quick-start p { margin: 0.5rem 0; } - .login-prompt { background: #fff3cd; border: 1px solid #ffc107; border-radius: 8px; padding: 1rem 1.5rem; margin-bottom: 1.5rem; } - .login-prompt a { color: var(--accent); font-weight: 500; } + .login-tracker { background: #fff3cd; border: 1px solid #ffc107; border-radius: 8px; padding: 1rem 1.5rem; margin-bottom: 1.5rem; } + .login-tracker a { color: var(--accent); font-weight: 500; }
    @@ -35,7 +35,7 @@ ← Home
    -