Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 

506 lignes
15 KiB

  1. package model
  2. import (
  3. "bytes"
  4. "database/sql/driver"
  5. "encoding/json"
  6. "time"
  7. "github.com/QuantumNous/new-api/common"
  8. "github.com/QuantumNous/new-api/constant"
  9. "github.com/QuantumNous/new-api/dto"
  10. commonRelay "github.com/QuantumNous/new-api/relay/common"
  11. )
  12. type TaskStatus string
  13. func (t TaskStatus) ToVideoStatus() string {
  14. var status string
  15. switch t {
  16. case TaskStatusQueued, TaskStatusSubmitted:
  17. status = dto.VideoStatusQueued
  18. case TaskStatusInProgress:
  19. status = dto.VideoStatusInProgress
  20. case TaskStatusSuccess:
  21. status = dto.VideoStatusCompleted
  22. case TaskStatusFailure:
  23. status = dto.VideoStatusFailed
  24. default:
  25. status = dto.VideoStatusUnknown // Default fallback
  26. }
  27. return status
  28. }
  29. const (
  30. TaskStatusNotStart TaskStatus = "NOT_START"
  31. TaskStatusSubmitted = "SUBMITTED"
  32. TaskStatusQueued = "QUEUED"
  33. TaskStatusInProgress = "IN_PROGRESS"
  34. TaskStatusFailure = "FAILURE"
  35. TaskStatusSuccess = "SUCCESS"
  36. TaskStatusUnknown = "UNKNOWN"
  37. )
  38. type Task struct {
  39. ID int64 `json:"id" gorm:"primary_key;AUTO_INCREMENT"`
  40. CreatedAt int64 `json:"created_at" gorm:"index"`
  41. UpdatedAt int64 `json:"updated_at"`
  42. TaskID string `json:"task_id" gorm:"type:varchar(191);index"`
  43. Platform constant.TaskPlatform `json:"platform" gorm:"type:varchar(30);index"`
  44. UserId int `json:"user_id" gorm:"index"`
  45. Group string `json:"group" gorm:"type:varchar(50)"`
  46. ChannelId int `json:"channel_id" gorm:"index"`
  47. Quota int `json:"quota"`
  48. Action string `json:"action" gorm:"type:varchar(40);index"`
  49. Status TaskStatus `json:"status" gorm:"type:varchar(20);index"`
  50. FailReason string `json:"fail_reason"`
  51. SubmitTime int64 `json:"submit_time" gorm:"index"`
  52. StartTime int64 `json:"start_time" gorm:"index"`
  53. FinishTime int64 `json:"finish_time" gorm:"index"`
  54. Progress string `json:"progress" gorm:"type:varchar(20);index"`
  55. Properties Properties `json:"properties" gorm:"type:json"`
  56. Username string `json:"username,omitempty" gorm:"-"`
  57. PrivateData TaskPrivateData `json:"-" gorm:"column:private_data;type:json"`
  58. Data json.RawMessage `json:"data" gorm:"type:json"`
  59. }
  60. func (t *Task) SetData(data any) {
  61. b, _ := common.Marshal(data)
  62. t.Data = json.RawMessage(b)
  63. }
  64. func (t *Task) GetData(v any) error {
  65. return common.Unmarshal(t.Data, &v)
  66. }
  67. type Properties struct {
  68. Input string `json:"input"`
  69. UpstreamModelName string `json:"upstream_model_name,omitempty"`
  70. OriginModelName string `json:"origin_model_name,omitempty"`
  71. }
  72. func (m *Properties) Scan(val interface{}) error {
  73. bytesValue, _ := val.([]byte)
  74. if len(bytesValue) == 0 {
  75. *m = Properties{}
  76. return nil
  77. }
  78. return common.Unmarshal(bytesValue, m)
  79. }
  80. func (m Properties) Value() (driver.Value, error) {
  81. if m == (Properties{}) {
  82. return nil, nil
  83. }
  84. return common.Marshal(m)
  85. }
  86. type TaskPrivateData struct {
  87. Key string `json:"key,omitempty"`
  88. UpstreamTaskID string `json:"upstream_task_id,omitempty"`
  89. ResultURL string `json:"result_url,omitempty"`
  90. BillingSource string `json:"billing_source,omitempty"`
  91. SubscriptionId int `json:"subscription_id,omitempty"`
  92. TokenId int `json:"token_id,omitempty"`
  93. BillingContext *TaskBillingContext `json:"billing_context,omitempty"`
  94. UpstreamRequest *TaskUpstreamRequestSnapshot `json:"upstream_request,omitempty"`
  95. }
  96. type TaskUpstreamRequestSnapshot struct {
  97. Body any `json:"body,omitempty"`
  98. RawBytes int `json:"raw_bytes,omitempty"`
  99. StoredBytes int `json:"stored_bytes,omitempty"`
  100. Truncated bool `json:"truncated,omitempty"`
  101. Redacted bool `json:"redacted,omitempty"`
  102. SHA256 string `json:"sha256,omitempty"`
  103. }
  104. type TaskBillingContext struct {
  105. ModelPrice float64 `json:"model_price,omitempty"`
  106. GroupRatio float64 `json:"group_ratio,omitempty"`
  107. ModelRatio float64 `json:"model_ratio,omitempty"`
  108. OtherRatios map[string]float64 `json:"other_ratios,omitempty"`
  109. OriginModelName string `json:"origin_model_name,omitempty"`
  110. PerCallBilling bool `json:"per_call_billing,omitempty"`
  111. BillingMode string `json:"billing_mode,omitempty"`
  112. PricingSnapshot map[string]any `json:"pricing_snapshot,omitempty"`
  113. BillingUnit string `json:"billing_unit,omitempty"`
  114. TokenUnitPriceUSD float64 `json:"token_unit_price_usd,omitempty"`
  115. }
  116. func (t *Task) GetUpstreamTaskID() string {
  117. if t.PrivateData.UpstreamTaskID != "" {
  118. return t.PrivateData.UpstreamTaskID
  119. }
  120. return t.TaskID
  121. }
  122. func (t *Task) GetResultURL() string {
  123. if t.PrivateData.ResultURL != "" {
  124. return t.PrivateData.ResultURL
  125. }
  126. return t.FailReason
  127. }
  128. func GenerateTaskID() string {
  129. key, _ := common.GenerateRandomCharsKey(32)
  130. return "task_" + key
  131. }
  132. func (p *TaskPrivateData) Scan(val interface{}) error {
  133. bytesValue, _ := val.([]byte)
  134. if len(bytesValue) == 0 {
  135. return nil
  136. }
  137. return common.Unmarshal(bytesValue, p)
  138. }
  139. func (p TaskPrivateData) Value() (driver.Value, error) {
  140. if (p == TaskPrivateData{}) {
  141. return nil, nil
  142. }
  143. return common.Marshal(p)
  144. }
  145. type SyncTaskQueryParams struct {
  146. Platform constant.TaskPlatform
  147. UserId int `json:"user_id" gorm:"index"`
  148. ChannelID string
  149. TaskID string
  150. UserID string
  151. Action string
  152. Status string
  153. StartTimestamp int64
  154. EndTimestamp int64
  155. UserIDs []int
  156. }
  157. func InitTask(platform constant.TaskPlatform, relayInfo *commonRelay.RelayInfo) *Task {
  158. properties := Properties{}
  159. privateData := TaskPrivateData{}
  160. if relayInfo != nil && relayInfo.ChannelMeta != nil {
  161. if relayInfo.ChannelMeta.ChannelType == constant.ChannelTypeGemini {
  162. privateData.Key = relayInfo.ChannelMeta.ApiKey
  163. }
  164. if relayInfo.UpstreamModelName != "" {
  165. properties.UpstreamModelName = relayInfo.UpstreamModelName
  166. }
  167. if relayInfo.OriginModelName != "" {
  168. properties.OriginModelName = relayInfo.OriginModelName
  169. }
  170. }
  171. taskID := ""
  172. if relayInfo.TaskRelayInfo != nil && relayInfo.TaskRelayInfo.PublicTaskID != "" {
  173. taskID = relayInfo.TaskRelayInfo.PublicTaskID
  174. } else {
  175. taskID = GenerateTaskID()
  176. }
  177. t := &Task{
  178. TaskID: taskID,
  179. UserId: relayInfo.UserId,
  180. Group: relayInfo.UsingGroup,
  181. SubmitTime: time.Now().Unix(),
  182. Status: TaskStatusNotStart,
  183. Progress: "0%",
  184. ChannelId: relayInfo.ChannelId,
  185. Platform: platform,
  186. Properties: properties,
  187. PrivateData: privateData,
  188. }
  189. return t
  190. }
  191. func TaskGetAllUserTask(userId int, startIdx int, num int, queryParams SyncTaskQueryParams) []*Task {
  192. var tasks []*Task
  193. var err error
  194. query := DB.Where("user_id = ?", userId)
  195. if queryParams.TaskID != "" {
  196. query = query.Where("task_id = ?", queryParams.TaskID)
  197. }
  198. if queryParams.Action != "" {
  199. query = query.Where("action = ?", queryParams.Action)
  200. }
  201. if queryParams.Status != "" {
  202. query = query.Where("status = ?", queryParams.Status)
  203. }
  204. if queryParams.Platform != "" {
  205. query = query.Where("platform = ?", queryParams.Platform)
  206. }
  207. if queryParams.StartTimestamp != 0 {
  208. query = query.Where("submit_time >= ?", queryParams.StartTimestamp)
  209. }
  210. if queryParams.EndTimestamp != 0 {
  211. query = query.Where("submit_time <= ?", queryParams.EndTimestamp)
  212. }
  213. err = query.Omit("channel_id").Order("id desc").Limit(num).Offset(startIdx).Find(&tasks).Error
  214. if err != nil {
  215. return nil
  216. }
  217. return tasks
  218. }
  219. func TaskGetAllTasks(startIdx int, num int, queryParams SyncTaskQueryParams) []*Task {
  220. var tasks []*Task
  221. var err error
  222. query := DB
  223. if queryParams.ChannelID != "" {
  224. query = query.Where("channel_id = ?", queryParams.ChannelID)
  225. }
  226. if queryParams.Platform != "" {
  227. query = query.Where("platform = ?", queryParams.Platform)
  228. }
  229. if queryParams.UserID != "" {
  230. query = query.Where("user_id = ?", queryParams.UserID)
  231. }
  232. if len(queryParams.UserIDs) != 0 {
  233. query = query.Where("user_id in (?)", queryParams.UserIDs)
  234. }
  235. if queryParams.TaskID != "" {
  236. query = query.Where("task_id = ?", queryParams.TaskID)
  237. }
  238. if queryParams.Action != "" {
  239. query = query.Where("action = ?", queryParams.Action)
  240. }
  241. if queryParams.Status != "" {
  242. query = query.Where("status = ?", queryParams.Status)
  243. }
  244. if queryParams.StartTimestamp != 0 {
  245. query = query.Where("submit_time >= ?", queryParams.StartTimestamp)
  246. }
  247. if queryParams.EndTimestamp != 0 {
  248. query = query.Where("submit_time <= ?", queryParams.EndTimestamp)
  249. }
  250. err = query.Order("id desc").Limit(num).Offset(startIdx).Find(&tasks).Error
  251. if err != nil {
  252. return nil
  253. }
  254. return tasks
  255. }
  256. func GetTimedOutUnfinishedTasks(cutoffUnix int64, limit int) []*Task {
  257. var tasks []*Task
  258. err := DB.Where("progress != ?", "100%").
  259. Where("status NOT IN ?", []string{TaskStatusFailure, TaskStatusSuccess}).
  260. Where("submit_time < ?", cutoffUnix).
  261. Order("submit_time").
  262. Limit(limit).
  263. Find(&tasks).Error
  264. if err != nil {
  265. return nil
  266. }
  267. return tasks
  268. }
  269. func GetAllUnFinishSyncTasks(limit int) []*Task {
  270. var tasks []*Task
  271. var err error
  272. // get all tasks progress is not 100%
  273. err = DB.Where("progress != ?", "100%").Where("status != ?", TaskStatusFailure).Where("status != ?", TaskStatusSuccess).Limit(limit).Order("id").Find(&tasks).Error
  274. if err != nil {
  275. return nil
  276. }
  277. return tasks
  278. }
  279. func GetByOnlyTaskId(taskId string) (*Task, bool, error) {
  280. if taskId == "" {
  281. return nil, false, nil
  282. }
  283. var task *Task
  284. var err error
  285. err = DB.Where("task_id = ?", taskId).First(&task).Error
  286. exist, err := RecordExist(err)
  287. if err != nil {
  288. return nil, false, err
  289. }
  290. return task, exist, err
  291. }
  292. func GetByTaskId(userId int, taskId string) (*Task, bool, error) {
  293. if taskId == "" {
  294. return nil, false, nil
  295. }
  296. var task *Task
  297. var err error
  298. err = DB.Where("user_id = ? and task_id = ?", userId, taskId).
  299. First(&task).Error
  300. exist, err := RecordExist(err)
  301. if err != nil {
  302. return nil, false, err
  303. }
  304. return task, exist, err
  305. }
  306. func GetByTaskIds(userId int, taskIds []any) ([]*Task, error) {
  307. if len(taskIds) == 0 {
  308. return nil, nil
  309. }
  310. var task []*Task
  311. var err error
  312. err = DB.Where("user_id = ? and task_id in (?)", userId, taskIds).
  313. Find(&task).Error
  314. if err != nil {
  315. return nil, err
  316. }
  317. return task, nil
  318. }
  319. func (Task *Task) Insert() error {
  320. var err error
  321. err = DB.Create(Task).Error
  322. return err
  323. }
  324. type taskSnapshot struct {
  325. Status TaskStatus
  326. Progress string
  327. StartTime int64
  328. FinishTime int64
  329. FailReason string
  330. ResultURL string
  331. Data json.RawMessage
  332. }
  333. func (s taskSnapshot) Equal(other taskSnapshot) bool {
  334. return s.Status == other.Status &&
  335. s.Progress == other.Progress &&
  336. s.StartTime == other.StartTime &&
  337. s.FinishTime == other.FinishTime &&
  338. s.FailReason == other.FailReason &&
  339. s.ResultURL == other.ResultURL &&
  340. bytes.Equal(s.Data, other.Data)
  341. }
  342. func (t *Task) Snapshot() taskSnapshot {
  343. return taskSnapshot{
  344. Status: t.Status,
  345. Progress: t.Progress,
  346. StartTime: t.StartTime,
  347. FinishTime: t.FinishTime,
  348. FailReason: t.FailReason,
  349. ResultURL: t.PrivateData.ResultURL,
  350. Data: t.Data,
  351. }
  352. }
  353. func (Task *Task) Update() error {
  354. var err error
  355. err = DB.Save(Task).Error
  356. return err
  357. }
  358. // UpdateWithStatus performs a conditional UPDATE guarded by fromStatus (CAS).
  359. // Returns (true, nil) if this caller won the update, (false, nil) if
  360. // another process already moved the task out of fromStatus.
  361. //
  362. // Uses Model().Select("*").Updates() instead of Save() because GORM's Save
  363. // falls back to INSERT ON CONFLICT when the WHERE-guarded UPDATE matches
  364. // zero rows, which silently bypasses the CAS guard.
  365. func (t *Task) UpdateWithStatus(fromStatus TaskStatus) (bool, error) {
  366. result := DB.Model(t).Where("status = ?", fromStatus).Select("*").Updates(t)
  367. if result.Error != nil {
  368. return false, result.Error
  369. }
  370. return result.RowsAffected > 0, nil
  371. }
  372. // TaskBulkUpdateByID performs an unconditional bulk UPDATE by primary key IDs.
  373. // any concurrent status changes. DO NOT use in billing/quota lifecycle flows
  374. // (e.g., timeout, success, failure transitions that trigger refunds or settlements).
  375. // For status transitions that involve billing, use Task.UpdateWithStatus() instead.
  376. func TaskBulkUpdateByID(ids []int64, params map[string]any) error {
  377. if len(ids) == 0 {
  378. return nil
  379. }
  380. return DB.Model(&Task{}).
  381. Where("id in (?)", ids).
  382. Updates(params).Error
  383. }
  384. type TaskQuotaUsage struct {
  385. Mode string `json:"mode"`
  386. Count float64 `json:"count"`
  387. }
  388. // TaskCountAllTasks returns total tasks that match the given query params (admin usage)
  389. func TaskCountAllTasks(queryParams SyncTaskQueryParams) int64 {
  390. var total int64
  391. query := DB.Model(&Task{})
  392. if queryParams.ChannelID != "" {
  393. query = query.Where("channel_id = ?", queryParams.ChannelID)
  394. }
  395. if queryParams.Platform != "" {
  396. query = query.Where("platform = ?", queryParams.Platform)
  397. }
  398. if queryParams.UserID != "" {
  399. query = query.Where("user_id = ?", queryParams.UserID)
  400. }
  401. if len(queryParams.UserIDs) != 0 {
  402. query = query.Where("user_id in (?)", queryParams.UserIDs)
  403. }
  404. if queryParams.TaskID != "" {
  405. query = query.Where("task_id = ?", queryParams.TaskID)
  406. }
  407. if queryParams.Action != "" {
  408. query = query.Where("action = ?", queryParams.Action)
  409. }
  410. if queryParams.Status != "" {
  411. query = query.Where("status = ?", queryParams.Status)
  412. }
  413. if queryParams.StartTimestamp != 0 {
  414. query = query.Where("submit_time >= ?", queryParams.StartTimestamp)
  415. }
  416. if queryParams.EndTimestamp != 0 {
  417. query = query.Where("submit_time <= ?", queryParams.EndTimestamp)
  418. }
  419. _ = query.Count(&total).Error
  420. return total
  421. }
  422. // TaskCountAllUserTask returns total tasks for given user
  423. func TaskCountAllUserTask(userId int, queryParams SyncTaskQueryParams) int64 {
  424. var total int64
  425. query := DB.Model(&Task{}).Where("user_id = ?", userId)
  426. if queryParams.TaskID != "" {
  427. query = query.Where("task_id = ?", queryParams.TaskID)
  428. }
  429. if queryParams.Action != "" {
  430. query = query.Where("action = ?", queryParams.Action)
  431. }
  432. if queryParams.Status != "" {
  433. query = query.Where("status = ?", queryParams.Status)
  434. }
  435. if queryParams.Platform != "" {
  436. query = query.Where("platform = ?", queryParams.Platform)
  437. }
  438. if queryParams.StartTimestamp != 0 {
  439. query = query.Where("submit_time >= ?", queryParams.StartTimestamp)
  440. }
  441. if queryParams.EndTimestamp != 0 {
  442. query = query.Where("submit_time <= ?", queryParams.EndTimestamp)
  443. }
  444. _ = query.Count(&total).Error
  445. return total
  446. }
  447. func (t *Task) ToOpenAIVideo() *dto.OpenAIVideo {
  448. openAIVideo := dto.NewOpenAIVideo()
  449. openAIVideo.ID = t.TaskID
  450. openAIVideo.Status = t.Status.ToVideoStatus()
  451. openAIVideo.Model = t.Properties.OriginModelName
  452. openAIVideo.SetProgressStr(t.Progress)
  453. openAIVideo.CreatedAt = t.CreatedAt
  454. openAIVideo.CompletedAt = t.UpdatedAt
  455. openAIVideo.SetMetadata("url", t.GetResultURL())
  456. return openAIVideo
  457. }