|
|
@@ -6,6 +6,7 @@ import (
|
|
|
"fmt"
|
|
|
"os"
|
|
|
"path/filepath"
|
|
|
+ "time"
|
|
|
|
|
|
_ "modernc.org/sqlite"
|
|
|
|
|
|
@@ -61,6 +62,10 @@ type StatusRecord struct {
|
|
|
Timestamp string `json:"timestamp"`
|
|
|
}
|
|
|
|
|
|
+type WorkDuration struct {
|
|
|
+ DurationMinutes int `json:"duration_minutes"`
|
|
|
+}
|
|
|
+
|
|
|
func New(dbPath string) (*DB, error) {
|
|
|
dir := filepath.Dir(dbPath)
|
|
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
|
|
@@ -439,3 +444,52 @@ func (d *DB) CleanOldStatusRecords(hours int) error {
|
|
|
}
|
|
|
return nil
|
|
|
}
|
|
|
+
|
|
|
+func (d *DB) GetTodayWorkDuration() (*WorkDuration, error) {
|
|
|
+ query := "SELECT code, timestamp FROM status_history WHERE timestamp >= date('now', 'localtime') ORDER BY timestamp ASC"
|
|
|
+ rows, err := d.conn.QueryContext(context.Background(), query)
|
|
|
+ if err != nil {
|
|
|
+ logger.Error("查询今日工作时长失败: %v", err)
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ defer rows.Close()
|
|
|
+
|
|
|
+ type record struct {
|
|
|
+ Code string
|
|
|
+ Time time.Time
|
|
|
+ }
|
|
|
+ var records []record
|
|
|
+ for rows.Next() {
|
|
|
+ var code, ts string
|
|
|
+ if err := rows.Scan(&code, &ts); err != nil {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ t, err := time.ParseInLocation("2006-01-02 15:04:05", ts, time.Local)
|
|
|
+ if err != nil {
|
|
|
+ logger.Warn("解析时间戳失败: %s, %v", ts, err)
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ records = append(records, record{Code: code, Time: t})
|
|
|
+ }
|
|
|
+
|
|
|
+ active := map[string]bool{
|
|
|
+ "busy": true, "reasoning": true, "using_tool": true,
|
|
|
+ "running": true, "pending": true, "retry": true,
|
|
|
+ }
|
|
|
+
|
|
|
+ var totalMs int64
|
|
|
+ now := time.Now()
|
|
|
+ for i, r := range records {
|
|
|
+ if !active[r.Code] {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ if i+1 < len(records) {
|
|
|
+ totalMs += records[i+1].Time.Sub(r.Time).Milliseconds()
|
|
|
+ } else if active[r.Code] {
|
|
|
+ totalMs += now.Sub(r.Time).Milliseconds()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ logger.Debug("今日工作时长: %d 分钟", int(totalMs/60000))
|
|
|
+ return &WorkDuration{DurationMinutes: int(totalMs / 60000)}, nil
|
|
|
+}
|