add ability to save
This commit is contained in:
parent
509de38353
commit
3f2789b4da
|
|
@ -1,7 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DataSourcePerFileMappings">
|
||||
<file url="file://$APPLICATION_CONFIG_DIR$/consoles/db/4464bfa3-4e16-44aa-b61e-e142cf841d2f/console.sql" value="4464bfa3-4e16-44aa-b61e-e142cf841d2f" />
|
||||
<file url="file://$APPLICATION_CONFIG_DIR$/consoles/db/85be2c57-4234-463a-a995-092322f406a0/console.sql" value="85be2c57-4234-463a-a995-092322f406a0" />
|
||||
</component>
|
||||
</project>
|
||||
54
main.go
54
main.go
|
|
@ -73,6 +73,26 @@ func main() {
|
|||
})
|
||||
r.GET("/db.yaml", func(c *gin.Context) { c.File("db.yaml") })
|
||||
|
||||
// Add knowledgeModel save endpoint (replaces snapshot)
|
||||
r.POST("/admin/chats/snapshot", func(c *gin.Context) {
|
||||
if repo == nil {
|
||||
c.JSON(500, gin.H{"error": "repository not configured"})
|
||||
return
|
||||
}
|
||||
var req struct {
|
||||
Text string `json:"text"`
|
||||
}
|
||||
if err := c.BindJSON(&req); err != nil || req.Text == "" {
|
||||
c.JSON(400, gin.H{"error": "invalid request"})
|
||||
return
|
||||
}
|
||||
if err := repo.SaveKnowledgeModel(c.Request.Context(), req.Text); err != nil {
|
||||
c.JSON(500, gin.H{"error": "failed to save snapshot"})
|
||||
return
|
||||
}
|
||||
c.JSON(200, gin.H{"status": "ok"})
|
||||
})
|
||||
|
||||
// JSON: list chat interactions
|
||||
r.GET("/admin/chats", func(c *gin.Context) {
|
||||
if repo == nil {
|
||||
|
|
@ -143,5 +163,39 @@ func main() {
|
|||
}
|
||||
})
|
||||
|
||||
// List knowledgeModel entries (replaces snapshots)
|
||||
r.GET("/admin/chats/snapshots", func(c *gin.Context) {
|
||||
if repo == nil {
|
||||
c.JSON(200, gin.H{"items": []interface{}{}})
|
||||
return
|
||||
}
|
||||
models, err := repo.ListKnowledgeModels(c.Request.Context(), 100, 0)
|
||||
if err != nil {
|
||||
c.JSON(500, gin.H{"error": "failed to list snapshots"})
|
||||
return
|
||||
}
|
||||
c.JSON(200, gin.H{"items": models})
|
||||
})
|
||||
|
||||
// Get a specific knowledgeModel entry (replaces snapshot)
|
||||
r.GET("/admin/chats/snapshot/:id", func(c *gin.Context) {
|
||||
if repo == nil {
|
||||
c.JSON(404, gin.H{"error": "repository not configured"})
|
||||
return
|
||||
}
|
||||
idStr := c.Param("id")
|
||||
id, err := strconv.ParseInt(idStr, 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(400, gin.H{"error": "invalid id"})
|
||||
return
|
||||
}
|
||||
text, err := repo.GetKnowledgeModelText(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
c.JSON(404, gin.H{"error": "snapshot not found"})
|
||||
return
|
||||
}
|
||||
c.Data(200, "text/yaml; charset=utf-8", []byte(text))
|
||||
})
|
||||
|
||||
r.Run(":8080")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,9 @@ type ChatRepositoryAPI interface {
|
|||
ListChatInteractions(ctx context.Context, limit, offset int) ([]ChatInteraction, error)
|
||||
SaveLLMRawEvent(ctx context.Context, correlationID, phase, raw string) error
|
||||
ListLLMRawEvents(ctx context.Context, correlationID string, limit, offset int) ([]RawLLMEvent, error)
|
||||
SaveKnowledgeModel(ctx context.Context, text string) error
|
||||
ListKnowledgeModels(ctx context.Context, limit, offset int) ([]knowledgeModelMeta, error)
|
||||
GetKnowledgeModelText(ctx context.Context, id int64) (string, error)
|
||||
}
|
||||
|
||||
// RawLLMEvent represents a stored raw LLM exchange phase
|
||||
|
|
@ -41,6 +44,13 @@ type RawLLMEvent struct {
|
|||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// knowledgeModelMeta is used for listing knowledgeModel metadata
|
||||
// (exported for use in interface, but can be unexported if not needed outside package)
|
||||
type knowledgeModelMeta struct {
|
||||
ID int64 `json:"id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// PGChatRepository is a PostgreSQL implementation using pgxpool
|
||||
type PGChatRepository struct {
|
||||
pool *pgxpool.Pool
|
||||
|
|
@ -94,6 +104,15 @@ func (r *PGChatRepository) ensureSchema(ctx context.Context) error {
|
|||
if _, err := r.pool.Exec(ctx, ddlRaw); err != nil {
|
||||
return err
|
||||
}
|
||||
// Add knowledgeModel table
|
||||
ddlKnowledgeModel := `CREATE TABLE IF NOT EXISTS knowledgeModel (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
knowledge_text TEXT NOT NULL
|
||||
);`
|
||||
if _, err := r.pool.Exec(ctx, ddlKnowledgeModel); err != nil {
|
||||
return err
|
||||
}
|
||||
// Legacy column cleanup (ignore errors)
|
||||
for _, drop := range []string{
|
||||
"ALTER TABLE chat_interactions DROP COLUMN IF EXISTS raw_keywords_json",
|
||||
|
|
@ -205,6 +224,58 @@ func (r *PGChatRepository) ListLLMRawEvents(ctx context.Context, correlationID s
|
|||
return out, rows.Err()
|
||||
}
|
||||
|
||||
// SaveKnowledgeModel inserts a new knowledgeModel entry
|
||||
func (r *PGChatRepository) SaveKnowledgeModel(ctx context.Context, text string) error {
|
||||
if r == nil || r.pool == nil {
|
||||
return nil
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
|
||||
defer cancel()
|
||||
_, err := r.pool.Exec(ctx, `INSERT INTO knowledgeModel (knowledge_text) VALUES ($1)`, text)
|
||||
return err
|
||||
}
|
||||
|
||||
// ListKnowledgeModels returns a list of knowledgeModel metadata (id, created_at)
|
||||
func (r *PGChatRepository) ListKnowledgeModels(ctx context.Context, limit, offset int) ([]knowledgeModelMeta, error) {
|
||||
if r == nil || r.pool == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if limit <= 0 || limit > 1000 {
|
||||
limit = 100
|
||||
}
|
||||
if offset < 0 {
|
||||
offset = 0
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
|
||||
defer cancel()
|
||||
rows, err := r.pool.Query(ctx, `SELECT id, created_at FROM knowledgeModel ORDER BY created_at DESC LIMIT $1 OFFSET $2`, limit, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var out []knowledgeModelMeta
|
||||
for rows.Next() {
|
||||
var s knowledgeModelMeta
|
||||
if err := rows.Scan(&s.ID, &s.CreatedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, s)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
// GetKnowledgeModelText returns the knowledge_text for a given id
|
||||
func (r *PGChatRepository) GetKnowledgeModelText(ctx context.Context, id int64) (string, error) {
|
||||
if r == nil || r.pool == nil {
|
||||
return "", nil
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
|
||||
defer cancel()
|
||||
var text string
|
||||
err := r.pool.QueryRow(ctx, `SELECT knowledge_text FROM knowledgeModel WHERE id=$1`, id).Scan(&text)
|
||||
return text, err
|
||||
}
|
||||
|
||||
// Close releases pool resources
|
||||
func (r *PGChatRepository) Close() {
|
||||
if r != nil && r.pool != nil {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,10 @@
|
|||
<button id="load" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition">Load db.yaml</button>
|
||||
<button id="add" class="px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition">Add Entry</button>
|
||||
<button id="download" class="px-4 py-2 bg-gray-600 text-white rounded-lg hover:bg-gray-700 transition">Download YAML</button>
|
||||
<button id="saveSnapshot" class="px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition">Save Snapshot</button>
|
||||
<select id="snapshotSelect" class="px-4 py-2 bg-yellow-100 border border-yellow-400 rounded-lg text-yellow-900">
|
||||
<option value="">Load from snapshot...</option>
|
||||
</select>
|
||||
</div>
|
||||
<textarea id="yamlArea" placeholder="YAML will appear here..." class="w-full h-40 mb-4 p-2 border border-gray-300 rounded-lg bg-gray-100 text-sm"></textarea>
|
||||
<div id="formArea"></div>
|
||||
|
|
@ -24,6 +28,8 @@
|
|||
const loadBtn = document.getElementById('load');
|
||||
const addBtn = document.getElementById('add');
|
||||
const downloadBtn = document.getElementById('download');
|
||||
const saveSnapshotBtn = document.getElementById('saveSnapshot');
|
||||
const snapshotSelect = document.getElementById('snapshotSelect');
|
||||
let data = [];
|
||||
|
||||
loadBtn.onclick = async function() {
|
||||
|
|
@ -58,6 +64,52 @@
|
|||
URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
saveSnapshotBtn.onclick = async function() {
|
||||
const text = yamlArea.value;
|
||||
status.textContent = 'Saving snapshot...';
|
||||
const resp = await fetch('/admin/chats/snapshot', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({text})
|
||||
});
|
||||
const result = await resp.json();
|
||||
if (result.status === 'ok') {
|
||||
status.textContent = 'Snapshot saved!';
|
||||
loadSnapshots(); // Refresh snapshot list
|
||||
} else {
|
||||
status.textContent = 'Error: ' + (result.error || 'Unknown error');
|
||||
}
|
||||
};
|
||||
|
||||
async function loadSnapshots() {
|
||||
const resp = await fetch('/admin/chats/snapshots');
|
||||
if (!resp.ok) return;
|
||||
const result = await resp.json();
|
||||
snapshotSelect.innerHTML = '<option value="">Load from snapshot...</option>';
|
||||
(result.items || []).forEach(s => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = s.id;
|
||||
opt.textContent = `#${s.id} - ${new Date(s.created_at).toLocaleString()}`;
|
||||
snapshotSelect.appendChild(opt);
|
||||
});
|
||||
}
|
||||
snapshotSelect.onchange = async function() {
|
||||
const id = snapshotSelect.value;
|
||||
if (!id) return;
|
||||
status.textContent = 'Loading snapshot...';
|
||||
const resp = await fetch(`/admin/chats/snapshot/${id}`);
|
||||
if (!resp.ok) { status.textContent = 'Failed to load snapshot'; return; }
|
||||
const text = await resp.text();
|
||||
yamlArea.value = text;
|
||||
try {
|
||||
data = jsyaml.load(text);
|
||||
status.textContent = 'Snapshot loaded!';
|
||||
renderForm();
|
||||
} catch (e) {
|
||||
status.textContent = 'YAML parse error!';
|
||||
}
|
||||
};
|
||||
|
||||
function renderForm() {
|
||||
const formArea = document.getElementById('formArea');
|
||||
formArea.innerHTML = '';
|
||||
|
|
@ -183,6 +235,9 @@
|
|||
procDiv.appendChild(pdiv);
|
||||
});
|
||||
}
|
||||
|
||||
// Load snapshot list on page load
|
||||
loadSnapshots();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
Binary file not shown.
Loading…
Reference in New Issue