feat: delete traffic targets and LLM task conversations from UI

Add delete controls to the Traffic and LLM Records pages so users can
purge recorded data without hand-editing the data volume.

Traffic page:
- Host target picker (Popover with checkboxes + select-all toggle)
- Exact-match batch delete for multiple hosts (DELETE /api/traffic/hosts)
- Substring delete for the host-filter input (DELETE /api/traffic?host=)
- Orphaned content-addressed blob garbage collection after deletes
- Distinct-hosts endpoint (GET /api/traffic/hosts) for the picker

LLM Records page:
- Task picker dropdown (distinct task_id + record counts)
- Per-task delete (DELETE /api/llm/records?task=)

Other:
- Add /dist/ to .gitignore (build artifact)
- Extend traffic_test.go with DeleteHost, DeleteHostsExact, Hosts tests
- Fix Popover render bug (replace ScrollArea that broke height calc)
- Remove fade/zoom animation on the host picker Popover
This commit is contained in:
FileShare Developer committed 2026-08-10 11:10:58 +08:00
1 parent 5962826f2d
commit c76d0f4c93
12 files changed
+883 -20

No files matched your search

+42 -1
View File
@@ -152,7 +152,7 @@ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14)`,
}
// ListLLMRecords returns paginated LLM records with optional filters.
func (d *DB) ListLLMRecords(model, session string, page, size int) ([]LLMRecord, int, error) {
func (d *DB) ListLLMRecords(model, session, task string, page, size int) ([]LLMRecord, int, error) {
if size <= 0 {
size = 50
}
@@ -174,6 +174,11 @@ func (d *DB) ListLLMRecords(model, session string, page, size int) ([]LLMRecord,
args = append(args, "%"+session+"%")
argN++
}
if task != "" {
where += fmt.Sprintf(` AND COALESCE(task_id,'') = $%d`, argN)
args = append(args, task)
argN++
}
var total int
if err := d.QueryRow(`SELECT COUNT(*) FROM llm_records `+where, args...).Scan(&total); err != nil {
@@ -204,6 +209,42 @@ func (d *DB) ListLLMRecords(model, session string, page, size int) ([]LLMRecord,
return out, total, rows.Err()
}
// LLMTask is one distinct task with its LLM-record count.
type LLMTask struct {
TaskID string `json:"task_id"`
Count int `json:"count"`
}
// LLMTasks returns distinct non-empty task_ids with record counts, most recent
// first — powers the LLM-records page's task picker.
func (d *DB) LLMTasks() ([]LLMTask, error) {
rows, err := d.Query(`SELECT task_id, COUNT(*) AS n FROM llm_records
WHERE COALESCE(task_id,'') <> '' GROUP BY task_id ORDER BY MAX(id) DESC`)
if err != nil {
return nil, err
}
defer rows.Close()
var out []LLMTask
for rows.Next() {
var t LLMTask
if err := rows.Scan(&t.TaskID, &t.Count); err != nil {
return nil, err
}
out = append(out, t)
}
return out, rows.Err()
}
// DeleteLLMRecords removes every LLM record for one exact task_id — the same
// match the page's task picker/filter uses. Returns rows deleted.
func (d *DB) DeleteLLMRecords(task string) (int64, error) {
res, err := d.Exec(`DELETE FROM llm_records WHERE COALESCE(task_id,'') = $1`, task)
if err != nil {
return 0, err
}
return res.RowsAffected()
}
// GetLLMRecord returns a single LLM record with full request/response bodies.
func (d *DB) GetLLMRecord(id int64) (*LLMRecord, error) {
var r LLMRecord