b_chat_assistant.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607
  1. package internal
  2. import (
  3. "context"
  4. "fmt"
  5. "sort"
  6. "strings"
  7. "time"
  8. "github.com/gogf/gf/v2/os/glog"
  9. "github.com/gogf/gf/v2/util/guid"
  10. "yx-dataset-server/app/errors"
  11. "yx-dataset-server/app/model"
  12. "yx-dataset-server/app/schema"
  13. "yx-dataset-server/library/ragflow"
  14. )
  15. // NewChatAssistant 创建ChatAssistant
  16. func NewChatAssistant(
  17. mChatAssistant model.IChatAssistant,
  18. mChatDataset model.IChatDataset,
  19. mTrans model.ITrans,
  20. mDataset model.IDataset,
  21. mUser model.IUser,
  22. mSession model.IChatSession,
  23. mMessage model.IChatMessage,
  24. mRelation model.IDatasetRelation,
  25. mFile model.IDatasetFile,
  26. ) *ChatAssistant {
  27. return &ChatAssistant{
  28. ChatAssistantModel: mChatAssistant,
  29. chatDatasetModel: mChatDataset,
  30. transModel: mTrans,
  31. datasetModel: mDataset,
  32. userModel: mUser,
  33. sessionModel: mSession,
  34. messageModel: mMessage,
  35. relationModel: mRelation,
  36. fileModel: mFile,
  37. }
  38. }
  39. // ChatAssistant 创建ChatAssistant对象
  40. type ChatAssistant struct {
  41. ChatAssistantModel model.IChatAssistant
  42. chatDatasetModel model.IChatDataset
  43. transModel model.ITrans
  44. datasetModel model.IDataset
  45. userModel model.IUser
  46. sessionModel model.IChatSession
  47. messageModel model.IChatMessage
  48. relationModel model.IDatasetRelation
  49. fileModel model.IDatasetFile
  50. }
  51. // Query 查询数据
  52. func (a *ChatAssistant) Query(ctx context.Context, params schema.ChatAssistantQueryParam, opts ...schema.ChatAssistantQueryOptions) (*schema.ChatAssistantQueryResult, error) {
  53. var userQueryParam schema.UserQueryParam
  54. userQueryParam.RoleCode = []string{"11", "12"}
  55. if !CheckIsRootUser(ctx) {
  56. user, err := a.userModel.Get(ctx, GetUserID(ctx))
  57. if err != nil {
  58. return nil, err
  59. }
  60. params.OrgId = user.OrgId
  61. userQueryParam.OrgId = user.OrgId
  62. }
  63. result, err := a.ChatAssistantModel.Query(ctx, params, opts...)
  64. if err != nil {
  65. return nil, err
  66. }
  67. chatDataset, err := a.chatDatasetModel.Query(ctx, schema.ChatDatasetQueryParam{})
  68. if err != nil {
  69. return nil, err
  70. }
  71. result.Data.FillDatasetId(chatDataset.Data)
  72. dataset, err := a.datasetModel.Query(ctx, schema.DatasetQueryParam{})
  73. if err != nil {
  74. return nil, err
  75. }
  76. result.Data.FillDataset(dataset.Data)
  77. users, err := a.userModel.Query(ctx, userQueryParam)
  78. if err != nil {
  79. return nil, err
  80. }
  81. result.Data.FillCreator(users.Data)
  82. sessions, err := a.sessionModel.Query(ctx, schema.ChatSessionQueryParam{})
  83. if err != nil {
  84. return nil, err
  85. }
  86. result.Data.FillSession(sessions.Data)
  87. return result, nil
  88. }
  89. // Get 查询指定数据
  90. func (a *ChatAssistant) Get(ctx context.Context, recordID string, opts ...schema.ChatAssistantQueryOptions) (*schema.ChatAssistant, error) {
  91. item, err := a.ChatAssistantModel.Get(ctx, recordID, opts...)
  92. if err != nil {
  93. return nil, err
  94. } else if item == nil {
  95. return nil, errors.ErrNotFound
  96. }
  97. user, err := a.userModel.Get(ctx, item.CreatorId)
  98. if err != nil {
  99. return nil, err
  100. }
  101. item.CreatorName = user.RealName
  102. chatDataset, err := a.chatDatasetModel.Query(ctx, schema.ChatDatasetQueryParam{ChatAssistantId: recordID})
  103. if err != nil {
  104. return nil, err
  105. }
  106. dataset, err := a.datasetModel.Query(ctx, schema.DatasetQueryParam{RecordIds: chatDataset.Data.ToDatasetIds()})
  107. if err != nil {
  108. return nil, err
  109. }
  110. users, err := a.userModel.Query(ctx, schema.UserQueryParam{RoleCode: []string{"11", "12"}})
  111. if err != nil {
  112. return nil, err
  113. }
  114. dataset.Data.FillCreator(users.Data)
  115. item.Datasets = dataset.Data
  116. sessions, err := a.sessionModel.Query(ctx, schema.ChatSessionQueryParam{AssistantId: item.RecordID})
  117. if err != nil {
  118. return nil, err
  119. }
  120. item.Sessions = sessions.Data
  121. return item, nil
  122. }
  123. // Create 创建数据
  124. func (a *ChatAssistant) Create(ctx context.Context, item schema.ChatAssistant) (*schema.ChatAssistant, error) {
  125. // root用户不能创建对话助手
  126. if CheckIsRootUser(ctx) {
  127. return nil, errors.New400Response("permission denied")
  128. }
  129. item.RecordID = guid.S()
  130. item.CreatorId = GetUserID(ctx)
  131. var ragDatasetIds []string
  132. // 获取用户组织id
  133. user, err := a.userModel.Get(ctx, item.CreatorId)
  134. if err != nil {
  135. return nil, err
  136. }
  137. item.OrgId = user.OrgId
  138. name := fmt.Sprintf("%s%s:%s", user.RealName, "对话助手", time.Now().Format("2006-01-02 15:04:05"))
  139. if item.Name != "" {
  140. name = item.Name
  141. }
  142. item.Name = name
  143. // 取当前用户(含其所属组织)能访问的全部知识库
  144. bizIds := []string{GetUserID(ctx)}
  145. if user.OrgId != "" {
  146. bizIds = append(bizIds, user.OrgId)
  147. }
  148. rel, err := a.relationModel.Query(ctx, schema.DatasetRelationQueryParam{BizIds: bizIds})
  149. if err != nil {
  150. return nil, err
  151. }
  152. dataset, err := a.datasetModel.Query(ctx, schema.DatasetQueryParam{RecordIds: rel.Data.ToDatasetIds()})
  153. if err != nil {
  154. return nil, err
  155. }
  156. err = a.CheckFiles(ctx, dataset.Data.ToRecordIds())
  157. if err != nil {
  158. return nil, err
  159. }
  160. ragDatasetIds = dataset.Data.ToRagDataIds()
  161. err = ExecTrans(ctx, a.transModel, func(ctx context.Context) error {
  162. for _, v := range dataset.Data {
  163. err = a.chatDatasetModel.Create(ctx, schema.ChatDataset{
  164. RecordID: guid.S(),
  165. ChatAssistantId: item.RecordID,
  166. DatasetId: v.RecordID,
  167. })
  168. if err != nil {
  169. return err
  170. }
  171. }
  172. resp, err := ragflow.GetHttpClient().CreateChat(ctx, &ragflow.CreateChatReq{
  173. Name: name,
  174. DatasetIDs: ragDatasetIds,
  175. })
  176. if err != nil {
  177. return err
  178. }
  179. item.RagChatId = resp.Data.ID
  180. err = a.ChatAssistantModel.Create(ctx, item)
  181. if err != nil {
  182. return err
  183. }
  184. if item.Source == 1 {
  185. user.H5AssistantId = item.RecordID
  186. err = a.userModel.Update(ctx, user.RecordID, *user)
  187. if err != nil {
  188. return err
  189. }
  190. }
  191. return nil
  192. })
  193. return a.Get(ctx, item.RecordID)
  194. }
  195. func (a *ChatAssistant) CheckFiles(ctx context.Context, datasetIds []string) error {
  196. files, err := a.fileModel.Query(ctx, schema.DatasetFileQueryParam{DatasetIds: datasetIds})
  197. if err != nil {
  198. return err
  199. }
  200. for _, v := range files.Data {
  201. if v.Enabled && v.ParseStatus == 2 {
  202. return nil
  203. }
  204. }
  205. return errors.New("用户关联知识库没有已解析的文件,不能使用对话助手,请联系管理员确认。")
  206. }
  207. // Update 更新数据
  208. func (a *ChatAssistant) Update(ctx context.Context, recordID string, item schema.ChatAssistant) error {
  209. oldItem, err := a.ChatAssistantModel.Get(ctx, recordID)
  210. if err != nil {
  211. return err
  212. } else if oldItem == nil {
  213. return errors.ErrNotFound
  214. }
  215. err = ExecTrans(ctx, a.transModel, func(ctx context.Context) error {
  216. err = a.ChatAssistantModel.Update(ctx, recordID, item)
  217. if err != nil {
  218. return err
  219. }
  220. if item.Name != oldItem.Name {
  221. _, err = ragflow.GetHttpClient().UpdateChat(ctx, item.RagChatId, &ragflow.UpdateChatReq{
  222. Name: item.Name,
  223. })
  224. if err != nil {
  225. return err
  226. }
  227. }
  228. return nil
  229. })
  230. return err
  231. }
  232. // Delete 删除数据
  233. func (a *ChatAssistant) Delete(ctx context.Context, recordID string) error {
  234. oldItem, err := a.ChatAssistantModel.Get(ctx, recordID)
  235. if err != nil {
  236. return err
  237. } else if oldItem == nil {
  238. return errors.ErrNotFound
  239. }
  240. err = ExecTrans(ctx, a.transModel, func(ctx context.Context) error {
  241. err = a.ChatAssistantModel.Delete(ctx, recordID)
  242. if err != nil {
  243. return err
  244. }
  245. _, err = ragflow.GetHttpClient().DeleteChats(ctx, []string{oldItem.RagChatId})
  246. if err != nil {
  247. return err
  248. }
  249. return nil
  250. })
  251. return err
  252. }
  253. // UpdateStatus 更新状态
  254. func (a *ChatAssistant) UpdateStatus(ctx context.Context, recordID string, status int) error {
  255. oldItem, err := a.ChatAssistantModel.Get(ctx, recordID)
  256. if err != nil {
  257. return err
  258. } else if oldItem == nil {
  259. return errors.ErrNotFound
  260. }
  261. return a.ChatAssistantModel.UpdateStatus(ctx, recordID, status)
  262. }
  263. // loadCurrentUserDatasets 取当前用户(及所属组织)可访问的全部知识库
  264. func (a *ChatAssistant) loadCurrentUserDatasets(ctx context.Context, user *schema.User) (schema.Datasets, error) {
  265. bizIds := []string{user.RecordID}
  266. rel, err := a.relationModel.Query(ctx, schema.DatasetRelationQueryParam{BizIds: bizIds})
  267. if err != nil {
  268. return nil, err
  269. }
  270. if len(rel.Data) == 0 {
  271. return schema.Datasets{}, nil
  272. }
  273. ds, err := a.datasetModel.Query(ctx, schema.DatasetQueryParam{RecordIds: rel.Data.ToDatasetIds()})
  274. if err != nil {
  275. return nil, err
  276. }
  277. return ds.Data, nil
  278. }
  279. // diffIds 返回 a 相比 b 的差集(按排序后的字符串切片)
  280. func diffIds(a, b []string) []string {
  281. m := make(map[string]struct{}, len(b))
  282. for _, v := range b {
  283. m[v] = struct{}{}
  284. }
  285. out := make([]string, 0)
  286. for _, v := range a {
  287. if _, ok := m[v]; !ok {
  288. out = append(out, v)
  289. }
  290. }
  291. sort.Strings(out)
  292. return out
  293. }
  294. // idsEqual 忽略顺序比较两组 id 是否一致
  295. func idsEqual(a, b []string) bool {
  296. if len(a) != len(b) {
  297. return false
  298. }
  299. aa := append([]string(nil), a...)
  300. bb := append([]string(nil), b...)
  301. sort.Strings(aa)
  302. sort.Strings(bb)
  303. for i := range aa {
  304. if aa[i] != bb[i] {
  305. return false
  306. }
  307. }
  308. return true
  309. }
  310. // CheckPermissionSync 对比当前登录用户的 KB 权限与其 h5 对话助手绑定的 KB
  311. func (a *ChatAssistant) CheckPermissionSync(ctx context.Context) (*schema.AssistantPermissionStatus, error) {
  312. if CheckIsRootUser(ctx) {
  313. return nil, errors.New400Response("permission denied")
  314. }
  315. userId := GetUserID(ctx)
  316. user, err := a.userModel.Get(ctx, userId)
  317. if err != nil {
  318. return nil, err
  319. } else if user == nil {
  320. return nil, errors.ErrNotFound
  321. }
  322. currentDatasets, err := a.loadCurrentUserDatasets(ctx, user)
  323. if err != nil {
  324. return nil, err
  325. }
  326. currentIds := currentDatasets.ToRecordIds()
  327. status := &schema.AssistantPermissionStatus{
  328. AssistantId: user.H5AssistantId,
  329. CurrentDatasetIds: currentIds,
  330. BoundDatasetIds: []string{},
  331. Added: []string{},
  332. Removed: []string{},
  333. }
  334. // 没有 h5 助手 => 需要创建
  335. if user.H5AssistantId == "" {
  336. status.Synced = false
  337. status.NeedCreate = true
  338. status.Added = append([]string{}, currentIds...)
  339. sort.Strings(status.Added)
  340. return status, nil
  341. }
  342. // 助手可能已被删除
  343. assistant, err := a.ChatAssistantModel.Get(ctx, user.H5AssistantId)
  344. if err != nil {
  345. return nil, err
  346. }
  347. if assistant == nil {
  348. status.Synced = false
  349. status.NeedCreate = true
  350. status.AssistantId = ""
  351. status.Added = append([]string{}, currentIds...)
  352. sort.Strings(status.Added)
  353. return status, nil
  354. }
  355. cd, err := a.chatDatasetModel.Query(ctx, schema.ChatDatasetQueryParam{ChatAssistantId: assistant.RecordID})
  356. if err != nil {
  357. return nil, err
  358. }
  359. boundIds := cd.Data.ToDatasetIds()
  360. status.BoundDatasetIds = boundIds
  361. status.Synced = idsEqual(currentIds, boundIds)
  362. status.Added = diffIds(currentIds, boundIds)
  363. status.Removed = diffIds(boundIds, currentIds)
  364. return status, nil
  365. }
  366. // SyncPermission 按当前用户的 KB 权限重建或创建 h5 对话助手、会话与欢迎消息
  367. // 流程:
  368. // 1. 无助手 => 创建 ragflow chat + 本地助手 + 本地 chat_dataset + 新会话 + 欢迎消息
  369. // 2. 已有助手且权限不一致 => 清理旧会话(含 ragflow)+ 重建 chat_dataset + 更新 ragflow chat + 创建新会话与欢迎消息
  370. // 3. 权限已一致 => 直接返回
  371. func (a *ChatAssistant) SyncPermission(ctx context.Context) (*schema.ChatAssistant, error) {
  372. if CheckIsRootUser(ctx) {
  373. return nil, errors.New400Response("permission denied")
  374. }
  375. userId := GetUserID(ctx)
  376. user, err := a.userModel.Get(ctx, userId)
  377. if err != nil {
  378. return nil, err
  379. } else if user == nil {
  380. return nil, errors.ErrNotFound
  381. }
  382. currentDatasets, err := a.loadCurrentUserDatasets(ctx, user)
  383. if err != nil {
  384. return nil, err
  385. }
  386. if len(currentDatasets) == 0 {
  387. return nil, errors.New400Response("当前用户没有可用的知识库,无法创建或更新对话助手")
  388. }
  389. if err := a.CheckFiles(ctx, currentDatasets.ToRecordIds()); err != nil {
  390. return nil, err
  391. }
  392. ragDatasetIds := currentDatasets.ToRagDataIds()
  393. // ---------- 1. 无助手:创建 ----------
  394. if user.H5AssistantId == "" {
  395. return a.createH5Assistant(ctx, user, currentDatasets, ragDatasetIds)
  396. }
  397. assistant, err := a.ChatAssistantModel.Get(ctx, user.H5AssistantId)
  398. if err != nil {
  399. return nil, err
  400. }
  401. // 助手已被删 => 重建
  402. if assistant == nil {
  403. user.H5AssistantId = ""
  404. return a.createH5Assistant(ctx, user, currentDatasets, ragDatasetIds)
  405. }
  406. // ---------- 2. 已有助手:检查是否一致 ----------
  407. cd, err := a.chatDatasetModel.Query(ctx, schema.ChatDatasetQueryParam{ChatAssistantId: assistant.RecordID})
  408. if err != nil {
  409. return nil, err
  410. }
  411. if idsEqual(currentDatasets.ToRecordIds(), cd.Data.ToDatasetIds()) {
  412. return a.Get(ctx, assistant.RecordID)
  413. }
  414. // ---------- 3. 已有助手且不一致:重建 ----------
  415. if err := a.rebuildAssistant(ctx, assistant, currentDatasets, ragDatasetIds); err != nil {
  416. return nil, err
  417. }
  418. return a.Get(ctx, assistant.RecordID)
  419. }
  420. // createH5Assistant 为用户创建一个 h5 对话助手,并同步创建一个新会话+欢迎消息
  421. func (a *ChatAssistant) createH5Assistant(ctx context.Context, user *schema.User, datasets schema.Datasets, ragDatasetIds []string) (*schema.ChatAssistant, error) {
  422. assistant := schema.ChatAssistant{
  423. RecordID: guid.S(),
  424. OrgId: user.OrgId,
  425. UserId: user.RecordID,
  426. Source: 1,
  427. CreatorId: user.RecordID,
  428. Name: fmt.Sprintf("%s%s:%s", user.RealName, "对话助手", time.Now().Format("2006-01-02 15:04:05")),
  429. }
  430. err := ExecTrans(ctx, a.transModel, func(ctx context.Context) error {
  431. resp, err := ragflow.GetHttpClient().CreateChat(ctx, &ragflow.CreateChatReq{
  432. Name: assistant.Name,
  433. DatasetIDs: ragDatasetIds,
  434. })
  435. if err != nil {
  436. return err
  437. }
  438. assistant.RagChatId = resp.Data.ID
  439. for _, d := range datasets {
  440. if err := a.chatDatasetModel.Create(ctx, schema.ChatDataset{
  441. RecordID: guid.S(),
  442. ChatAssistantId: assistant.RecordID,
  443. DatasetId: d.RecordID,
  444. RagDataId: d.RagDataId,
  445. }); err != nil {
  446. return err
  447. }
  448. }
  449. if err := a.ChatAssistantModel.Create(ctx, assistant); err != nil {
  450. return err
  451. }
  452. user.H5AssistantId = assistant.RecordID
  453. if err := a.userModel.Update(ctx, user.RecordID, *user); err != nil {
  454. return err
  455. }
  456. return a.createWelcomeSession(ctx, &assistant, datasets, user.RecordID)
  457. })
  458. if err != nil {
  459. return nil, err
  460. }
  461. return a.Get(ctx, assistant.RecordID)
  462. }
  463. // rebuildAssistant 清空助手的旧会话、旧知识库映射,按最新 KB 重建,并创建新会话与欢迎消息
  464. func (a *ChatAssistant) rebuildAssistant(ctx context.Context, assistant *schema.ChatAssistant, datasets schema.Datasets, ragDatasetIds []string) error {
  465. return ExecTrans(ctx, a.transModel, func(ctx context.Context) error {
  466. // 清理旧会话(先删 ragflow 侧再删本地)
  467. oldSessions, err := a.sessionModel.Query(ctx, schema.ChatSessionQueryParam{AssistantId: assistant.RecordID})
  468. if err != nil {
  469. return err
  470. }
  471. if len(oldSessions.Data) > 0 && assistant.RagChatId != "" {
  472. ragSessionIds := make([]string, 0, len(oldSessions.Data))
  473. for _, s := range oldSessions.Data {
  474. if s.RagSessionId != "" {
  475. ragSessionIds = append(ragSessionIds, s.RagSessionId)
  476. }
  477. }
  478. if len(ragSessionIds) > 0 {
  479. if _, err := ragflow.GetHttpClient().DeleteSessions(ctx, assistant.RagChatId, ragSessionIds); err != nil {
  480. glog.Errorf(ctx, "删除 ragflow 会话失败: %s", err.Error())
  481. }
  482. }
  483. }
  484. if err := a.sessionModel.DeleteByAssistantId(ctx, assistant.RecordID); err != nil {
  485. return err
  486. }
  487. // 重建 chat_dataset 映射
  488. if err := a.chatDatasetModel.DeleteByAssistantId(ctx, assistant.RecordID); err != nil {
  489. return err
  490. }
  491. for _, d := range datasets {
  492. if err := a.chatDatasetModel.Create(ctx, schema.ChatDataset{
  493. RecordID: guid.S(),
  494. ChatAssistantId: assistant.RecordID,
  495. DatasetId: d.RecordID,
  496. RagDataId: d.RagDataId,
  497. }); err != nil {
  498. return err
  499. }
  500. }
  501. // 同步 ragflow 助手绑定的知识库
  502. if assistant.RagChatId != "" {
  503. if _, err := ragflow.GetHttpClient().UpdateChat(ctx, assistant.RagChatId, &ragflow.UpdateChatReq{
  504. DatasetIDs: ragDatasetIds,
  505. }); err != nil {
  506. return err
  507. }
  508. }
  509. return a.createWelcomeSession(ctx, assistant, datasets, assistant.CreatorId)
  510. })
  511. }
  512. // createWelcomeSession 为助手创建一条新的 ragflow 会话,并写入一条欢迎消息
  513. func (a *ChatAssistant) createWelcomeSession(ctx context.Context, assistant *schema.ChatAssistant, datasets schema.Datasets, userId string) error {
  514. if assistant.RagChatId == "" {
  515. return nil
  516. }
  517. sessionName := fmt.Sprintf("%s%s", time.Now().Format("2006-01-02 15:04:05 "), "会话")
  518. resp, err := ragflow.GetHttpClient().CreateSession(ctx, assistant.RagChatId, &ragflow.CreateSessionReq{Name: sessionName})
  519. if err != nil {
  520. return err
  521. }
  522. session := schema.ChatSession{
  523. RecordID: guid.S(),
  524. Name: sessionName,
  525. AssistantId: assistant.RecordID,
  526. RagChatId: assistant.RagChatId,
  527. RagSessionId: resp.Data.Id,
  528. Source: assistant.Source,
  529. CreatorId: userId,
  530. }
  531. if err := a.sessionModel.Create(ctx, session); err != nil {
  532. return err
  533. }
  534. datasetNames := make([]string, 0, len(datasets))
  535. for _, d := range datasets {
  536. datasetNames = append(datasetNames, fmt.Sprintf("《%s》", d.Name))
  537. }
  538. answer := fmt.Sprintf("你好,欢迎使用知识库问答助手!关于%s的疑问,我都会尽力为你解答,快来提问吧!", strings.Join(datasetNames, ","))
  539. message := schema.ChatMessage{
  540. RecordID: guid.S(),
  541. UserId: userId,
  542. AssistantId: assistant.RecordID,
  543. SessionId: session.RecordID,
  544. RagSessionId: resp.Data.Id,
  545. Question: "",
  546. Answer: strings.ReplaceAll(answer, `\`, ``),
  547. CreatorId: userId,
  548. }
  549. return a.messageModel.Create(ctx, message)
  550. }