You've already forked agentic-coding-workflow
改动
This commit is contained in:
+96
-62
@@ -99,6 +99,9 @@ func (h *Handler) Mount(r chi.Router) {
|
||||
// User usage.
|
||||
r.Get("/users/me/usage", h.userDailyUsage)
|
||||
|
||||
// Public (non-admin) models list for chat composers.
|
||||
r.Get("/models", h.listModelsPublic)
|
||||
|
||||
// Admin-only routes.
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(h.adminGuard)
|
||||
@@ -246,7 +249,13 @@ func (h *Handler) getConversation(w http.ResponseWriter, r *http.Request) {
|
||||
writeErr(w, r, err)
|
||||
return
|
||||
}
|
||||
conv, msgs, err := h.convSvc.Get(r.Context(), uid, id)
|
||||
limit := 50
|
||||
if v := r.URL.Query().Get("limit"); v != "" {
|
||||
if n, e := strconv.Atoi(v); e == nil && n > 0 {
|
||||
limit = n
|
||||
}
|
||||
}
|
||||
conv, msgs, err := h.convSvc.Get(r.Context(), uid, id, limit)
|
||||
if err != nil {
|
||||
writeErr(w, r, err)
|
||||
return
|
||||
@@ -398,12 +407,15 @@ func (h *Handler) retryMessage(w http.ResponseWriter, r *http.Request) {
|
||||
writeErr(w, r, errs.Wrap(err, errs.CodeInvalidInput, "invalid message id"))
|
||||
return
|
||||
}
|
||||
newID, err := h.msgSvc.Retry(r.Context(), uid, msgID)
|
||||
newID, convID, err := h.msgSvc.Retry(r.Context(), uid, msgID)
|
||||
if err != nil {
|
||||
writeErr(w, r, err)
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]int64{"assistant_message_id": newID})
|
||||
writeJSON(w, http.StatusOK, map[string]any{
|
||||
"assistant_message_id": newID,
|
||||
"stream_url": fmt.Sprintf("/api/v1/conversations/%s/stream", convID),
|
||||
})
|
||||
}
|
||||
|
||||
// ===== Template handlers =====
|
||||
@@ -641,6 +653,22 @@ func (h *Handler) listModels(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, http.StatusOK, out)
|
||||
}
|
||||
|
||||
// listModelsPublic mirrors listModels but is reachable by any authenticated
|
||||
// user (non-admin), so chat composers can list available models.
|
||||
func (h *Handler) listModelsPublic(w http.ResponseWriter, r *http.Request) {
|
||||
onlyEnabled := r.URL.Query().Get("only_enabled") == "true"
|
||||
models, err := h.epSvc.ListModels(r.Context(), onlyEnabled)
|
||||
if err != nil {
|
||||
writeErr(w, r, err)
|
||||
return
|
||||
}
|
||||
out := make([]ModelDTO, 0, len(models))
|
||||
for i := range models {
|
||||
out = append(out, toModelDTO(&models[i]))
|
||||
}
|
||||
writeJSON(w, http.StatusOK, out)
|
||||
}
|
||||
|
||||
func (h *Handler) createModel(w http.ResponseWriter, r *http.Request) {
|
||||
var req CreateModelReq
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
@@ -648,18 +676,18 @@ func (h *Handler) createModel(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
m, err := h.epSvc.CreateModel(r.Context(), CreateModelInput{
|
||||
EndpointID: req.EndpointID,
|
||||
ModelID: req.ModelID,
|
||||
DisplayName: req.DisplayName,
|
||||
Capabilities: req.Capabilities,
|
||||
ContextWindow: req.ContextWindow,
|
||||
MaxOutputTokens: req.MaxOutputTokens,
|
||||
PromptPrice: req.PromptPricePerMillionUSD,
|
||||
CompletionPrice: req.CompletionPricePerMillionUSD,
|
||||
ThinkingPrice: req.ThinkingPricePerMillionUSD,
|
||||
EndpointID: req.EndpointID,
|
||||
ModelID: req.ModelID,
|
||||
DisplayName: req.DisplayName,
|
||||
Capabilities: req.Capabilities,
|
||||
ContextWindow: req.ContextWindow,
|
||||
MaxOutputTokens: req.MaxOutputTokens,
|
||||
PromptPrice: req.PromptPricePerMillionUSD,
|
||||
CompletionPrice: req.CompletionPricePerMillionUSD,
|
||||
ThinkingPrice: req.ThinkingPricePerMillionUSD,
|
||||
IsTitleGenerator: req.IsTitleGenerator,
|
||||
Enabled: req.Enabled,
|
||||
SortOrder: req.SortOrder,
|
||||
Enabled: req.Enabled,
|
||||
SortOrder: req.SortOrder,
|
||||
})
|
||||
if err != nil {
|
||||
writeErr(w, r, err)
|
||||
@@ -727,56 +755,62 @@ func (h *Handler) adminUsage(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
byUser, err := h.usageSvc.SummarizeByUser(r.Context(), from, to)
|
||||
if err != nil {
|
||||
writeErr(w, r, err)
|
||||
return
|
||||
groupBy := q.Get("group_by")
|
||||
if groupBy == "" {
|
||||
groupBy = "day"
|
||||
}
|
||||
byModel, err := h.usageSvc.SummarizeByModel(r.Context(), from, to)
|
||||
if err != nil {
|
||||
writeErr(w, r, err)
|
||||
return
|
||||
}
|
||||
byDay, err := h.usageSvc.SummarizeByDay(r.Context(), from, to)
|
||||
if err != nil {
|
||||
writeErr(w, r, err)
|
||||
|
||||
items := make([]AdminUsageItem, 0)
|
||||
switch groupBy {
|
||||
case "user":
|
||||
rows, err := h.usageSvc.SummarizeByUser(r.Context(), from, to)
|
||||
if err != nil {
|
||||
writeErr(w, r, err)
|
||||
return
|
||||
}
|
||||
for _, row := range rows {
|
||||
items = append(items, AdminUsageItem{
|
||||
Key: row.UserID.String(),
|
||||
PromptTokens: row.PromptTokens,
|
||||
CompletionTokens: row.CompletionTokens,
|
||||
ThinkingTokens: row.ThinkingTokens,
|
||||
CostUSD: row.CostUSD,
|
||||
})
|
||||
}
|
||||
case "model":
|
||||
rows, err := h.usageSvc.SummarizeByModel(r.Context(), from, to)
|
||||
if err != nil {
|
||||
writeErr(w, r, err)
|
||||
return
|
||||
}
|
||||
for _, row := range rows {
|
||||
items = append(items, AdminUsageItem{
|
||||
Key: row.ModelID.String(),
|
||||
PromptTokens: row.PromptTokens,
|
||||
CompletionTokens: row.CompletionTokens,
|
||||
ThinkingTokens: row.ThinkingTokens,
|
||||
CostUSD: row.CostUSD,
|
||||
})
|
||||
}
|
||||
case "day":
|
||||
rows, err := h.usageSvc.SummarizeByDay(r.Context(), from, to)
|
||||
if err != nil {
|
||||
writeErr(w, r, err)
|
||||
return
|
||||
}
|
||||
for _, row := range rows {
|
||||
items = append(items, AdminUsageItem{
|
||||
Key: row.Day.Format(time.RFC3339),
|
||||
PromptTokens: row.PromptTokens,
|
||||
CompletionTokens: row.CompletionTokens,
|
||||
ThinkingTokens: row.ThinkingTokens,
|
||||
CostUSD: row.CostUSD,
|
||||
})
|
||||
}
|
||||
default:
|
||||
writeErr(w, r, errs.New(errs.CodeInvalidInput, "invalid group_by"))
|
||||
return
|
||||
}
|
||||
|
||||
userDTOs := make([]UserUsageDTO, 0, len(byUser))
|
||||
for _, row := range byUser {
|
||||
userDTOs = append(userDTOs, UserUsageDTO{
|
||||
UserID: row.UserID,
|
||||
PromptTokens: row.PromptTokens,
|
||||
CompletionTokens: row.CompletionTokens,
|
||||
ThinkingTokens: row.ThinkingTokens,
|
||||
CostUSD: row.CostUSD,
|
||||
})
|
||||
}
|
||||
modelDTOs := make([]ModelUsageDTO, 0, len(byModel))
|
||||
for _, row := range byModel {
|
||||
modelDTOs = append(modelDTOs, ModelUsageDTO{
|
||||
ModelID: row.ModelID,
|
||||
PromptTokens: row.PromptTokens,
|
||||
CompletionTokens: row.CompletionTokens,
|
||||
ThinkingTokens: row.ThinkingTokens,
|
||||
CostUSD: row.CostUSD,
|
||||
})
|
||||
}
|
||||
dayDTOs := make([]DayUsageDTO, 0, len(byDay))
|
||||
for _, row := range byDay {
|
||||
dayDTOs = append(dayDTOs, DayUsageDTO{
|
||||
Day: row.Day,
|
||||
PromptTokens: row.PromptTokens,
|
||||
CompletionTokens: row.CompletionTokens,
|
||||
ThinkingTokens: row.ThinkingTokens,
|
||||
CostUSD: row.CostUSD,
|
||||
})
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, AdminUsageResp{
|
||||
ByUser: userDTOs,
|
||||
ByModel: modelDTOs,
|
||||
ByDay: dayDTOs,
|
||||
})
|
||||
writeJSON(w, http.StatusOK, AdminUsageResp{Items: items})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user