channels.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712
  1. import json
  2. import logging
  3. from typing import Optional
  4. from fastapi import APIRouter, Depends, HTTPException, Request, status, BackgroundTasks
  5. from pydantic import BaseModel
  6. from open_webui.socket.main import sio, get_user_ids_from_room
  7. from open_webui.models.users import Users, UserNameResponse
  8. from open_webui.models.channels import Channels, ChannelModel, ChannelForm
  9. from open_webui.models.messages import (
  10. Messages,
  11. MessageModel,
  12. MessageResponse,
  13. MessageForm,
  14. )
  15. from open_webui.config import ENABLE_ADMIN_CHAT_ACCESS, ENABLE_ADMIN_EXPORT
  16. from open_webui.constants import ERROR_MESSAGES
  17. from open_webui.env import SRC_LOG_LEVELS
  18. from open_webui.utils.auth import get_admin_user, get_verified_user
  19. from open_webui.utils.access_control import has_access, get_users_with_access
  20. from open_webui.utils.webhook import post_webhook
  21. log = logging.getLogger(__name__)
  22. log.setLevel(SRC_LOG_LEVELS["MODELS"])
  23. router = APIRouter()
  24. ############################
  25. # GetChatList
  26. ############################
  27. @router.get("/", response_model=list[ChannelModel])
  28. async def get_channels(user=Depends(get_verified_user)):
  29. if user.role == "admin":
  30. return Channels.get_channels()
  31. else:
  32. return Channels.get_channels_by_user_id(user.id)
  33. ############################
  34. # CreateNewChannel
  35. ############################
  36. @router.post("/create", response_model=Optional[ChannelModel])
  37. async def create_new_channel(form_data: ChannelForm, user=Depends(get_admin_user)):
  38. try:
  39. channel = Channels.insert_new_channel(None, form_data, user.id)
  40. return ChannelModel(**channel.model_dump())
  41. except Exception as e:
  42. log.exception(e)
  43. raise HTTPException(
  44. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  45. )
  46. ############################
  47. # GetChannelById
  48. ############################
  49. @router.get("/{id}", response_model=Optional[ChannelModel])
  50. async def get_channel_by_id(id: str, user=Depends(get_verified_user)):
  51. channel = Channels.get_channel_by_id(id)
  52. if not channel:
  53. raise HTTPException(
  54. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  55. )
  56. if user.role != "admin" and not has_access(
  57. user.id, type="read", access_control=channel.access_control
  58. ):
  59. raise HTTPException(
  60. status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
  61. )
  62. return ChannelModel(**channel.model_dump())
  63. ############################
  64. # UpdateChannelById
  65. ############################
  66. @router.post("/{id}/update", response_model=Optional[ChannelModel])
  67. async def update_channel_by_id(
  68. id: str, form_data: ChannelForm, user=Depends(get_admin_user)
  69. ):
  70. channel = Channels.get_channel_by_id(id)
  71. if not channel:
  72. raise HTTPException(
  73. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  74. )
  75. try:
  76. channel = Channels.update_channel_by_id(id, form_data)
  77. return ChannelModel(**channel.model_dump())
  78. except Exception as e:
  79. log.exception(e)
  80. raise HTTPException(
  81. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  82. )
  83. ############################
  84. # DeleteChannelById
  85. ############################
  86. @router.delete("/{id}/delete", response_model=bool)
  87. async def delete_channel_by_id(id: str, user=Depends(get_admin_user)):
  88. channel = Channels.get_channel_by_id(id)
  89. if not channel:
  90. raise HTTPException(
  91. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  92. )
  93. try:
  94. Channels.delete_channel_by_id(id)
  95. return True
  96. except Exception as e:
  97. log.exception(e)
  98. raise HTTPException(
  99. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  100. )
  101. ############################
  102. # GetChannelMessages
  103. ############################
  104. class MessageUserResponse(MessageResponse):
  105. user: UserNameResponse
  106. @router.get("/{id}/messages", response_model=list[MessageUserResponse])
  107. async def get_channel_messages(
  108. id: str, skip: int = 0, limit: int = 50, user=Depends(get_verified_user)
  109. ):
  110. channel = Channels.get_channel_by_id(id)
  111. if not channel:
  112. raise HTTPException(
  113. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  114. )
  115. if user.role != "admin" and not has_access(
  116. user.id, type="read", access_control=channel.access_control
  117. ):
  118. raise HTTPException(
  119. status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
  120. )
  121. message_list = Messages.get_messages_by_channel_id(id, skip, limit)
  122. users = {}
  123. messages = []
  124. for message in message_list:
  125. if message.user_id not in users:
  126. user = Users.get_user_by_id(message.user_id)
  127. users[message.user_id] = user
  128. replies = Messages.get_replies_by_message_id(message.id)
  129. latest_reply_at = replies[0].created_at if replies else None
  130. messages.append(
  131. MessageUserResponse(
  132. **{
  133. **message.model_dump(),
  134. "reply_count": len(replies),
  135. "latest_reply_at": latest_reply_at,
  136. "reactions": Messages.get_reactions_by_message_id(message.id),
  137. "user": UserNameResponse(**users[message.user_id].model_dump()),
  138. }
  139. )
  140. )
  141. return messages
  142. ############################
  143. # PostNewMessage
  144. ############################
  145. async def send_notification(name, webui_url, channel, message, active_user_ids):
  146. users = get_users_with_access("read", channel.access_control)
  147. for user in users:
  148. if user.id in active_user_ids:
  149. continue
  150. else:
  151. if user.settings:
  152. webhook_url = user.settings.ui.get("notifications", {}).get(
  153. "webhook_url", None
  154. )
  155. if webhook_url:
  156. post_webhook(
  157. name,
  158. webhook_url,
  159. f"#{channel.name} - {webui_url}/channels/{channel.id}\n\n{message.content}",
  160. {
  161. "action": "channel",
  162. "message": message.content,
  163. "title": channel.name,
  164. "url": f"{webui_url}/channels/{channel.id}",
  165. },
  166. )
  167. @router.post("/{id}/messages/post", response_model=Optional[MessageModel])
  168. async def post_new_message(
  169. request: Request,
  170. id: str,
  171. form_data: MessageForm,
  172. background_tasks: BackgroundTasks,
  173. user=Depends(get_verified_user),
  174. ):
  175. channel = Channels.get_channel_by_id(id)
  176. if not channel:
  177. raise HTTPException(
  178. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  179. )
  180. if user.role != "admin" and not has_access(
  181. user.id, type="read", access_control=channel.access_control
  182. ):
  183. raise HTTPException(
  184. status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
  185. )
  186. try:
  187. message = Messages.insert_new_message(form_data, channel.id, user.id)
  188. if message:
  189. event_data = {
  190. "channel_id": channel.id,
  191. "message_id": message.id,
  192. "data": {
  193. "type": "message",
  194. "data": MessageUserResponse(
  195. **{
  196. **message.model_dump(),
  197. "reply_count": 0,
  198. "latest_reply_at": None,
  199. "reactions": Messages.get_reactions_by_message_id(
  200. message.id
  201. ),
  202. "user": UserNameResponse(**user.model_dump()),
  203. }
  204. ).model_dump(),
  205. },
  206. "user": UserNameResponse(**user.model_dump()).model_dump(),
  207. "channel": channel.model_dump(),
  208. }
  209. await sio.emit(
  210. "channel-events",
  211. event_data,
  212. to=f"channel:{channel.id}",
  213. )
  214. if message.parent_id:
  215. # If this message is a reply, emit to the parent message as well
  216. parent_message = Messages.get_message_by_id(message.parent_id)
  217. if parent_message:
  218. await sio.emit(
  219. "channel-events",
  220. {
  221. "channel_id": channel.id,
  222. "message_id": parent_message.id,
  223. "data": {
  224. "type": "message:reply",
  225. "data": MessageUserResponse(
  226. **{
  227. **parent_message.model_dump(),
  228. "user": UserNameResponse(
  229. **Users.get_user_by_id(
  230. parent_message.user_id
  231. ).model_dump()
  232. ),
  233. }
  234. ).model_dump(),
  235. },
  236. "user": UserNameResponse(**user.model_dump()).model_dump(),
  237. "channel": channel.model_dump(),
  238. },
  239. to=f"channel:{channel.id}",
  240. )
  241. active_user_ids = get_user_ids_from_room(f"channel:{channel.id}")
  242. background_tasks.add_task(
  243. send_notification,
  244. request.app.state.WEBUI_NAME,
  245. request.app.state.config.WEBUI_URL,
  246. channel,
  247. message,
  248. active_user_ids,
  249. )
  250. return MessageModel(**message.model_dump())
  251. except Exception as e:
  252. log.exception(e)
  253. raise HTTPException(
  254. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  255. )
  256. ############################
  257. # GetChannelMessage
  258. ############################
  259. @router.get("/{id}/messages/{message_id}", response_model=Optional[MessageUserResponse])
  260. async def get_channel_message(
  261. id: str, message_id: str, user=Depends(get_verified_user)
  262. ):
  263. channel = Channels.get_channel_by_id(id)
  264. if not channel:
  265. raise HTTPException(
  266. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  267. )
  268. if user.role != "admin" and not has_access(
  269. user.id, type="read", access_control=channel.access_control
  270. ):
  271. raise HTTPException(
  272. status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
  273. )
  274. message = Messages.get_message_by_id(message_id)
  275. if not message:
  276. raise HTTPException(
  277. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  278. )
  279. if message.channel_id != id:
  280. raise HTTPException(
  281. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  282. )
  283. return MessageUserResponse(
  284. **{
  285. **message.model_dump(),
  286. "user": UserNameResponse(
  287. **Users.get_user_by_id(message.user_id).model_dump()
  288. ),
  289. }
  290. )
  291. ############################
  292. # GetChannelThreadMessages
  293. ############################
  294. @router.get(
  295. "/{id}/messages/{message_id}/thread", response_model=list[MessageUserResponse]
  296. )
  297. async def get_channel_thread_messages(
  298. id: str,
  299. message_id: str,
  300. skip: int = 0,
  301. limit: int = 50,
  302. user=Depends(get_verified_user),
  303. ):
  304. channel = Channels.get_channel_by_id(id)
  305. if not channel:
  306. raise HTTPException(
  307. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  308. )
  309. if user.role != "admin" and not has_access(
  310. user.id, type="read", access_control=channel.access_control
  311. ):
  312. raise HTTPException(
  313. status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
  314. )
  315. message_list = Messages.get_messages_by_parent_id(id, message_id, skip, limit)
  316. users = {}
  317. messages = []
  318. for message in message_list:
  319. if message.user_id not in users:
  320. user = Users.get_user_by_id(message.user_id)
  321. users[message.user_id] = user
  322. messages.append(
  323. MessageUserResponse(
  324. **{
  325. **message.model_dump(),
  326. "reply_count": 0,
  327. "latest_reply_at": None,
  328. "reactions": Messages.get_reactions_by_message_id(message.id),
  329. "user": UserNameResponse(**users[message.user_id].model_dump()),
  330. }
  331. )
  332. )
  333. return messages
  334. ############################
  335. # UpdateMessageById
  336. ############################
  337. @router.post(
  338. "/{id}/messages/{message_id}/update", response_model=Optional[MessageModel]
  339. )
  340. async def update_message_by_id(
  341. id: str, message_id: str, form_data: MessageForm, user=Depends(get_verified_user)
  342. ):
  343. channel = Channels.get_channel_by_id(id)
  344. if not channel:
  345. raise HTTPException(
  346. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  347. )
  348. if user.role != "admin" and not has_access(
  349. user.id, type="read", access_control=channel.access_control
  350. ):
  351. raise HTTPException(
  352. status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
  353. )
  354. message = Messages.get_message_by_id(message_id)
  355. if not message:
  356. raise HTTPException(
  357. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  358. )
  359. if message.channel_id != id:
  360. raise HTTPException(
  361. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  362. )
  363. try:
  364. message = Messages.update_message_by_id(message_id, form_data)
  365. message = Messages.get_message_by_id(message_id)
  366. if message:
  367. await sio.emit(
  368. "channel-events",
  369. {
  370. "channel_id": channel.id,
  371. "message_id": message.id,
  372. "data": {
  373. "type": "message:update",
  374. "data": MessageUserResponse(
  375. **{
  376. **message.model_dump(),
  377. "user": UserNameResponse(
  378. **user.model_dump()
  379. ).model_dump(),
  380. }
  381. ).model_dump(),
  382. },
  383. "user": UserNameResponse(**user.model_dump()).model_dump(),
  384. "channel": channel.model_dump(),
  385. },
  386. to=f"channel:{channel.id}",
  387. )
  388. return MessageModel(**message.model_dump())
  389. except Exception as e:
  390. log.exception(e)
  391. raise HTTPException(
  392. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  393. )
  394. ############################
  395. # AddReactionToMessage
  396. ############################
  397. class ReactionForm(BaseModel):
  398. name: str
  399. @router.post("/{id}/messages/{message_id}/reactions/add", response_model=bool)
  400. async def add_reaction_to_message(
  401. id: str, message_id: str, form_data: ReactionForm, user=Depends(get_verified_user)
  402. ):
  403. channel = Channels.get_channel_by_id(id)
  404. if not channel:
  405. raise HTTPException(
  406. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  407. )
  408. if user.role != "admin" and not has_access(
  409. user.id, type="read", access_control=channel.access_control
  410. ):
  411. raise HTTPException(
  412. status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
  413. )
  414. message = Messages.get_message_by_id(message_id)
  415. if not message:
  416. raise HTTPException(
  417. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  418. )
  419. if message.channel_id != id:
  420. raise HTTPException(
  421. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  422. )
  423. try:
  424. Messages.add_reaction_to_message(message_id, user.id, form_data.name)
  425. message = Messages.get_message_by_id(message_id)
  426. await sio.emit(
  427. "channel-events",
  428. {
  429. "channel_id": channel.id,
  430. "message_id": message.id,
  431. "data": {
  432. "type": "message:reaction:add",
  433. "data": {
  434. **message.model_dump(),
  435. "user": UserNameResponse(
  436. **Users.get_user_by_id(message.user_id).model_dump()
  437. ).model_dump(),
  438. "name": form_data.name,
  439. },
  440. },
  441. "user": UserNameResponse(**user.model_dump()).model_dump(),
  442. "channel": channel.model_dump(),
  443. },
  444. to=f"channel:{channel.id}",
  445. )
  446. return True
  447. except Exception as e:
  448. log.exception(e)
  449. raise HTTPException(
  450. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  451. )
  452. ############################
  453. # RemoveReactionById
  454. ############################
  455. @router.post("/{id}/messages/{message_id}/reactions/remove", response_model=bool)
  456. async def remove_reaction_by_id_and_user_id_and_name(
  457. id: str, message_id: str, form_data: ReactionForm, user=Depends(get_verified_user)
  458. ):
  459. channel = Channels.get_channel_by_id(id)
  460. if not channel:
  461. raise HTTPException(
  462. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  463. )
  464. if user.role != "admin" and not has_access(
  465. user.id, type="read", access_control=channel.access_control
  466. ):
  467. raise HTTPException(
  468. status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
  469. )
  470. message = Messages.get_message_by_id(message_id)
  471. if not message:
  472. raise HTTPException(
  473. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  474. )
  475. if message.channel_id != id:
  476. raise HTTPException(
  477. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  478. )
  479. try:
  480. Messages.remove_reaction_by_id_and_user_id_and_name(
  481. message_id, user.id, form_data.name
  482. )
  483. message = Messages.get_message_by_id(message_id)
  484. await sio.emit(
  485. "channel-events",
  486. {
  487. "channel_id": channel.id,
  488. "message_id": message.id,
  489. "data": {
  490. "type": "message:reaction:remove",
  491. "data": {
  492. **message.model_dump(),
  493. "user": UserNameResponse(
  494. **Users.get_user_by_id(message.user_id).model_dump()
  495. ).model_dump(),
  496. "name": form_data.name,
  497. },
  498. },
  499. "user": UserNameResponse(**user.model_dump()).model_dump(),
  500. "channel": channel.model_dump(),
  501. },
  502. to=f"channel:{channel.id}",
  503. )
  504. return True
  505. except Exception as e:
  506. log.exception(e)
  507. raise HTTPException(
  508. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  509. )
  510. ############################
  511. # DeleteMessageById
  512. ############################
  513. @router.delete("/{id}/messages/{message_id}/delete", response_model=bool)
  514. async def delete_message_by_id(
  515. id: str, message_id: str, user=Depends(get_verified_user)
  516. ):
  517. channel = Channels.get_channel_by_id(id)
  518. if not channel:
  519. raise HTTPException(
  520. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  521. )
  522. if user.role != "admin" and not has_access(
  523. user.id, type="read", access_control=channel.access_control
  524. ):
  525. raise HTTPException(
  526. status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
  527. )
  528. message = Messages.get_message_by_id(message_id)
  529. if not message:
  530. raise HTTPException(
  531. status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
  532. )
  533. if message.channel_id != id:
  534. raise HTTPException(
  535. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  536. )
  537. try:
  538. Messages.delete_message_by_id(message_id)
  539. await sio.emit(
  540. "channel-events",
  541. {
  542. "channel_id": channel.id,
  543. "message_id": message.id,
  544. "data": {
  545. "type": "message:delete",
  546. "data": {
  547. **message.model_dump(),
  548. "user": UserNameResponse(**user.model_dump()).model_dump(),
  549. },
  550. },
  551. "user": UserNameResponse(**user.model_dump()).model_dump(),
  552. "channel": channel.model_dump(),
  553. },
  554. to=f"channel:{channel.id}",
  555. )
  556. if message.parent_id:
  557. # If this message is a reply, emit to the parent message as well
  558. parent_message = Messages.get_message_by_id(message.parent_id)
  559. if parent_message:
  560. await sio.emit(
  561. "channel-events",
  562. {
  563. "channel_id": channel.id,
  564. "message_id": parent_message.id,
  565. "data": {
  566. "type": "message:reply",
  567. "data": MessageUserResponse(
  568. **{
  569. **parent_message.model_dump(),
  570. "user": UserNameResponse(
  571. **Users.get_user_by_id(
  572. parent_message.user_id
  573. ).model_dump()
  574. ),
  575. }
  576. ).model_dump(),
  577. },
  578. "user": UserNameResponse(**user.model_dump()).model_dump(),
  579. "channel": channel.model_dump(),
  580. },
  581. to=f"channel:{channel.id}",
  582. )
  583. return True
  584. except Exception as e:
  585. log.exception(e)
  586. raise HTTPException(
  587. status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
  588. )