Compare commits

..
333 Commits
Author SHA1 Message Date
Danny AvilaandGitHub 1308ef1394 Merge pull request #88 from wtlyu/feat-clean-after-regenerate-pr
Feat clean after regenerate pr
2023-03-17 15:31:32 -04:00
Danny Avila 1513c27f7d adjust: wording and placeholder text size 2023-03-17 14:48:21 -04:00
Wentao Lyu 0ff3bbb28f clean up 2023-03-18 02:15:24 +08:00
Wentao Lyu 7987c0100c fix: add crypto as deps. [need confirm] 2023-03-18 02:04:51 +08:00
Wentao Lyu e8611a1d07 fix: don't reset isSubmitting in useEffect of submission 2023-03-18 01:54:06 +08:00
Wentao Lyu ce78123369 fix: allow delete of last char while submitting 2023-03-18 01:24:35 +08:00
Wentao Lyu a90db1f1a4 fix: add model to customGpts
fix: filter models by model only
2023-03-18 01:12:45 +08:00
Wentao Lyu a213868b17 fix: set isSubmitting with messages together
style: some notification
2023-03-18 01:12:45 +08:00
Wentao Lyu 6b2a2bb858 feat: save cancelled flag in message 2023-03-18 01:11:44 +08:00
Wentao Lyu fea3afa740 fix: missing import 2023-03-18 01:10:20 +08:00
Wentao Lyu 7372b37fe6 style: hide footer in mobile mode
style: avoid ui overflow in mobile mode
2023-03-18 01:09:59 +08:00
Danny AvilaandGitHub e11ce141d7 Merge pull request #86 from danny-avila/fix-scroll
fix: fix weird scrolling behavior on last message
2023-03-17 12:38:15 -04:00
Danny Avila 46fbd3b66a fix: fix weird scrolling behavior on last message 2023-03-17 12:34:54 -04:00
Danny AvilaandGitHub ce3f03267a Merge pull request #80 from wtlyu/feat-regenerate-and-cancel
Feat regenerate and cancel
2023-03-17 10:40:45 -04:00
Danny AvilaandWentao Lyu 9a2392e4d5 fix: mobile styling after regen buttons 2023-03-17 22:20:23 +08:00
Danny AvilaandGitHub 1eab4d240d Merge pull request #85 from danny-avila/bing-refusal
Bing refusal
2023-03-17 09:14:40 -04:00
Danny Avila 5568a60174 handle bing message refusal 2023-03-17 09:11:45 -04:00
Danny Avila ea4180f22a remove detect code (not always accurate) 2023-03-17 09:11:31 -04:00
Wentao Lyu 2310bab348 revert: dont overwrite the mobile input panel color 2023-03-17 03:54:15 +08:00
Wentao Lyu d64edfdc7d feat: add chrome title color 2023-03-17 03:50:34 +08:00
Wentao Lyu a8c53f1f0d mobile style, for input panel and regenerate buttons 2023-03-17 03:50:34 +08:00
Wentao Lyu ef9f1ee1cf feat: cancellable api request 2023-03-17 03:50:34 +08:00
Wentao Lyu 66ad54168a feat: regenerate for bingai 2023-03-17 03:50:34 +08:00
Wentao Lyu 0891566d1e feat: add regenerate to all response message as official 2023-03-17 03:49:59 +08:00
Wentao Lyu e3b0cb7db7 feat: show model at the top of conversation messages. 2023-03-17 03:48:45 +08:00
Danny Avila c27554ed2e fix: allow customs in model filter 2023-03-16 15:05:23 -04:00
Danny AvilaandGitHub 87f793f1c4 Merge pull request #79 from danny-avila/improve-titles
fix: add deterministic titling
2023-03-16 15:03:04 -04:00
Danny Avila 4078c5283b fix: add deterministic titling 2023-03-16 15:02:40 -04:00
Danny AvilaandGitHub 5cac7e48f0 Merge pull request #74 from wtlyu/feat-model-based-on-key
feat: show model based on configured Keys
2023-03-16 15:00:23 -04:00
Danny AvilaandGitHub 7a08c77850 Update README.md 2023-03-16 14:11:24 -04:00
Danny AvilaandGitHub 1fe9e29187 Merge branch 'master' into feat-model-based-on-key 2023-03-16 13:39:41 -04:00
Danny AvilaandGitHub ba8692dbe4 Merge pull request #59 from wtlyu/feat-usersys
Feat usersys
2023-03-16 13:35:03 -04:00
Danny AvilaandGitHub d30b406c4c Merge pull request #4 from wtlyu/revisions
Revisions
2023-03-16 13:34:36 -04:00
Danny Avila 915cda70ef fix: user:user redundancies 2023-03-16 13:32:04 -04:00
Danny Avila df19595c5b fix: icon and mobile textarea styling 2023-03-16 13:30:10 -04:00
Danny Avila 867b3073d4 fix: icon and mobile textarea styling 2023-03-16 13:29:13 -04:00
Danny AvilaandGitHub e4e28dbbe2 Update README.md 2023-03-16 10:57:44 -04:00
Danny AvilaandGitHub cdbc0e21e7 Update README.md 2023-03-16 10:57:18 -04:00
Wentao Lyu b6f7f95709 feat: set default model once model list read. 2023-03-16 21:12:33 +08:00
Wentao Lyu 131af50034 feat: show model based on configured Keys 2023-03-16 14:44:14 +08:00
Wentao Lyu 23c050b54e style: mobile style of landing 2023-03-16 14:07:48 +08:00
Wentao Lyu 7442294c41 fix: mobile sizing style of icon 2023-03-16 14:05:12 +08:00
Wentao Lyu a47dbe6262 feat: use same icon style in TextChat 2023-03-16 13:45:46 +08:00
Wentao Lyu aabb19656e feat: combine customgpt to user 2023-03-16 13:30:20 +08:00
Wentao Lyu b0284b6974 sync updates and merge with feat-resubmit 2023-03-16 13:20:54 +08:00
Wentao Lyu 62d88380e0 feat: add sample multi-user support
feat: update README
2023-03-16 13:20:34 +08:00
Daniel Avila 41f351786f fix: oversight, artifact in bing route 2023-03-15 22:36:16 -04:00
Danny AvilaandGitHub ffcfb69dee Merge pull request #72 from danny-avila/fix-mobile-switch
fix: mobile view for sibling switch
2023-03-15 19:06:41 -04:00
Daniel Avila c91ce36227 fix: mobile view for sibling switch 2023-03-15 19:05:17 -04:00
Danny AvilaandGitHub d06e58f043 Merge pull request #55 from wtlyu/feat-resubmit
Feature: multipath message & resubmit, conversation placeholder, separate title generation, and some bug fix.
2023-03-15 18:06:35 -04:00
Daniel Avila d052d221dc chore: switch focus to textarea when custom model change (still need to figure out reg model change) 2023-03-15 18:05:34 -04:00
Danny AvilaandGitHub ff45511011 Merge pull request #3 from wtlyu/final-adj
Final adjustments
2023-03-15 17:08:18 -04:00
Danny Avila 8c6340aed0 chore: refactor cursor blink, debugging 2023-03-15 16:38:01 -04:00
Danny Avila 84b104e65f chore: delegate response text parsing to one location 2023-03-15 15:44:48 -04:00
Danny Avila a0c94715ce chore: refactor titleConvo 2023-03-15 15:21:04 -04:00
Danny Avila a8aad30fc8 chore: memoized Messages component, will require custom equality check 2023-03-15 14:36:17 -04:00
Danny Avila 2fd50c99b8 fix: debounce title request and handle error with default title 2023-03-15 12:47:30 -04:00
Danny Avila 96ca783517 chore: re-organize message modules, fix icon size, convo reset properly rebuilds Tree 2023-03-15 10:42:45 -04:00
Wentao Lyu 45ca0a8713 fix: gptCustom icon should show as same in model and message 2023-03-15 14:42:39 +08:00
Wentao Lyu 5d0b849930 feat: show icon within model select menu
fix: use icon for gptCustom
2023-03-15 14:21:08 +08:00
Wentao Lyu 54aa9debb4 fix: don't reset new convo if model not change
fix: change model will clear all messages.
2023-03-15 13:38:01 +08:00
Daniel Avila 4e91437049 fix: convo resets, sets new Convo 2023-03-14 21:32:25 -04:00
Daniel Avila 918f2fecb6 fix: convo resets on model change 2023-03-14 21:25:02 -04:00
Daniel Avila 796d8031e8 fix: ensure custom params are not passed to non custom models 2023-03-14 20:21:41 -04:00
Daniel Avila 6e32f71565 fix: adjust custom client for new progress CB 2023-03-14 20:15:06 -04:00
Daniel Avila 6192c2964e fix: validation to avoid saving customGpt params to non-custom models 2023-03-14 20:14:38 -04:00
Daniel Avila 626a8fbd8e fix: if db is empty, will never show new convos until refresh 2023-03-14 18:53:46 -04:00
Danny AvilaandGitHub a8344ec5bf Merge pull request #2 from wtlyu/refactors
Refactors
2023-03-14 18:16:44 -04:00
Danny AvilaandGitHub c230fe41f4 Merge branch 'feat-resubmit' into refactors 2023-03-14 16:09:25 -04:00
Danny Avila d0ef0f84c8 chore: delegate text handling to one place, html sanitization in progress 2023-03-14 16:05:46 -04:00
Wentao Lyu 8289558d94 feat: pagination in nav 2023-03-15 04:05:14 +08:00
Danny Avila 2e20b28c4d chore: refactor progressCB to one place, fix sydney, and sanitize html 2023-03-14 15:42:59 -04:00
Danny Avila 9a17e94f8f fix: refactor migration and sort old convos correctly 2023-03-14 14:51:26 -04:00
Wentao Lyu 71fc86b9a6 fix: buildTree should store parent-not-exist message as root. rather than dropping them. 2023-03-15 02:43:21 +08:00
Wentao Lyu 8882432210 fix: hide the edit button when bingai 2023-03-15 02:33:08 +08:00
Wentao Lyu 644f3f716f feat: re-orginazed three ask api. To provide ability to reproduce message.
feat: bing and sydney come to work again, [need more test]
2023-03-15 02:15:46 +08:00
Wentao Lyu 8b00805d24 fix: dont send gen_title twice 2023-03-15 02:15:46 +08:00
Wentao Lyu d73375958b feat: return error as a error message, not only text 2023-03-15 02:15:46 +08:00
Wentao Lyu 7168498543 fix: jailbreakConversationId=false response will be saved jailbreakConversationId='false' in database. 2023-03-15 02:15:46 +08:00
Wentao Lyu 27515cb00a fix: generate title by backend 2023-03-15 02:15:46 +08:00
Daniel AvilaandWentao Lyu 3e7ce67609 fix: migrate old schema to new 2023-03-15 02:15:46 +08:00
Daniel AvilaandWentao Lyu 4fd05e15b4 fix: migrate old schema to new 2023-03-15 02:15:46 +08:00
Wentao Lyu 0fa19bb6ad feat: save error message into database. 2023-03-15 02:15:46 +08:00
Wentao Lyu d9e5464b3b fix: cleanup debug msg 2023-03-15 02:15:46 +08:00
Wentao Lyu 953c5fc970 fix: w<1024px, will overflow. 2023-03-15 02:15:46 +08:00
Wentao Lyu 2afbc5883f fix: use onCompositionStart and onCompositionEnd to aviod enter submit when using input method. 2023-03-15 02:15:46 +08:00
Wentao Lyu 953f846958 fix: loading and send button, mobile style
feat: sibling switch, mobile style
fix: only the real submitting message will blink
feat: drop the text version username, use a similar square. (or it will mass up the sibling switch)
2023-03-15 02:15:46 +08:00
Wentao Lyu a4d5f6a3f2 feat: fully multipath and resubmit 2023-03-15 02:15:46 +08:00
Wentao Lyu 4a39965b22 fix: add proxy to titleConvo 2023-03-15 02:15:46 +08:00
Wentao Lyu 90dc171b34 test: generate of title shouldn't be mislead by answer 2023-03-15 02:15:46 +08:00
Wentao Lyu 0e98cb4206 fix: in mobile view, resubmit edit button should always visible 2023-03-15 02:15:46 +08:00
Wentao Lyu 9f8e9cb091 feat: gen title by sperate api call
feat:

fix: rename of convo should based on real request.
2023-03-15 02:15:46 +08:00
Wentao Lyu 8773878be2 feat: create conversation at the beginning then return the userMessage 2023-03-15 02:15:46 +08:00
Wentao Lyu 5a409ccfa6 fix: don't resubmit html label
fix: hide the resubmit editor border
2023-03-15 02:15:46 +08:00
Wentao Lyu 0ed8a40a41 feat: merge all message.id into message.messageId
feat: the first message will have a parentMessageId as 00000000-0000-0000-0000-000000000000 (in order not to create new convo when resubmit)
feat: ask will return the userMessage as well, to send back the messageId

TODO: comment out the title generation.
TODO: bing version need to be test

fix: never use the same messageId
fix: never delete exist messages
fix: connect response.parentMessageId to the userMessage.messageId
fix: set default convo title as new Chat
2023-03-15 02:15:46 +08:00
Wentao Lyu be71140dd4 fix: new message should append to the exist one 2023-03-15 02:15:46 +08:00
Wentao Lyu 6d51ec3e37 feat: auto hide edit button when edit is enabled. 2023-03-15 02:15:46 +08:00
Wentao Lyu bdfc895800 feat: support resubmit.
TODO: basic implementation. should add multi-path record in future.

feat: add deleteMessahesSince
feat: saveMessage will do createOrSave
feat: reorginazed submission
2023-03-15 02:15:46 +08:00
Wentao Lyu b9975ac283 fix: missing setSubmission 2023-03-15 02:15:46 +08:00
Wentao Lyu dd1f74da72 replace all created to timestamps: true in db 2023-03-15 02:15:46 +08:00
Danny AvilaandGitHub 5d65427a6e Merge pull request #56 from HyunggyuJang/build/docker-from-rootdir
Build docker files from root dir
2023-03-13 13:17:23 -04:00
Danny AvilaandGitHub 6a16d31f26 Merge pull request #60 from wtlyu/feat-autofocus
feat: auto focus
2023-03-13 13:15:43 -04:00
Wentao Lyu 8affbfdb4a feat: auto focus 2023-03-14 01:12:11 +08:00
Hyunggyu Jang 70d59c5b3c Build docker files from root dir 2023-03-13 22:49:40 +09:00
Danny AvilaandGitHub 73979ee67f Merge pull request #54 from danny-avila/minor-fixes
fix: model menu key issue
2023-03-12 16:46:25 -04:00
Daniel Avila 8e513d83a5 fix: model menu key issue 2023-03-12 16:45:44 -04:00
Danny AvilaandGitHub 9bc85ea83d Merge pull request #47 from wtlyu/feat-title-with-language
fix: give a stronger prompt to generate tile using the input language
2023-03-12 15:10:45 -04:00
Wentao Lyu 943eb5c74d fix: give a stronger prompt to generate tile using the input language 2023-03-13 01:51:32 +08:00
Danny AvilaandGitHub bac37cfe36 Update README.md 2023-03-11 23:29:27 -05:00
Danny AvilaandGitHub b2334054da Update README.md 2023-03-11 23:28:56 -05:00
Danny AvilaandGitHub a705e907ff Merge pull request #45 from danny-avila/submit-state
fixes: allow simultaneous convos & minor improvements
2023-03-11 23:28:33 -05:00
Daniel Avila c8bb6b13bf update: readme.md 2 2023-03-11 23:24:53 -05:00
Daniel Avila 50a17bc1b8 update: readme.md 2023-03-11 23:22:51 -05:00
Daniel Avila 3038015a5b fix: removes newline from start of stream response if present 2023-03-11 23:11:55 -05:00
Daniel Avila 20da895ee7 update bypass proxy url for browser client 2023-03-11 23:08:04 -05:00
Daniel Avila 8762765dd0 fix: retain scroll view on delete convo 2023-03-11 22:59:32 -05:00
Daniel Avila f1aabfa543 fix: reset submission when convo clears 2023-03-11 22:35:39 -05:00
Daniel Avila 23de688bf3 fix: stops stream upon conversation 2023-03-11 21:42:08 -05:00
Daniel Avila 79f050bac7 feat: loading state for messages 2023-03-11 18:39:46 -05:00
Danny AvilaandGitHub afae13eec6 Merge pull request #43 from danny-avila/fix-custom-name
fix: format sender text change and mobile styling
2023-03-11 17:38:17 -05:00
Daniel Avila 4fa599e97a fix: format sender text change and mobile styling 2023-03-11 17:36:10 -05:00
Danny AvilaandGitHub 3b1d6aaa79 Merge pull request #42 from danny-avila/title-lang
feat: title the chat in user's language
2023-03-11 16:28:36 -05:00
Daniel Avila 339230cd74 feat: title the chat in user's language 2023-03-11 16:26:41 -05:00
Daniel Avila 95bad60f7d fix: detectCode bug 2023-03-11 14:46:21 -05:00
Danny AvilaandGitHub 950b8f3f1e Merge pull request #34 from wtlyu/feat-mobile
feat: basic support mobile mode.
2023-03-11 12:59:09 -05:00
Danny AvilaandGitHub 0e2e5b8393 Update README.md 2023-03-11 12:32:59 -05:00
Danny AvilaandGitHub d2cb9957fe Merge pull request #35 from danny-avila/rm-short-filter
fix: rm short filter
2023-03-11 12:10:56 -05:00
Daniel Avila 78b4004ead fix: rm short filter 2023-03-11 12:10:00 -05:00
Danny AvilaandGitHub 8624062488 Merge pull request #26 from wtlyu/master
support config host name and proxy address. and fix a docker bug
2023-03-11 12:05:16 -05:00
Wentao Lyu 070fee2ece feat: basic support in mobile mode. including:
navbar show and hide,
similar fade animation,
auto close when select new convo,
mobile title will change with convo,
new chat button.
2023-03-12 00:32:03 +08:00
Danny AvilaandGitHub b1569450fd Merge pull request #32 from danny-avila/fix-hjs
Fix hjs
2023-03-11 10:32:38 -05:00
Daniel Avila 09659be002 fix: unsupported lang handler in client 2023-03-11 10:28:46 -05:00
Wentao Lyu 5e4fa09dcb update readme, add proxy and host information 2023-03-11 16:14:24 +08:00
Wentao Lyu 16c9589058 fix: use quotes outside of key=value in docker-compose.yml
fix: set HOST=0.0.0.0 by default in api's dockerfile
2023-03-11 16:14:11 +08:00
Wentao Lyu 8604030404 Add more information in env.example.
Give current host address when HOST=0.0.0.0
2023-03-11 15:03:18 +08:00
Wentao Lyu 97668217b9 fix: add proxy to bingai too 2023-03-11 15:02:26 +08:00
Wentao Lyu c5c865a25f fix: frontend api request shouldn't hard code the domain and port. 2023-03-11 14:13:39 +08:00
Wentao Lyu 06c99154ac feat: support config host name and proxy address 2023-03-11 14:12:51 +08:00
Daniel Avila 9d71b58345 fix: handles unsupported hjs languages in client response 2023-03-10 22:21:15 -05:00
Danny AvilaandGitHub fb9f77ae5e Merge pull request #23 from danny-avila/tab-links
Add back Tab links
2023-03-10 13:18:54 -05:00
Danny Avila fc2b9bf7f2 feat: tab link component, edit wrapper 2023-03-10 13:12:24 -05:00
Danny Avila cdbe00ec6f feat: tab link component 2023-03-10 13:11:17 -05:00
Danny Avila 72ff47e204 Rolled back to v0.0.2 2023-03-10 12:55:45 -05:00
Danny AvilaandGitHub 57d3025717 Merge pull request #20 from danny-avila/override-links
feat: links open in new tab
2023-03-10 09:45:52 -05:00
Danny Avila 6c02558f1b feat: links open in new tab 2023-03-10 09:44:40 -05:00
Danny AvilaandGitHub 0c62863e52 Update README.md 2023-03-10 08:38:45 -05:00
Danny Avila d825721dad fix: update readme and compose file 2023-03-10 08:37:15 -05:00
Danny AvilaandGitHub 0b3b6f91fc Merge pull request #19 from wtlyu/master
support config host name and proxy address.
2023-03-10 08:21:22 -05:00
Wentao Lyu 3a9b532248 fix: frontend api request shouldn't hard code the domain and port. 2023-03-10 21:12:16 +08:00
Wentao Lyu 67156f4a7a feat: support config host name and proxy address 2023-03-10 21:12:16 +08:00
Daniel Avila 4e616cd2ed fix: error handling titles, remove openAI req 2023-03-10 07:43:43 -05:00
Danny AvilaandGitHub 12cf3405e4 Merge pull request #16 from danny-avila/updateAI
feat: add sydney (jailbroken bing) and more bing styling with citations
2023-03-09 20:34:28 -05:00
Daniel Avila 4383894ad3 finish update 2023-03-09 20:31:56 -05:00
Daniel Avila d0ecaabfd8 feat: cites text with links 2023-03-09 20:29:44 -05:00
Daniel Avila a286f027c8 model menu won't close on model deletion 2023-03-09 19:02:59 -05:00
Daniel Avila 0ba92c4c03 add new reducer to ensure conversations are renewed on model change 2023-03-09 18:57:37 -05:00
Daniel Avila 30936573ac feat: includes sources 2023-03-09 18:42:36 -05:00
Daniel Avila a451574760 feat: cites text with links 2023-03-09 18:07:36 -05:00
Danny Avila 5e57deab5f bing styling in progress 2023-03-09 16:09:53 -05:00
Daniel Avila eae82edb83 merge readme update 2023-03-08 22:33:25 -05:00
Daniel Avila 1e0cbbf1cc merge readme update 2023-03-08 22:32:51 -05:00
Daniel Avila 3986b4ec9e Merge branch 'master' of https://github.com/danny-avila/chatgpt-clone into updateAI 2023-03-08 22:31:40 -05:00
Daniel Avila 67054e7504 working sydney, need to test the other models 2023-03-08 22:30:29 -05:00
Daniel Avila 2c1ae68dc4 adding sydney in progress. only uses jailbreakConvoId and parentMsgId 2023-03-08 21:06:58 -05:00
Daniel Avila 69b3edc52c feat: sydney is functional 2023-03-08 19:47:23 -05:00
Danny AvilaandGitHub 732c75d145 Update README.md 2023-03-08 10:26:12 -05:00
Danny AvilaandGitHub 0f54251459 Merge pull request #12 from HyunggyuJang/fix/webpack-artifacts
webpack artifacts should exist for nginx image
2023-03-07 21:07:13 -05:00
Hyunggyu Jang 72ef76b8a0 webpack artifacts should exist for nginx image 2023-03-08 10:38:22 +09:00
Danny AvilaandGitHub 81abbdb335 Merge pull request #11 from HyunggyuJang/fix/docker-name
DockerFile is invalid name
2023-03-07 20:11:44 -05:00
Hyunggyu Jang 67ca79dd3f DockerFile is invalid name 2023-03-08 09:51:04 +09:00
Danny AvilaandGitHub 5af03bb3d8 Update README.md 2023-03-07 14:54:44 -05:00
Danny AvilaandGitHub e5d25f45e3 Merge pull request #7 from danny-avila/easy-config-setup
dockerize app, improve ui/ux, add setup instructions
2023-03-07 14:53:57 -05:00
Danny Avila ad967beaf8 update readme 2023-03-07 14:51:41 -05:00
Danny Avila a8eaf2452a update readme, setup files, and clear convo bug 2023-03-07 14:43:55 -05:00
Danny Avila 5ad32e211f update docker-compose 2023-03-07 14:23:56 -05:00
Danny Avila da61a2841f fix: customGpt persistence blindspot, also remove free gpt 2023-03-07 14:22:33 -05:00
Danny Avila da55ac3774 update readme 2023-03-07 13:58:57 -05:00
Danny Avila f2b092545f fix: convos correctly delete 2023-03-07 13:53:23 -05:00
Danny Avila 17c01aa252 fix: ignoreDir pointing correctly 2023-03-07 13:49:15 -05:00
Danny Avila 6be037323e chore: model menu functions behave as expected 2023-03-07 13:19:56 -05:00
Daniel Avila 191118b90b add buttons to custom model menu items 2023-03-06 21:43:49 -05:00
Daniel Avila 6d757ff91f improve custom model selection ux 2023-03-06 21:12:36 -05:00
Daniel Avila d93282640c modify starting files and readme 2023-03-06 18:17:43 -05:00
Daniel Avila b9e9203a75 Merge branch 'easy-config-setup' of https://github.com/danny-avila/chatgpt-clone into easy-config-setup 2023-03-06 16:59:03 -05:00
Danny Avila ee8eba9ad5 updating readme 2023-03-06 16:30:16 -05:00
Danny Avila 102327313e modify docker setup 2023-03-06 15:57:01 -05:00
Danny Avila 08f59819cf modify docker setup 2023-03-06 15:56:25 -05:00
Danny Avila d9930dd2bc edit docker setup 2023-03-06 14:38:34 -05:00
Danny Avila 453301db0e chore: edit readme and docker-compose 2023-03-06 14:30:58 -05:00
Danny Avila 52e529dcbe complete docker setup 2023-03-06 14:04:06 -05:00
Danny Avila ff7b016190 initial docker detup 2023-03-06 12:49:22 -05:00
Danny Avila f5e079742a chore: reorganize client files for docker 2023-03-06 10:47:06 -05:00
Danny Avila affbaaf1a5 chore: server setup complete 2023-03-06 10:28:37 -05:00
Danny Avila fca546af63 reorganize dirs for dockerize 2023-03-06 10:15:07 -05:00
Danny AvilaandGitHub 062cd1d838 Merge pull request #6 from danny-avila/easy-config-setup
chore: misc. fixes/improvements before containerizing
2023-03-06 09:27:14 -05:00
Danny Avila 6ae154cc42 optimize convo paginations 2023-03-06 08:58:52 -05:00
Danny Avila c617b15bcd disable prompts until complete 2023-03-06 08:50:36 -05:00
Danny Avila b750bd2970 update roadmap 2023-03-06 08:41:09 -05:00
Daniel Avila 8d60cbdc7a feat: dynamic custom model saving/updating in dialog 2023-03-05 17:02:00 -05:00
Daniel Avila 15986c078b update readme with 2nd use case img 2023-03-05 16:23:35 -05:00
Daniel Avila 15f3972b9f remove models caching 2023-03-05 15:30:40 -05:00
Danny AvilaandGitHub 3224dca3b8 Merge pull request #5 from danny-avila/convo-pagination
feat: convo pagination in increments of 12
2023-03-05 14:42:43 -05:00
Daniel Avila a98367d27f convo fetching in increments of 12 2023-03-05 14:41:50 -05:00
Danny AvilaandGitHub 80319c7fcc Merge pull request #4 from danny-avila/dark-mode-match
feat: match more official stylings
2023-03-05 13:32:59 -05:00
Daniel Avila dae7f8d822 match more official stylings 2023-03-05 13:31:12 -05:00
Danny AvilaandGitHub c8d8484d86 Merge pull request #2 from danny-avila/customChatGPT
feature: customize chat gpt
2023-03-04 21:47:48 -05:00
Daniel Avila f40f80f030 update readme 2023-03-04 21:46:58 -05:00
Daniel Avila fc899da944 convo change also changes model selection 2023-03-04 21:10:45 -05:00
Daniel Avila d64f914552 complete customGpt 2023-03-04 20:48:59 -05:00
Daniel Avila b4b0c123ba prompt prefix and label state changes when customGpt selected 2023-03-04 18:00:12 -05:00
Daniel Avila 9c3a78f96b customGpts persist through localStorage 2023-03-04 17:39:06 -05:00
Daniel Avila 62bb6ea8f8 refactor model menu items into component 2023-03-03 21:33:09 -05:00
Daniel Avila 0bec306e59 remove punctuation from titles 2023-03-03 21:31:37 -05:00
Daniel Avila 42c74f254c reduce stylesheet clutter 2023-03-03 21:31:05 -05:00
Danny Avila 9a56130750 save custom settings in progress 2023-03-03 16:33:02 -05:00
Danny Avila 214228542a complete customChatGpt model selection 2023-03-03 15:52:06 -05:00
Danny AvilaandGitHub 0f9cfd0395 Merge pull request #1 from danny-avila/copyCode
complete copyCode for code blocks
2023-03-03 09:46:49 -05:00
Danny Avila 92648554ad complete copyCode for code blocks 2023-03-03 09:45:20 -05:00
Danny Avila ac2229fbb6 add more detail to titleConvo prompt 2023-03-03 09:07:29 -05:00
Danny Avila ec7aaf01a4 add hoverbuttons 2023-03-03 08:51:33 -05:00
Danny Avila 50ff96cbc5 copy code in progress 2023-03-02 16:31:00 -05:00
Danny Avila 0d8755a03a update readme 2023-03-02 16:08:26 -05:00
Danny Avila d235bb7b1b complete language detection for code blocks 2023-03-02 16:07:36 -05:00
Danny Avila c4d0787b49 chatgpt now titles conversations 2023-03-02 13:52:41 -05:00
Daniel Avila 897c384ac9 fix code parsing bug 2023-03-01 21:59:13 -05:00
Daniel Avila cbcc1a1bb4 readme update 2023-03-01 21:41:24 -05:00
Daniel Avila 1f270d349a add official api, remove davinci, organize root dir 2023-03-01 21:39:57 -05:00
Danny AvilaandGitHub 90ac43c8c5 Create LICENSE.md 2023-02-28 17:21:29 -05:00
Daniel Avila 1bba86f2e2 minor changes 2023-02-26 14:10:44 -05:00
Daniel Avila 8e5148b390 fix embed wrapper not invoking immediately 2023-02-25 10:33:26 -05:00
Daniel Avila 0ee31f0443 fix codeWrapper issue, will produce funky text with ` commenting 2023-02-25 10:16:21 -05:00
Daniel Avila ed699d2c06 wrap code in progress, revert old message handling 2023-02-25 09:06:26 -05:00
Daniel Avila 0f6fcd5928 organize modules 2023-02-25 09:04:32 -05:00
Daniel Avila 78c9f3dc74 trying different markdown library for proper list styling 2023-02-24 23:16:19 -05:00
Danny Avila 34ebd97c9d indicate initial gpt response 2023-02-24 16:31:59 -05:00
Danny Avila 61d898c387 fix bing chat not persisting after 1st invocation 2023-02-24 16:20:23 -05:00
Daniel Avila fd01fd3334 adds markdown support, fix code wrapping 2023-02-24 11:24:09 -05:00
Daniel Avila c58a9bbe93 finish highlight.js styling (for chatgpt) 2023-02-23 23:56:55 -05:00
Danny Avila e95e22de15 code highlighting in progress 2023-02-23 16:32:08 -05:00
Daniel Avila 187f7b5b03 handles in-line code in model response text (not multi-line yet) 2023-02-22 22:42:22 -05:00
Daniel Avila 1a6cddb8bb persist models in localStorage, minor changes 2023-02-22 21:30:48 -05:00
Daniel Avila e2a3ecbf88 update readme 2023-02-21 21:41:30 -05:00
Daniel Avila a516b38e27 adds linting, fix linter errors, abort automatic scrolling 2023-02-21 21:31:36 -05:00
Daniel Avila 16932b37c0 fix bingai bugs and add chatgptbrowser client 2023-02-21 21:30:56 -05:00
Daniel Avila 168d5e8075 fix linter errors 2023-02-21 19:40:28 -05:00
Daniel Avila 7b7548c905 persist bing ai conversation 2023-02-20 21:52:08 -05:00
Daniel Avila 81e52fed73 properly records bing conversations 2023-02-20 21:16:40 -05:00
Daniel Avila b4e22936ba update schema for bing options 2023-02-19 21:06:21 -05:00
Danny Avila ac2e897709 adds og bing icon 2023-02-16 14:06:18 -05:00
Danny Avila 5c7000c273 adds bing ai functionality, still need to code out message/convo storing since it returns a lot of useful parameters 2023-02-15 12:29:56 -05:00
Daniel Avila e0a26f52cd update readme 2023-02-15 00:06:10 -05:00
Daniel Avila f18317f7b7 useDocumentTitle changes title on first title change, scrollBar renders when expected 2023-02-14 21:23:14 -05:00
Daniel Avila 7347eda219 update readme 2023-02-14 19:46:24 -05:00
Daniel Avila e1b615835a new demo gif 2023-02-14 19:38:28 -05:00
Daniel Avila eaa0242e10 new demo gif 2023-02-14 19:11:47 -05:00
Daniel Avila 481927c5a3 adds demo gif 2023-02-14 19:03:48 -05:00
Daniel Avila 0833d795bd readme update and footer link update 2023-02-14 18:40:00 -05:00
Daniel Avila 7d7a4dd905 proxy server is down 2023-02-14 18:34:46 -05:00
Daniel Avila e520973203 draft readme 2023-02-14 18:31:29 -05:00
Danny Avila 9c225a9bb3 drafting readme 2023-02-14 16:33:22 -05:00
Danny Avila 742073fb89 refactor convos fetching to nav components, results in faster loading 2023-02-14 16:15:45 -05:00
Daniel Avila e4d1ff2523 rearrange convo fetching for performance in progress 2023-02-13 23:14:35 -05:00
Daniel Avila c00a2c902b front and backend logic for model switching 2023-02-13 21:15:28 -05:00
Daniel Avila a5afd5c48f animates scroll to bottom and debounces bottom 2023-02-13 20:13:59 -05:00
Daniel Avila 779f142058 fix static flag bug 2023-02-13 18:02:29 -05:00
Danny Avila 92b2109dc3 adds scrolltobottom button, still handling initial state when scrollbar is present 2023-02-13 16:31:18 -05:00
Danny Avila 583fd56ca5 adds shadcn-ui components, and model button to switch chat models 2023-02-13 15:58:35 -05:00
Danny Avila acaef39d12 refactors server route for brevity, state working, styling matching to chatgpt 2023-02-13 13:32:54 -05:00
Danny Avila dcf4f2a524 debugging convo state, useSWR now within convos component 2023-02-13 10:20:21 -05:00
Daniel Avila 0e5c7c7721 fixing convo state in progress 2023-02-12 22:57:59 -05:00
Daniel Avila 500b6c9fa8 adds functionality for waylaidwanderer chatgpt client 2023-02-12 22:24:36 -05:00
Daniel Avila 5b8c4452cc remove unused packages and minor changes 2023-02-12 16:48:15 -05:00
Daniel Avila ed44daf8b3 refactor express to use routes 2023-02-12 16:38:33 -05:00
Daniel Avila cd71bb0727 remove nodemon from server script 2023-02-11 17:35:07 -05:00
Daniel Avila 48c591a8b9 scrollbar styling 2023-02-11 13:48:48 -05:00
Daniel Avila 8eeac36b35 update press release 2023-02-11 13:05:48 -05:00
Daniel Avila 4cecbcf642 update press release 2023-02-11 13:02:30 -05:00
Daniel Avila 8432cbd293 update press release 2023-02-11 13:01:58 -05:00
Daniel Avila 256b60435f update press release, minor change in textchat component, remove unused package 2023-02-11 13:01:03 -05:00
Daniel Avila d9d5f4a509 delegate footer to own component 2023-02-11 12:54:02 -05:00
Daniel Avila e8c514fff9 delete updates state title 2023-02-11 12:49:07 -05:00
Daniel Avila ef4ec39a76 update press release notes 2023-02-11 11:54:17 -05:00
Daniel Avila 4304597cb4 small detail for renaming convos 2023-02-11 11:54:03 -05:00
Daniel Avila 7dd4e78bbf tests scrolling to top, show/hide templates 2023-02-11 11:37:20 -05:00
Daniel Avila 5af5a97d8f complete renaming functions, abstracts more svg, sets title to current convo title, adds a try again feature to errors 2023-02-11 10:22:15 -05:00
Daniel Avila 592b7629aa abstracts svgs, fiddling with prompt templates idea 2023-02-10 21:00:02 -05:00
Danny Avila cab4b70b7e took a screenshot of use case 2023-02-09 16:21:36 -05:00
Daniel Avila c36f752551 fix scrolling bug 2023-02-08 22:58:24 -05:00
Danny Avila 217bdb4865 client error handling, regen response without saving messages 2023-02-08 15:26:42 -05:00
Danny Avila 87685c3791 conversation conditional rendering 2023-02-08 11:05:54 -05:00
Danny Avila 0c6dc8665b dark mode alternates to light mode on dark theme, error state in progress 2023-02-08 10:49:38 -05:00
Danny Avila 5cd50e7826 handle textarea text state with redux and setText on example click 2023-02-08 09:59:01 -05:00
Danny Avila 581ee0e2ca adds favicon to head 2023-02-08 09:46:26 -05:00
Danny Avila d07047722e adds favicon to head 2023-02-08 09:44:10 -05:00
Danny Avila b5042a738e adds dark mode and logs errors in message response 2023-02-08 09:15:47 -05:00
Danny Avila 58498ed951 clears conversations and no new line on enter 2023-02-08 08:27:23 -05:00
Daniel Avila 51d93da3f8 adds server and client error handling, clear convos in progress 2023-02-08 00:02:29 -05:00
Daniel Avila 511ac948b4 clear all convos in progress, async code refactored in debugging warning 2023-02-07 19:07:48 -05:00
Daniel Avila 053368646d useeffect triggers react warning 'expected static flag was missing' but workarounds don't look as nice 2023-02-07 19:07:12 -05:00
Danny Avila 6842ac880c optimistic ui for message sending and submit state 2023-02-07 16:22:35 -05:00
Danny Avila 9d41ed4615 reorganize components and add root import plugin 2023-02-07 10:26:19 -05:00
Danny Avila faf8800e67 remove sourcemap error 2023-02-07 09:59:45 -05:00
Danny Avila 36ac055ae5 Complete rewire state to redux 2023-02-07 09:41:54 -05:00
Daniel Avila 7978ddd871 adding redux in progress 2023-02-07 00:05:00 -05:00
Daniel Avila 85efaa4173 adding redux to reduce prop drilling and allow for advanced conversation state 2023-02-06 21:18:28 -05:00
Daniel Avila 6e3f63ee46 adding clear methods for convos 2023-02-06 21:17:46 -05:00
Daniel Avila c7c50dbbab convo successfully changes on convo click 2023-02-06 18:25:11 -05:00
Danny Avila d35ed31b20 convo selection handling in progress 2023-02-06 16:28:50 -05:00
Danny Avila c794287ced load convos on message send & other conditional rendering 2023-02-06 16:00:59 -05:00
Danny Avila 2869638cc0 get convos endpoint 2023-02-06 15:17:54 -05:00
Danny Avila c821d1eee5 made model method one line shorter 2023-02-06 14:58:43 -05:00
Danny Avila 4e6b6be15c titles conversations based on first two messages 2023-02-06 14:57:47 -05:00
Danny Avila 511c8c9599 saves all conversations with messages 2023-02-06 14:05:02 -05:00
Danny Avila 232a823b9b store all messages to mongoDB 2023-02-06 13:28:21 -05:00
Danny Avila f2003da779 layout mimics actual chatgpt ui 2023-02-06 13:27:28 -05:00
Daniel Avila 254f9d7e26 adds db connectivity and saving, need to process user messages still 2023-02-05 23:05:07 -05:00
Daniel Avila f889f23792 chatgpt is taking shape, convo persists, layout mimics original 2023-02-05 19:41:24 -05:00
Daniel Avila 3a199757ae sse events on text submit 2023-02-05 15:29:35 -05:00
Daniel Avila 27f7276fc8 working SSE stream 2023-02-05 15:26:59 -05:00
Daniel Avila 81866f197a webpack config for polyfills 2023-02-04 20:49:58 -05:00
Daniel Avila 502bdc5dd5 building potential Server side events 2023-02-04 20:49:42 -05:00
Daniel Avila 641dcfad73 chat methods defined, also test file for library testing 2023-02-04 20:49:01 -05:00
Daniel Avila 7c4e7ab07a abstracts textarea 2023-02-04 20:48:33 -05:00
Daniel Avila 7b9f3ee9c2 update env variables and build 2023-02-04 20:09:58 -05:00
Daniel Avila 39781f5318 testing chatgpt logic 2023-02-04 20:09:02 -05:00
Daniel Avila 521a8eb704 templating model 2023-02-04 20:08:46 -05:00
Daniel Avila 92860a1bb4 adds express server 2023-02-04 20:08:28 -05:00
Daniel Avila 037a12a282 logs textarea input 2023-02-04 20:07:37 -05:00
Daniel Avila 5dc2d5efba updates to react 18 method createRoot 2023-02-04 20:07:08 -05:00
Daniel Avila 21a6a0587c adds basic layout 2023-02-04 19:19:53 -05:00
Daniel Avila 0fb3a1cab3 adds tailwindcss 2023-02-04 18:17:47 -05:00
Daniel Avila 74232d7671 initial commit, project is ready to start 2023-02-04 17:17:17 -05:00
hackreactor-bot 466dd01327 (init) 2022-10-20 11:45:11 +00:00
2462 changed files with 38926 additions and 916124 deletions
-2661
View File
File diff suppressed because it is too large Load Diff
-13
View File
@@ -1,13 +0,0 @@
# used by CI/CD testing
openai==1.67.0
python-dotenv
tiktoken
importlib_metadata
cohere
redis
anthropic
orjson==3.9.15
pydantic==2.7.1
google-cloud-aiplatform==1.43.0
fastapi-sso==0.10.0
uvloop==0.21.0
-52
View File
@@ -1,52 +0,0 @@
{
"name": "Python 3.11",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/python:3.11-bookworm",
// https://github.com/devcontainers/images/tree/main/src/python
// https://mcr.microsoft.com/en-us/product/devcontainers/python/tags
// "build": {
// "dockerfile": "Dockerfile",
// "context": ".."
// },
// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},
// Configure tool-specific properties.
"customizations": {
// Configure properties specific to VS Code.
"vscode": {
"settings": {},
"extensions": [
"ms-python.python",
"ms-python.vscode-pylance",
"GitHub.copilot",
"GitHub.copilot-chat",
"ms-python.autopep8"
]
}
},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [4000],
"containerEnv": {
"LITELLM_LOG": "DEBUG"
},
// Use 'portsAttributes' to set default properties for specific forwarded ports.
// More info: https://containers.dev/implementors/json_reference/#port-attributes
"portsAttributes": {
"4000": {
"label": "LiteLLM Server",
"onAutoForward": "notify"
}
},
// More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "litellm",
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "pipx install poetry && poetry install -E extra_proxy -E proxy"
}
-12
View File
@@ -1,12 +0,0 @@
docs
cookbook
.circleci
.github
tests
.git
.github
.circleci
.devcontainer
*.tgz
log.txt
docker/Dockerfile.*
-27
View File
@@ -1,27 +0,0 @@
# OpenAI
OPENAI_API_KEY = ""
OPENAI_API_BASE = ""
# Cohere
COHERE_API_KEY = ""
# OpenRouter
OR_SITE_URL = ""
OR_APP_NAME = "LiteLLM Example app"
OR_API_KEY = ""
# Azure API base URL
AZURE_API_BASE = ""
# Azure API version
AZURE_API_VERSION = ""
# Azure API key
AZURE_API_KEY = ""
# Replicate
REPLICATE_API_KEY = ""
REPLICATE_API_TOKEN = ""
# Anthropic
ANTHROPIC_API_KEY = ""
# Infisical
INFISICAL_TOKEN = ""
# Development Configs
LITELLM_MASTER_KEY = "sk-1234"
DATABASE_URL = "postgresql://llmproxy:dbpassword9090@db:5432/litellm"
STORE_MODEL_IN_DB = "True"
-46
View File
@@ -1,46 +0,0 @@
[flake8]
ignore =
# The following ignores can be removed when formatting using black
W191,W291,W292,W293,W391,W504
E101,E111,E114,E116,E117,E121,E122,E123,E124,E125,E126,E127,E128,E129,E131,
E201,E202,E221,E222,E225,E226,E231,E241,E251,E252,E261,E265,E271,E272,E275,
E301,E302,E303,E305,E306,
# line break before binary operator
W503,
# inline comment should start with '# '
E262,
# too many leading '#' for block comment
E266,
# multiple imports on one line
E401,
# module level import not at top of file
E402,
# Line too long (82 > 79 characters)
E501,
# comparison to None should be 'if cond is None:'
E711,
# comparison to True should be 'if cond is True:' or 'if cond:'
E712,
# do not compare types, for exact checks use `is` / `is not`, for instance checks use `isinstance()`
E721,
# do not use bare 'except'
E722,
# x is imported but unused
F401,
# 'from . import *' used; unable to detect undefined names
F403,
# x may be undefined, or defined from star imports:
F405,
# f-string is missing placeholders
F541,
# dictionary key '' repeated with different values
F601,
# redefinition of unused x from line 123
F811,
# undefined name x
F821,
# local variable x is assigned to but never used
F841,
# https://black.readthedocs.io/en/stable/guides/using_black_with_other_tools.html#flake8
extend-ignore = E203
-10
View File
@@ -1,10 +0,0 @@
# Add the commit hash of any commit you want to ignore in `git blame` here.
# One commit hash per line.
#
# The GitHub Blame UI will use this file automatically!
#
# Run this command to always ignore formatting commits in `git blame`
# git config blame.ignoreRevsFile .git-blame-ignore-revs
# Update pydantic code to fix warnings (GH-3600)
876840e9957bc7e9f7d6a2b58c4d7c53dad16481
-1
View File
@@ -1 +0,0 @@
*.ipynb linguist-vendored
-13
View File
@@ -1,13 +0,0 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
custom: https://buy.stripe.com/9AQ03Kd3P91o0Q8bIS
-49
View File
@@ -1,49 +0,0 @@
name: Bug Report
description: File a bug report
title: "[Bug]: "
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
- type: textarea
id: what-happened
attributes:
label: What happened?
description: Also tell us, what did you expect to happen?
placeholder: Tell us what you see!
value: "A bug happened!"
validations:
required: true
- type: textarea
id: logs
attributes:
label: Relevant log output
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
render: shell
- type: dropdown
id: ml-ops-team
attributes:
label: Are you a ML Ops Team?
description: This helps us prioritize your requests correctly
options:
- "No"
- "Yes"
validations:
required: true
- type: input
id: version
attributes:
label: What LiteLLM version are you on ?
placeholder: v1.53.1
validations:
required: true
- type: input
id: contact
attributes:
label: Twitter / LinkedIn details
description: We announce new features on Twitter + LinkedIn. If this issue leads to an announcement, and you'd like a mention, we'll gladly shout you out!
placeholder: ex. @krrish_dh / https://www.linkedin.com/in/krish-d/
validations:
required: false
-8
View File
@@ -1,8 +0,0 @@
blank_issues_enabled: true
contact_links:
- name: Schedule Demo
url: https://calendly.com/d/4mp-gd3-k5k/litellm-1-1-onboarding-chat
about: Speak directly with Krrish and Ishaan, the founders, to discuss issues, share feedback, or explore improvements for LiteLLM
- name: Discord
url: https://discord.com/invite/wuPM9dRgDw
about: Join 250+ LiteLLM community members!
@@ -1,42 +0,0 @@
name: 🚀 Feature Request
description: Submit a proposal/request for a new LiteLLM feature.
title: "[Feature]: "
labels: ["enhancement"]
body:
- type: markdown
attributes:
value: |
Thanks for making LiteLLM better!
- type: textarea
id: the-feature
attributes:
label: The Feature
description: A clear and concise description of the feature proposal
placeholder: Tell us what you want!
validations:
required: true
- type: textarea
id: motivation
attributes:
label: Motivation, pitch
description: Please outline the motivation for the proposal. Is your feature request related to a specific problem? e.g., "I'm working on X and would like Y to be possible". If this is related to another GitHub issue, please link here too.
validations:
required: true
- type: dropdown
id: ml-ops-team
attributes:
label: Are you a ML Ops Team?
description: This helps us prioritize your requests correctly
options:
- "No"
- "Yes"
validations:
required: true
- type: input
id: contact
attributes:
label: Twitter / LinkedIn details
description: We announce new features on Twitter + LinkedIn. When this is announced, and you'd like a mention, we'll gladly shout you out!
placeholder: ex. @krrish_dh / https://www.linkedin.com/in/krish-d/
validations:
required: false
@@ -1,77 +0,0 @@
name: Helm OCI Chart Releaser
description: Push Helm charts to OCI-based (Docker) registries
author: sergeyshaykhullin
branding:
color: yellow
icon: upload-cloud
inputs:
name:
required: true
description: Chart name
repository:
required: true
description: Chart repository name
tag:
required: true
description: Chart version
app_version:
required: true
description: App version
path:
required: false
description: Chart path (Default 'charts/{name}')
registry:
required: true
description: OCI registry
registry_username:
required: true
description: OCI registry username
registry_password:
required: true
description: OCI registry password
update_dependencies:
required: false
default: 'false'
description: Update chart dependencies before packaging (Default 'false')
outputs:
image:
value: ${{ steps.output.outputs.image }}
description: Chart image (Default '{registry}/{repository}/{image}:{tag}')
runs:
using: composite
steps:
- name: Helm | Login
shell: bash
run: echo ${{ inputs.registry_password }} | helm registry login -u ${{ inputs.registry_username }} --password-stdin ${{ inputs.registry }}
env:
HELM_EXPERIMENTAL_OCI: '1'
- name: Helm | Dependency
if: inputs.update_dependencies == 'true'
shell: bash
run: helm dependency update ${{ inputs.path == null && format('{0}/{1}', 'charts', inputs.name) || inputs.path }}
env:
HELM_EXPERIMENTAL_OCI: '1'
- name: Helm | Package
shell: bash
run: helm package ${{ inputs.path == null && format('{0}/{1}', 'charts', inputs.name) || inputs.path }} --version ${{ inputs.tag }} --app-version ${{ inputs.app_version }}
env:
HELM_EXPERIMENTAL_OCI: '1'
- name: Helm | Push
shell: bash
run: helm push ${{ inputs.name }}-${{ inputs.tag }}.tgz oci://${{ inputs.registry }}/${{ inputs.repository }}
env:
HELM_EXPERIMENTAL_OCI: '1'
- name: Helm | Logout
shell: bash
run: helm registry logout ${{ inputs.registry }}
env:
HELM_EXPERIMENTAL_OCI: '1'
- name: Helm | Output
id: output
shell: bash
run: echo "image=${{ inputs.registry }}/${{ inputs.repository }}/${{ inputs.name }}:${{ inputs.tag }}" >> $GITHUB_OUTPUT
-10
View File
@@ -1,10 +0,0 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
groups:
github-actions:
patterns:
- "*"
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

-33
View File
@@ -1,33 +0,0 @@
## Title
<!-- e.g. "Implement user authentication feature" -->
## Relevant issues
<!-- e.g. "Fixes #000" -->
## Pre-Submission checklist
**Please complete all items before asking a LiteLLM maintainer to review your PR**
- [ ] I have Added testing in the [`tests/litellm/`](https://github.com/BerriAI/litellm/tree/main/tests/litellm) directory, **Adding at least 1 test is a hard requirement** - [see details](https://docs.litellm.ai/docs/extras/contributing_code)
- [ ] I have added a screenshot of my new test passing locally
- [ ] My PR passes all unit tests on (`make test-unit`)[https://docs.litellm.ai/docs/extras/contributing_code]
- [ ] My PR's scope is as isolated as possible, it only solves 1 specific problem
## Type
<!-- Select the type of Pull Request -->
<!-- Keep only the necessary ones -->
🆕 New Feature
🐛 Bug Fix
🧹 Refactoring
📖 Documentation
🚄 Infrastructure
✅ Test
## Changes
-94
View File
@@ -1,94 +0,0 @@
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
llmlite-service
SAM Template for llmlite-service
# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
Function:
Timeout: 600
MemorySize: 128
Environment:
Variables:
WORKER_CONFIG: !Ref WorkerConfigParameter
Parameters:
AliasParameter:
Type: String
Default: live
WorkerConfigParameter:
Type: String
Description: Sample environment variable
Default: '{"model": null, "alias": null, "api_base": null, "api_version": "2023-07-01-preview", "debug": false, "temperature": null, "max_tokens": null, "request_timeout": 600, "max_budget": null, "telemetry": true, "drop_params": false, "add_function_to_prompt": false, "headers": null, "save": false, "config": null, "use_queue": false}'
Resources:
MyUrlFunctionPermissions:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !Ref URL
Action: lambda:InvokeFunctionUrl
Principal: "*"
FunctionUrlAuthType: NONE
Function:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub "${AWS::StackName}-function"
CodeUri: "./litellm"
Handler: proxy/lambda.handler
Runtime: python3.11
AutoPublishAlias: !Ref AliasParameter
Architectures:
- x86_64
DeploymentPreference:
Type: AllAtOnce
Alarms:
- !Ref NewVersionErrorMetricGreaterThanZeroAlarm
NewVersionErrorMetricGreaterThanZeroAlarm:
Type: "AWS::CloudWatch::Alarm"
Properties:
AlarmDescription: Lambda Function Error > 0
ComparisonOperator: GreaterThanThreshold
Dimensions:
- Name: Resource
Value: !Sub "${Function}:live"
- Name: FunctionName
Value: !Ref Function
- Name: ExecutedVersion
Value: !GetAtt Function.Version.Version
EvaluationPeriods: 1
Unit: Count
MetricName: Errors
Namespace: AWS/Lambda
Period: 60
Statistic: Sum
Threshold: 0
URL:
Type: AWS::Lambda::Url
DependsOn: FunctionAliaslive
Properties:
AuthType: NONE
Qualifier: live
TargetFunctionArn: !GetAtt Function.Arn
Outputs:
FunctionARN:
Description: "Lambda Function ARN"
Value: !GetAtt Function.Arn
FunctionUrl:
Description: "Lambda Function URL Endpoint"
Value:
Fn::GetAtt: URL.FunctionUrl
FunctionVersion:
Description: "Lambda Function Version"
Value: !GetAtt Function.Version.Version
FunctionNewAlarmARN:
Description: "Lambda Function New Alarm ARN"
Value: !GetAtt NewVersionErrorMetricGreaterThanZeroAlarm.Arn
@@ -1,28 +0,0 @@
name: Updates model_prices_and_context_window.json and Create Pull Request
on:
schedule:
- cron: "0 0 * * 0" # Run every Sundays at midnight
#- cron: "0 0 * * *" # Run daily at midnight
jobs:
auto_update_price_and_context_window:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Dependencies
run: |
pip install aiohttp
- name: Update JSON Data
run: |
python ".github/workflows/auto_update_price_and_context_window_file.py"
- name: Create Pull Request
run: |
git add model_prices_and_context_window.json
git commit -m "Update model_prices_and_context_window.json file: $(date +'%Y-%m-%d')"
gh pr create --title "Update model_prices_and_context_window.json file" \
--body "Automated update for model_prices_and_context_window.json" \
--head auto-update-price-and-context-window-$(date +'%Y-%m-%d') \
--base main
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
@@ -1,121 +0,0 @@
import asyncio
import aiohttp
import json
# Asynchronously fetch data from a given URL
async def fetch_data(url):
try:
# Create an asynchronous session
async with aiohttp.ClientSession() as session:
# Send a GET request to the URL
async with session.get(url) as resp:
# Raise an error if the response status is not OK
resp.raise_for_status()
# Parse the response JSON
resp_json = await resp.json()
print("Fetch the data from URL.")
# Return the 'data' field from the JSON response
return resp_json['data']
except Exception as e:
# Print an error message if fetching data fails
print("Error fetching data from URL:", e)
return None
# Synchronize local data with remote data
def sync_local_data_with_remote(local_data, remote_data):
# Update existing keys in local_data with values from remote_data
for key in (set(local_data) & set(remote_data)):
local_data[key].update(remote_data[key])
# Add new keys from remote_data to local_data
for key in (set(remote_data) - set(local_data)):
local_data[key] = remote_data[key]
# Write data to the json file
def write_to_file(file_path, data):
try:
# Open the file in write mode
with open(file_path, "w") as file:
# Dump the data as JSON into the file
json.dump(data, file, indent=4)
print("Values updated successfully.")
except Exception as e:
# Print an error message if writing to file fails
print("Error updating JSON file:", e)
# Update the existing models and add the missing models
def transform_remote_data(data):
transformed = {}
for row in data:
# Add the fields 'max_tokens' and 'input_cost_per_token'
obj = {
"max_tokens": row["context_length"],
"input_cost_per_token": float(row["pricing"]["prompt"]),
}
# Add 'max_output_tokens' as a field if it is not None
if "top_provider" in row and "max_completion_tokens" in row["top_provider"] and row["top_provider"]["max_completion_tokens"] is not None:
obj['max_output_tokens'] = int(row["top_provider"]["max_completion_tokens"])
# Add the field 'output_cost_per_token'
obj.update({
"output_cost_per_token": float(row["pricing"]["completion"]),
})
# Add field 'input_cost_per_image' if it exists and is non-zero
if "pricing" in row and "image" in row["pricing"] and float(row["pricing"]["image"]) != 0.0:
obj['input_cost_per_image'] = float(row["pricing"]["image"])
# Add the fields 'litellm_provider' and 'mode'
obj.update({
"litellm_provider": "openrouter",
"mode": "chat"
})
# Add the 'supports_vision' field if the modality is 'multimodal'
if row.get('architecture', {}).get('modality') == 'multimodal':
obj['supports_vision'] = True
# Use a composite key to store the transformed object
transformed[f'openrouter/{row["id"]}'] = obj
return transformed
# Load local data from a specified file
def load_local_data(file_path):
try:
# Open the file in read mode
with open(file_path, "r") as file:
# Load and return the JSON data
return json.load(file)
except FileNotFoundError:
# Print an error message if the file is not found
print("File not found:", file_path)
return None
except json.JSONDecodeError as e:
# Print an error message if JSON decoding fails
print("Error decoding JSON:", e)
return None
def main():
local_file_path = "model_prices_and_context_window.json" # Path to the local data file
url = "https://openrouter.ai/api/v1/models" # URL to fetch remote data
# Load local data from file
local_data = load_local_data(local_file_path)
# Fetch remote data asynchronously
remote_data = asyncio.run(fetch_data(url))
# Transform the fetched remote data
remote_data = transform_remote_data(remote_data)
# If both local and remote data are available, synchronize and save
if local_data and remote_data:
sync_local_data_with_remote(local_data, remote_data)
write_to_file(local_file_path, local_data)
else:
print("Failed to fetch model data from either local file or URL.")
# Entry point of the script
if __name__ == "__main__":
main()
-389
View File
@@ -1,389 +0,0 @@
# this workflow is triggered by an API call when there is a new PyPI release of LiteLLM
name: Build, Publish LiteLLM Docker Image. New Release
on:
workflow_dispatch:
inputs:
tag:
description: "The tag version you want to build"
release_type:
description: "The release type you want to build. Can be 'latest', 'stable', 'dev'"
type: string
default: "latest"
commit_hash:
description: "Commit hash"
required: true
# Defines two custom environment variables for the workflow. Used for the Container registry domain, and a name for the Docker image that this workflow builds.
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
CHART_NAME: litellm-helm
# There is a single job in this workflow. It's configured to run on the latest available version of Ubuntu.
jobs:
# print commit hash, tag, and release type
print:
runs-on: ubuntu-latest
steps:
- run: |
echo "Commit hash: ${{ github.event.inputs.commit_hash }}"
echo "Tag: ${{ github.event.inputs.tag }}"
echo "Release type: ${{ github.event.inputs.release_type }}"
docker-hub-deploy:
if: github.repository == 'BerriAI/litellm'
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.commit_hash }}
-
name: Set up QEMU
uses: docker/setup-qemu-action@v3
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
-
name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: litellm/litellm:${{ github.event.inputs.tag || 'latest' }}
-
name: Build and push litellm-database image
uses: docker/build-push-action@v5
with:
context: .
push: true
file: ./docker/Dockerfile.database
tags: litellm/litellm-database:${{ github.event.inputs.tag || 'latest' }}
-
name: Build and push litellm-spend-logs image
uses: docker/build-push-action@v5
with:
context: .
push: true
file: ./litellm-js/spend-logs/Dockerfile
tags: litellm/litellm-spend_logs:${{ github.event.inputs.tag || 'latest' }}
build-and-push-image:
runs-on: ubuntu-latest
# Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job.
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.commit_hash }}
# Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here.
- name: Log in to the Container registry
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels.
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
# Configure multi platform Docker builds
- name: Set up QEMU
uses: docker/setup-qemu-action@e0e4588fad221d38ee467c0bffd91115366dc0c5
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@edfb0fe6204400c56fbfd3feba3fe9ad1adfa345
# This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages.
# It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository.
# It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step.
- name: Build and push Docker image
uses: docker/build-push-action@4976231911ebf5f32aad765192d35f942aa48cb8
with:
context: .
push: true
tags: |
${{ steps.meta.outputs.tags }}-${{ github.event.inputs.tag || 'latest' }},
${{ steps.meta.outputs.tags }}-${{ github.event.inputs.release_type }}
${{ github.event.inputs.release_type == 'stable' && format('{0}/berriai/litellm:main-{1}', env.REGISTRY, github.event.inputs.tag) || '' }},
${{ github.event.inputs.release_type == 'stable' && format('{0}/berriai/litellm:main-stable', env.REGISTRY) || '' }}
labels: ${{ steps.meta.outputs.labels }}
platforms: local,linux/amd64,linux/arm64,linux/arm64/v8
build-and-push-image-database:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.commit_hash }}
- name: Log in to the Container registry
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for database Dockerfile
id: meta-database
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-database
# Configure multi platform Docker builds
- name: Set up QEMU
uses: docker/setup-qemu-action@e0e4588fad221d38ee467c0bffd91115366dc0c5
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@edfb0fe6204400c56fbfd3feba3fe9ad1adfa345
- name: Build and push Database Docker image
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
with:
context: .
file: ./docker/Dockerfile.database
push: true
tags: |
${{ steps.meta-database.outputs.tags }}-${{ github.event.inputs.tag || 'latest' }},
${{ steps.meta-database.outputs.tags }}-${{ github.event.inputs.release_type }}
${{ github.event.inputs.release_type == 'stable' && format('{0}/berriai/litellm-database:main-{1}', env.REGISTRY, github.event.inputs.tag) || '' }},
${{ github.event.inputs.release_type == 'stable' && format('{0}/berriai/litellm-database:main-stable', env.REGISTRY) || '' }}
labels: ${{ steps.meta-database.outputs.labels }}
platforms: local,linux/amd64,linux/arm64,linux/arm64/v8
build-and-push-image-non_root:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.commit_hash }}
- name: Log in to the Container registry
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for non_root Dockerfile
id: meta-non_root
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-non_root
# Configure multi platform Docker builds
- name: Set up QEMU
uses: docker/setup-qemu-action@e0e4588fad221d38ee467c0bffd91115366dc0c5
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@edfb0fe6204400c56fbfd3feba3fe9ad1adfa345
- name: Build and push non_root Docker image
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
with:
context: .
file: ./docker/Dockerfile.non_root
push: true
tags: |
${{ steps.meta-non_root.outputs.tags }}-${{ github.event.inputs.tag || 'latest' }},
${{ steps.meta-non_root.outputs.tags }}-${{ github.event.inputs.release_type }}
${{ github.event.inputs.release_type == 'stable' && format('{0}/berriai/litellm-non_root:main-{1}', env.REGISTRY, github.event.inputs.tag) || '' }},
${{ github.event.inputs.release_type == 'stable' && format('{0}/berriai/litellm-non_root:main-stable', env.REGISTRY) || '' }}
labels: ${{ steps.meta-non_root.outputs.labels }}
platforms: local,linux/amd64,linux/arm64,linux/arm64/v8
build-and-push-image-spend-logs:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.commit_hash }}
- name: Log in to the Container registry
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for spend-logs Dockerfile
id: meta-spend-logs
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-spend_logs
# Configure multi platform Docker builds
- name: Set up QEMU
uses: docker/setup-qemu-action@e0e4588fad221d38ee467c0bffd91115366dc0c5
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@edfb0fe6204400c56fbfd3feba3fe9ad1adfa345
- name: Build and push Database Docker image
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
with:
context: .
file: ./litellm-js/spend-logs/Dockerfile
push: true
tags: |
${{ steps.meta-spend-logs.outputs.tags }}-${{ github.event.inputs.tag || 'latest' }},
${{ steps.meta-spend-logs.outputs.tags }}-${{ github.event.inputs.release_type }}
${{ github.event.inputs.release_type == 'stable' && format('{0}/berriai/litellm-spend_logs:main-{1}', env.REGISTRY, github.event.inputs.tag) || '' }},
${{ github.event.inputs.release_type == 'stable' && format('{0}/berriai/litellm-spend_logs:main-stable', env.REGISTRY) || '' }}
platforms: local,linux/amd64,linux/arm64,linux/arm64/v8
build-and-push-helm-chart:
if: github.event.inputs.release_type != 'dev'
needs: [docker-hub-deploy, build-and-push-image, build-and-push-image-database]
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Log in to the Container registry
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: lowercase github.repository_owner
run: |
echo "REPO_OWNER=`echo ${{github.repository_owner}} | tr '[:upper:]' '[:lower:]'`" >>${GITHUB_ENV}
- name: Get LiteLLM Latest Tag
id: current_app_tag
shell: bash
run: |
LATEST_TAG=$(git describe --tags --exclude "*dev*" --abbrev=0)
if [ -z "${LATEST_TAG}" ]; then
echo "latest_tag=latest" | tee -a $GITHUB_OUTPUT
else
echo "latest_tag=${LATEST_TAG}" | tee -a $GITHUB_OUTPUT
fi
- name: Get last published chart version
id: current_version
shell: bash
run: |
CHART_LIST=$(helm show chart oci://${{ env.REGISTRY }}/${{ env.REPO_OWNER }}/${{ env.CHART_NAME }} 2>/dev/null || true)
if [ -z "${CHART_LIST}" ]; then
echo "current-version=0.1.0" | tee -a $GITHUB_OUTPUT
else
printf '%s' "${CHART_LIST}" | grep '^version:' | awk 'BEGIN{FS=":"}{print "current-version="$2}' | tr -d " " | tee -a $GITHUB_OUTPUT
fi
env:
HELM_EXPERIMENTAL_OCI: '1'
# Automatically update the helm chart version one "patch" level
- name: Bump release version
id: bump_version
uses: christian-draeger/increment-semantic-version@1.1.0
with:
current-version: ${{ steps.current_version.outputs.current-version || '0.1.0' }}
version-fragment: 'bug'
- uses: ./.github/actions/helm-oci-chart-releaser
with:
name: ${{ env.CHART_NAME }}
repository: ${{ env.REPO_OWNER }}
tag: ${{ github.event.inputs.chartVersion || steps.bump_version.outputs.next-version || '0.1.0' }}
app_version: ${{ steps.current_app_tag.outputs.latest_tag }}
path: deploy/charts/${{ env.CHART_NAME }}
registry: ${{ env.REGISTRY }}
registry_username: ${{ github.actor }}
registry_password: ${{ secrets.GITHUB_TOKEN }}
update_dependencies: true
release:
name: "New LiteLLM Release"
needs: [docker-hub-deploy, build-and-push-image, build-and-push-image-database]
runs-on: "ubuntu-latest"
steps:
- name: Display version
run: echo "Current version is ${{ github.event.inputs.tag }}"
- name: "Set Release Tag"
run: echo "RELEASE_TAG=${{ github.event.inputs.tag }}" >> $GITHUB_ENV
- name: Display release tag
run: echo "RELEASE_TAG is $RELEASE_TAG"
- name: "Create release"
uses: "actions/github-script@v6"
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"
script: |
const commitHash = "${{ github.event.inputs.commit_hash}}";
console.log("Commit Hash:", commitHash); // Add this line for debugging
try {
const response = await github.rest.repos.createRelease({
draft: false,
generate_release_notes: true,
target_commitish: commitHash,
name: process.env.RELEASE_TAG,
owner: context.repo.owner,
prerelease: false,
repo: context.repo.repo,
tag_name: process.env.RELEASE_TAG,
});
core.exportVariable('RELEASE_ID', response.data.id);
core.exportVariable('RELEASE_UPLOAD_URL', response.data.upload_url);
} catch (error) {
core.setFailed(error.message);
}
- name: Fetch Release Notes
id: release-notes
uses: actions/github-script@v6
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"
script: |
try {
const response = await github.rest.repos.getRelease({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: process.env.RELEASE_ID,
});
const formattedBody = JSON.stringify(response.data.body).slice(1, -1);
return formattedBody;
} catch (error) {
core.setFailed(error.message);
}
env:
RELEASE_ID: ${{ env.RELEASE_ID }}
- name: Github Releases To Discord
env:
WEBHOOK_URL: ${{ secrets.WEBHOOK_URL }}
REALEASE_TAG: ${{ env.RELEASE_TAG }}
RELEASE_NOTES: ${{ steps.release-notes.outputs.result }}
run: |
curl -H "Content-Type: application/json" -X POST -d '{
"content": "New LiteLLM release '"${RELEASE_TAG}"'",
"username": "Release Changelog",
"avatar_url": "https://cdn.discordapp.com/avatars/487431320314576937/bd64361e4ba6313d561d54e78c9e7171.png",
"embeds": [
{
"title": "Changelog for LiteLLM '"${RELEASE_TAG}"'",
"description": "'"${RELEASE_NOTES}"'",
"color": 2105893
}
]
}' $WEBHOOK_URL
-67
View File
@@ -1,67 +0,0 @@
# this workflow is triggered by an API call when there is a new PyPI release of LiteLLM
name: Build, Publish LiteLLM Helm Chart. New Release
on:
workflow_dispatch:
inputs:
chartVersion:
description: "Update the helm chart's version to this"
# Defines two custom environment variables for the workflow. Used for the Container registry domain, and a name for the Docker image that this workflow builds.
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
REPO_OWNER: ${{github.repository_owner}}
# There is a single job in this workflow. It's configured to run on the latest available version of Ubuntu.
jobs:
build-and-push-helm-chart:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Log in to the Container registry
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: lowercase github.repository_owner
run: |
echo "REPO_OWNER=`echo ${{github.repository_owner}} | tr '[:upper:]' '[:lower:]'`" >>${GITHUB_ENV}
- name: Get LiteLLM Latest Tag
id: current_app_tag
uses: WyriHaximus/github-action-get-previous-tag@v1.3.0
- name: Get last published chart version
id: current_version
shell: bash
run: helm show chart oci://${{ env.REGISTRY }}/${{ env.REPO_OWNER }}/litellm-helm | grep '^version:' | awk 'BEGIN{FS=":"}{print "current-version="$2}' | tr -d " " | tee -a $GITHUB_OUTPUT
env:
HELM_EXPERIMENTAL_OCI: '1'
# Automatically update the helm chart version one "patch" level
- name: Bump release version
id: bump_version
uses: christian-draeger/increment-semantic-version@1.1.0
with:
current-version: ${{ steps.current_version.outputs.current-version || '0.1.0' }}
version-fragment: 'bug'
- name: Lint helm chart
run: helm lint deploy/charts/litellm-helm
- uses: ./.github/actions/helm-oci-chart-releaser
with:
name: litellm-helm
repository: ${{ env.REPO_OWNER }}
tag: ${{ github.event.inputs.chartVersion || steps.bump_version.outputs.next-version || '0.1.0' }}
app_version: ${{ steps.current_app_tag.outputs.tag || 'latest' }}
path: deploy/charts/litellm-helm
registry: ${{ env.REGISTRY }}
registry_username: ${{ github.actor }}
registry_password: ${{ secrets.GITHUB_TOKEN }}
update_dependencies: true
-27
View File
@@ -1,27 +0,0 @@
name: Helm unit test
on:
pull_request:
push:
branches:
- main
jobs:
unit-test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up Helm 3.11.1
uses: azure/setup-helm@v1
with:
version: '3.11.1'
- name: Install Helm Unit Test Plugin
run: |
helm plugin install https://github.com/helm-unittest/helm-unittest --version v0.4.4
- name: Run unit tests
run:
helm unittest -f 'tests/*.yaml' deploy/charts/litellm-helm
-138
View File
@@ -1,138 +0,0 @@
import csv
import os
from github import Github
def interpret_results(csv_file):
with open(csv_file, newline="") as csvfile:
csvreader = csv.DictReader(csvfile)
rows = list(csvreader)
"""
in this csv reader
- Create 1 new column "Status"
- if a row has a median response time < 300 and an average response time < 300, Status = "Passed ✅"
- if a row has a median response time >= 300 or an average response time >= 300, Status = "Failed ❌"
- Order the table in this order Name, Status, Median Response Time, Average Response Time, Requests/s,Failures/s, Min Response Time, Max Response Time, all other columns
"""
# Add a new column "Status"
for row in rows:
median_response_time = float(
row["Median Response Time"].strip().rstrip("ms")
)
average_response_time = float(
row["Average Response Time"].strip().rstrip("s")
)
request_count = int(row["Request Count"])
failure_count = int(row["Failure Count"])
failure_percent = round((failure_count / request_count) * 100, 2)
# Determine status based on conditions
if (
median_response_time < 300
and average_response_time < 300
and failure_percent < 5
):
row["Status"] = "Passed ✅"
else:
row["Status"] = "Failed ❌"
# Construct Markdown table header
markdown_table = "| Name | Status | Median Response Time (ms) | Average Response Time (ms) | Requests/s | Failures/s | Request Count | Failure Count | Min Response Time (ms) | Max Response Time (ms) |"
markdown_table += (
"\n| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |"
)
# Construct Markdown table rows
for row in rows:
markdown_table += f"\n| {row['Name']} | {row['Status']} | {row['Median Response Time']} | {row['Average Response Time']} | {row['Requests/s']} | {row['Failures/s']} | {row['Request Count']} | {row['Failure Count']} | {row['Min Response Time']} | {row['Max Response Time']} |"
print("markdown table: ", markdown_table)
return markdown_table
def _get_docker_run_command_stable_release(release_version):
return f"""
\n\n
## Docker Run LiteLLM Proxy
```
docker run \\
-e STORE_MODEL_IN_DB=True \\
-p 4000:4000 \\
ghcr.io/berriai/litellm:litellm_stable_release_branch-{release_version}
```
"""
def _get_docker_run_command(release_version):
return f"""
\n\n
## Docker Run LiteLLM Proxy
```
docker run \\
-e STORE_MODEL_IN_DB=True \\
-p 4000:4000 \\
ghcr.io/berriai/litellm:main-{release_version}
```
"""
def get_docker_run_command(release_version):
if "stable" in release_version:
return _get_docker_run_command_stable_release(release_version)
else:
return _get_docker_run_command(release_version)
if __name__ == "__main__":
csv_file = "load_test_stats.csv" # Change this to the path of your CSV file
markdown_table = interpret_results(csv_file)
# Update release body with interpreted results
github_token = os.getenv("GITHUB_TOKEN")
g = Github(github_token)
repo = g.get_repo(
"BerriAI/litellm"
) # Replace with your repository's username and name
latest_release = repo.get_latest_release()
print("got latest release: ", latest_release)
print(latest_release.title)
print(latest_release.tag_name)
release_version = latest_release.title
print("latest release body: ", latest_release.body)
print("markdown table: ", markdown_table)
# check if "Load Test LiteLLM Proxy Results" exists
existing_release_body = latest_release.body
if "Load Test LiteLLM Proxy Results" in latest_release.body:
# find the "Load Test LiteLLM Proxy Results" section and delete it
start_index = latest_release.body.find("Load Test LiteLLM Proxy Results")
existing_release_body = latest_release.body[:start_index]
docker_run_command = get_docker_run_command(release_version)
print("docker run command: ", docker_run_command)
new_release_body = (
existing_release_body
+ docker_run_command
+ "\n\n"
+ "### Don't want to maintain your internal proxy? get in touch 🎉"
+ "\nHosted Proxy Alpha: https://calendly.com/d/4mp-gd3-k5k/litellm-1-1-onboarding-chat"
+ "\n\n"
+ "## Load Test LiteLLM Proxy Results"
+ "\n\n"
+ markdown_table
)
print("new release body: ", new_release_body)
try:
latest_release.update_release(
name=latest_release.tag_name,
message=new_release_body,
)
except Exception as e:
print(e)
-17
View File
@@ -1,17 +0,0 @@
name: Label ML Ops Team Issues
on:
issues:
types:
- opened
jobs:
add-mlops-label:
runs-on: ubuntu-latest
steps:
- name: Check if ML Ops Team is selected
uses: actions-ecosystem/action-add-labels@v1
if: contains(github.event.issue.body, '### Are you a ML Ops Team?') && contains(github.event.issue.body, 'Yes')
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
labels: "mlops user request"
-59
View File
@@ -1,59 +0,0 @@
name: Test Locust Load Test
on:
workflow_run:
workflows: ["Build, Publish LiteLLM Docker Image. New Release"]
types:
- completed
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v1
- name: Setup Python
uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install PyGithub
- name: re-deploy proxy
run: |
echo "Current working directory: $PWD"
ls
python ".github/workflows/redeploy_proxy.py"
env:
LOAD_TEST_REDEPLOY_URL1: ${{ secrets.LOAD_TEST_REDEPLOY_URL1 }}
LOAD_TEST_REDEPLOY_URL2: ${{ secrets.LOAD_TEST_REDEPLOY_URL2 }}
working-directory: ${{ github.workspace }}
- name: Run Load Test
id: locust_run
uses: BerriAI/locust-github-action@master
with:
LOCUSTFILE: ".github/workflows/locustfile.py"
URL: "https://post-release-load-test-proxy.onrender.com/"
USERS: "20"
RATE: "20"
RUNTIME: "300s"
- name: Process Load Test Stats
run: |
echo "Current working directory: $PWD"
ls
python ".github/workflows/interpret_load_test.py"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
working-directory: ${{ github.workspace }}
- name: Upload CSV as Asset to Latest Release
uses: xresloader/upload-to-github-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
file: "load_test_stats.csv;load_test.html"
update_latest_release: true
tag_name: "load-test"
overwrite: true
-28
View File
@@ -1,28 +0,0 @@
from locust import HttpUser, task, between
class MyUser(HttpUser):
wait_time = between(1, 5)
@task
def chat_completion(self):
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer sk-8N1tLOOyH8TIxwOLahhIVg",
# Include any additional headers you may need for authentication, etc.
}
# Customize the payload with "model" and "messages" keys
payload = {
"model": "fake-openai-endpoint",
"messages": [
{"role": "system", "content": "You are a chat bot."},
{"role": "user", "content": "Hello, how are you?"},
],
# Add more data as necessary
}
# Make a POST request to the "chat/completions" endpoint
response = self.client.post("chat/completions", json=payload, headers=headers)
# Print or log the response if needed
-34
View File
@@ -1,34 +0,0 @@
name: Publish Dev Release to PyPI
on:
workflow_dispatch:
jobs:
publish-dev-release:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.8 # Adjust the Python version as needed
- name: Install dependencies
run: pip install toml twine
- name: Read version from pyproject.toml
id: read-version
run: |
version=$(python -c 'import toml; print(toml.load("pyproject.toml")["tool"]["commitizen"]["version"])')
printf "LITELLM_VERSION=%s" "$version" >> $GITHUB_ENV
- name: Check if version exists on PyPI
id: check-version
run: |
set -e
if twine check --repository-url https://pypi.org/simple/ "litellm==$LITELLM_VERSION" >/dev/null 2>&1; then
echo "Version $LITELLM_VERSION already exists on PyPI. Skipping publish."
@@ -1,31 +0,0 @@
name: Read Version from pyproject.toml
on:
push:
branches:
- main # Change this to the default branch of your repository
jobs:
read-version:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.8 # Adjust the Python version as needed
- name: Install dependencies
run: pip install toml
- name: Read version from pyproject.toml
id: read-version
run: |
version=$(python -c 'import toml; print(toml.load("pyproject.toml")["tool"]["commitizen"]["version"])')
printf "LITELLM_VERSION=%s" "$version" >> $GITHUB_ENV
- name: Display version
run: echo "Current version is $LITELLM_VERSION"
-20
View File
@@ -1,20 +0,0 @@
"""
redeploy_proxy.py
"""
import os
import requests
import time
# send a get request to this endpoint
deploy_hook1 = os.getenv("LOAD_TEST_REDEPLOY_URL1")
response = requests.get(deploy_hook1, timeout=20)
deploy_hook2 = os.getenv("LOAD_TEST_REDEPLOY_URL2")
response = requests.get(deploy_hook2, timeout=20)
print("SENT GET REQUESTS to re-deploy proxy")
print("sleeeping.... for 60s")
time.sleep(60)
-39
View File
@@ -1,39 +0,0 @@
name: Reset litellm_stable branch
on:
release:
types: [published, created]
jobs:
update-stable-branch:
if: ${{ startsWith(github.event.release.tag_name, 'v') && !endsWith(github.event.release.tag_name, '-stable') }}
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Reset litellm_stable_release_branch branch to the release commit
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Configure Git user
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
# Fetch all branches and tags
git fetch --all
# Check if the litellm_stable_release_branch branch exists
if git show-ref --verify --quiet refs/remotes/origin/litellm_stable_release_branch; then
echo "litellm_stable_release_branch branch exists."
git checkout litellm_stable_release_branch
else
echo "litellm_stable_release_branch branch does not exist. Creating it."
git checkout -b litellm_stable_release_branch
fi
# Reset litellm_stable_release_branch branch to the release commit
git reset --hard $GITHUB_SHA
# Push the updated litellm_stable_release_branch branch
git push origin litellm_stable_release_branch --force
-27
View File
@@ -1,27 +0,0 @@
Date,"Ben
Ashley",Tom Brooks,Jimmy Cooney,"Sue
Daniels",Berlinda Fong,Terry Jones,Angelina Little,Linda Smith
10/1,FALSE,TRUE,TRUE,TRUE,TRUE,TRUE,FALSE,TRUE
10/2,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE
10/3,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE
10/4,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE
10/5,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE
10/6,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE
10/7,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE
10/8,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE
10/9,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE
10/10,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE
10/11,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE
10/12,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE
10/13,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE
10/14,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE
10/15,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE
10/16,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE
10/17,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE
10/18,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE
10/19,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE
10/20,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE
10/21,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE
10/22,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE
10/23,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE
Total,0,1,1,1,1,1,0,1
1 Date Ben Ashley Tom Brooks Jimmy Cooney Sue Daniels Berlinda Fong Terry Jones Angelina Little Linda Smith
2 10/1 FALSE TRUE TRUE TRUE TRUE TRUE FALSE TRUE
3 10/2 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
4 10/3 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
5 10/4 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
6 10/5 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
7 10/6 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
8 10/7 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
9 10/8 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
10 10/9 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
11 10/10 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
12 10/11 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
13 10/12 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
14 10/13 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
15 10/14 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
16 10/15 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
17 10/16 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
18 10/17 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
19 10/18 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
20 10/19 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
21 10/20 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
22 10/21 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
23 10/22 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
24 10/23 FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
25 Total 0 1 1 1 1 1 0 1
-20
View File
@@ -1,20 +0,0 @@
name: "Stale Issue Management"
on:
schedule:
- cron: '0 0 * * *' # Runs daily at midnight UTC
workflow_dispatch:
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v8
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
stale-issue-message: "This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs."
stale-pr-message: "This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs."
days-before-stale: 90 # Revert to 60 days
days-before-close: 7 # Revert to 7 days
stale-issue-label: "stale"
operations-per-run: 1000
-54
View File
@@ -1,54 +0,0 @@
import os
import requests
from datetime import datetime
# GitHub API endpoints
GITHUB_API_URL = "https://api.github.com"
REPO_OWNER = "BerriAI"
REPO_NAME = "litellm"
# GitHub personal access token (required for uploading release assets)
GITHUB_ACCESS_TOKEN = os.environ.get("GITHUB_ACCESS_TOKEN")
# Headers for GitHub API requests
headers = {
"Accept": "application/vnd.github+json",
"Authorization": f"Bearer {GITHUB_ACCESS_TOKEN}",
"X-GitHub-Api-Version": "2022-11-28",
}
# Get the latest release
releases_url = f"{GITHUB_API_URL}/repos/{REPO_OWNER}/{REPO_NAME}/releases/latest"
response = requests.get(releases_url, headers=headers)
latest_release = response.json()
print("Latest release:", latest_release)
# Upload an asset to the latest release
upload_url = latest_release["upload_url"].split("{?")[0]
asset_name = "results_stats.csv"
asset_path = os.path.join(os.getcwd(), asset_name)
print("upload_url:", upload_url)
with open(asset_path, "rb") as asset_file:
asset_data = asset_file.read()
upload_payload = {
"name": asset_name,
"label": "Load test results",
"created_at": datetime.utcnow().isoformat() + "Z",
}
upload_headers = headers.copy()
upload_headers["Content-Type"] = "application/octet-stream"
upload_response = requests.post(
upload_url,
headers=upload_headers,
data=asset_data,
params=upload_payload,
)
if upload_response.status_code == 201:
print(f"Asset '{asset_name}' uploaded successfully to the latest release.")
else:
print(f"Failed to upload asset. Response: {upload_response.text}")
+54 -84
View File
@@ -1,85 +1,55 @@
.venv
.env
.newenv
newenv/*
litellm/proxy/myenv/*
litellm_uuid.txt
__pycache__/
*.pyc
bun.lockb
**/.DS_Store
.aider*
litellm_results.jsonl
secrets.toml
.gitignore
litellm/proxy/litellm_secrets.toml
litellm/proxy/api_log.json
.idea/
router_config.yaml
litellm_server/config.yaml
litellm/proxy/_secret_config.yaml
.aws-sam/
litellm/tests/aiologs.log
litellm/tests/exception_data.txt
litellm/tests/config_*.yaml
litellm/tests/langfuse.log
langfuse.log
.langfuse.log
litellm/tests/test_custom_logger.py
litellm/tests/langfuse.log
litellm/tests/dynamo*.log
.vscode/settings.json
litellm/proxy/log.txt
proxy_server_config_@.yaml
.gitignore
proxy_server_config_2.yaml
litellm/proxy/secret_managers/credentials.json
hosted_config.yaml
litellm/proxy/tests/node_modules
litellm/proxy/tests/package.json
litellm/proxy/tests/package-lock.json
ui/litellm-dashboard/.next
ui/litellm-dashboard/node_modules
ui/litellm-dashboard/next-env.d.ts
ui/litellm-dashboard/package.json
ui/litellm-dashboard/package-lock.json
deploy/charts/litellm/*.tgz
deploy/charts/litellm/charts/*
deploy/charts/*.tgz
litellm/proxy/vertex_key.json
**/.vim/
**/node_modules
kub.yaml
loadtest_kub.yaml
litellm/proxy/_new_secret_config.yaml
litellm/proxy/_new_secret_config.yaml
litellm/proxy/_super_secret_config.yaml
litellm/proxy/_super_secret_config.yaml
litellm/proxy/myenv/bin/activate
litellm/proxy/myenv/bin/Activate.ps1
myenv/*
litellm/proxy/_experimental/out/404/index.html
litellm/proxy/_experimental/out/model_hub/index.html
litellm/proxy/_experimental/out/onboarding/index.html
litellm/tests/log.txt
litellm/tests/langfuse.log
litellm/tests/langfuse.log
litellm/proxy/google-cloud-sdk/*
tests/llm_translation/log.txt
venv/
tests/local_testing/log.txt
### node etc ###
.codegpt
litellm/proxy/_new_new_secret_config.yaml
litellm/proxy/custom_guardrail.py
litellm/proxy/_experimental/out/404.html
litellm/proxy/_experimental/out/404.html
litellm/proxy/_experimental/out/model_hub.html
.mypy_cache/*
litellm/proxy/application.log
tests/llm_translation/vertex_test_account.json
tests/llm_translation/test_vertex_key.json
litellm/proxy/migrations/0_init/migration.sql
litellm/proxy/db/migrations/0_init/migration.sql
litellm/proxy/db/migrations/*
litellm/proxy/migrations/*
# Logs
data-node
logs
*.log
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Compiled Dirs (http://nodejs.org/api/addons.html)
build/
dist/
public/main.js
public/main.js.map
public/main.js.LICENSE.txt
client/public/main.js
client/public/main.js.map
client/public/main.js.LICENSE.txt
# Dependency directorys
# Deployed apps should consider commenting these lines out:
# see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git
node_modules/
meili_data/
api/node_modules/
client/node_modules/
bower_components/
# Floobits
.floo
.floobit
.floo
.flooignore
# Environment
.env
cache.json
api/data/
owner.yml
archive
.vscode/settings.json
src/style - official.css
-49
View File
@@ -1,49 +0,0 @@
repos:
- repo: local
hooks:
- id: pyright
name: pyright
entry: pyright
language: system
types: [python]
files: ^litellm/
- id: isort
name: isort
entry: isort
language: system
types: [python]
files: litellm/.*\.py
exclude: ^litellm/__init__.py$
- repo: https://github.com/psf/black
rev: 24.2.0
hooks:
- id: black
- repo: https://github.com/pycqa/flake8
rev: 7.0.0 # The version of flake8 to use
hooks:
- id: flake8
exclude: ^litellm/tests/|^litellm/proxy/tests/|^litellm/tests/litellm/|^tests/litellm/
additional_dependencies: [flake8-print]
files: litellm/.*\.py
# - id: flake8
# name: flake8 (router.py function length)
# files: ^litellm/router\.py$
# args: [--max-function-length=40]
# # additional_dependencies: [flake8-functions]
- repo: https://github.com/python-poetry/poetry
rev: 1.8.0
hooks:
- id: poetry-check
- repo: local
hooks:
- id: check-files-match
name: Check if files match
entry: python3 ci_cd/check_files_match.py
language: system
# - id: check-file-length
# name: Check file length
# entry: python check_file_length.py
# args: ["10000"] # set your desired maximum number of lines
# language: python
# files: litellm/.*\.py
# exclude: ^litellm/tests/
-83
View File
@@ -1,83 +0,0 @@
# Base image for building
ARG LITELLM_BUILD_IMAGE=cgr.dev/chainguard/python:latest-dev
# Runtime image
ARG LITELLM_RUNTIME_IMAGE=cgr.dev/chainguard/python:latest-dev
# Builder stage
FROM $LITELLM_BUILD_IMAGE AS builder
# Set the working directory to /app
WORKDIR /app
USER root
# Install build dependencies
RUN apk update && \
apk add --no-cache gcc python3-dev openssl openssl-dev
RUN pip install --upgrade pip && \
pip install build
# Copy the current directory contents into the container at /app
COPY . .
# Build Admin UI
RUN chmod +x docker/build_admin_ui.sh && ./docker/build_admin_ui.sh
# Build the package
RUN rm -rf dist/* && python -m build
# There should be only one wheel file now, assume the build only creates one
RUN ls -1 dist/*.whl | head -1
# Install the package
RUN pip install dist/*.whl
# install dependencies as wheels
RUN pip wheel --no-cache-dir --wheel-dir=/wheels/ -r requirements.txt
# install semantic-cache [Experimental]- we need this here and not in requirements.txt because redisvl pins to pydantic 1.0
RUN pip install redisvl==0.0.7 --no-deps
# ensure pyjwt is used, not jwt
RUN pip uninstall jwt -y
RUN pip uninstall PyJWT -y
RUN pip install PyJWT==2.9.0 --no-cache-dir
# Build Admin UI
RUN chmod +x docker/build_admin_ui.sh && ./docker/build_admin_ui.sh
# Runtime stage
FROM $LITELLM_RUNTIME_IMAGE AS runtime
# Ensure runtime stage runs as root
USER root
# Install runtime dependencies
RUN apk update && \
apk add --no-cache openssl
WORKDIR /app
# Copy the current directory contents into the container at /app
COPY . .
RUN ls -la /app
# Copy the built wheel from the builder stage to the runtime stage; assumes only one wheel file is present
COPY --from=builder /app/dist/*.whl .
COPY --from=builder /wheels/ /wheels/
# Install the built wheel using pip; again using a wildcard if it's the only file
RUN pip install *.whl /wheels/* --no-index --find-links=/wheels/ && rm -f *.whl && rm -rf /wheels
# Generate prisma client
RUN prisma generate
RUN chmod +x docker/entrypoint.sh
RUN chmod +x docker/prod_entrypoint.sh
EXPOSE 4000/tcp
ENTRYPOINT ["docker/prod_entrypoint.sh"]
# Append "--detailed_debug" to the end of CMD to view detailed debug logs
CMD ["--port", "4000"]
+1 -6
View File
@@ -1,11 +1,6 @@
Portions of this software are licensed as follows:
* All content that resides under the "enterprise/" directory of this repository, if that directory exists, is licensed under the license defined in "enterprise/LICENSE".
* Content outside of the above mentioned directories or restrictions above is available under the MIT license as defined below.
---
MIT License
Copyright (c) 2023 Berri AI
Copyright (c) 2023 Danny Avila
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
-32
View File
@@ -1,32 +0,0 @@
# LiteLLM Makefile
# Simple Makefile for running tests and basic development tasks
.PHONY: help test test-unit test-integration lint format
# Default target
help:
@echo "Available commands:"
@echo " make test - Run all tests"
@echo " make test-unit - Run unit tests"
@echo " make test-integration - Run integration tests"
@echo " make test-unit-helm - Run helm unit tests"
install-dev:
poetry install --with dev
lint: install-dev
poetry run pip install types-requests types-setuptools types-redis types-PyYAML
cd litellm && poetry run mypy . --ignore-missing-imports
# Testing
test:
poetry run pytest tests/
test-unit:
poetry run pytest tests/litellm/
test-integration:
poetry run pytest tests/ -k "not litellm"
test-unit-helm:
helm unittest -f 'tests/*.yaml' deploy/charts/litellm-helm
+275 -377
View File
@@ -1,413 +1,311 @@
<h1 align="center">
🚅 LiteLLM
</h1>
<p align="center">
<p align="center">
<a href="https://render.com/deploy?repo=https://github.com/BerriAI/litellm" target="_blank" rel="nofollow"><img src="https://render.com/images/deploy-to-render-button.svg" alt="Deploy to Render"></a>
<a href="https://railway.app/template/HLP0Ub?referralCode=jch2ME">
<img src="https://railway.app/button.svg" alt="Deploy on Railway">
</a>
</p>
<p align="center">Call all LLM APIs using the OpenAI format [Bedrock, Huggingface, VertexAI, TogetherAI, Azure, OpenAI, Groq etc.]
<br>
</p>
<h4 align="center"><a href="https://docs.litellm.ai/docs/simple_proxy" target="_blank">LiteLLM Proxy Server (LLM Gateway)</a> | <a href="https://docs.litellm.ai/docs/hosted" target="_blank"> Hosted Proxy (Preview)</a> | <a href="https://docs.litellm.ai/docs/enterprise"target="_blank">Enterprise Tier</a></h4>
<h4 align="center">
<a href="https://pypi.org/project/litellm/" target="_blank">
<img src="https://img.shields.io/pypi/v/litellm.svg" alt="PyPI Version">
</a>
<a href="https://dl.circleci.com/status-badge/redirect/gh/BerriAI/litellm/tree/main" target="_blank">
<img src="https://dl.circleci.com/status-badge/img/gh/BerriAI/litellm/tree/main.svg?style=svg" alt="CircleCI">
</a>
<a href="https://www.ycombinator.com/companies/berriai">
<img src="https://img.shields.io/badge/Y%20Combinator-W23-orange?style=flat-square" alt="Y Combinator W23">
</a>
<a href="https://wa.link/huol9n">
<img src="https://img.shields.io/static/v1?label=Chat%20on&message=WhatsApp&color=success&logo=WhatsApp&style=flat-square" alt="Whatsapp">
</a>
<a href="https://discord.gg/wuPM9dRgDw">
<img src="https://img.shields.io/static/v1?label=Chat%20on&message=Discord&color=blue&logo=Discord&style=flat-square" alt="Discord">
</a>
</h4>
# ChatGPT Clone #
https://user-images.githubusercontent.com/110412045/223754183-8b7f45ce-6517-4bd5-9b39-c624745bf399.mp4
LiteLLM manages:
## All AI Conversations under One Roof. ##
Assistant AIs are the future and OpenAI revolutionized this movement with ChatGPT. While numerous methods exist to integrate them, this app commemorates the original styling of ChatGPT, with the ability to integrate any current/future AI models, while improving upon original client features, such as conversation search and prompt templates (currently WIP).
- Translate inputs to provider's `completion`, `embedding`, and `image_generation` endpoints
- [Consistent output](https://docs.litellm.ai/docs/completion/output), text responses will always be available at `['choices'][0]['message']['content']`
- Retry/fallback logic across multiple deployments (e.g. Azure/OpenAI) - [Router](https://docs.litellm.ai/docs/routing)
- Set Budgets & Rate limits per project, api key, model [LiteLLM Proxy Server (LLM Gateway)](https://docs.litellm.ai/docs/simple_proxy)
This project was started early in Feb '23, anticipating the release of the official ChatGPT API from OpenAI, and now uses it. Through this clone, you can avoid ChatGPT Plus in favor of free or pay-per-call APIs. I will soon deploy a demo of this app. Feel free to contribute, clone, or fork. Currently dockerized.
[**Jump to LiteLLM Proxy (LLM Gateway) Docs**](https://github.com/BerriAI/litellm?tab=readme-ov-file#openai-proxy---docs) <br>
[**Jump to Supported LLM Providers**](https://github.com/BerriAI/litellm?tab=readme-ov-file#supported-providers-docs)
🚨 **Stable Release:** Use docker images with the `-stable` tag. These have undergone 12 hour load tests, before being published. [More information about the release cycle here](https://docs.litellm.ai/docs/proxy/release_cycle)
Support for more providers. Missing a provider or LLM Platform, raise a [feature request](https://github.com/BerriAI/litellm/issues/new?assignees=&labels=enhancement&projects=&template=feature_request.yml&title=%5BFeature%5D%3A+).
# Usage ([**Docs**](https://docs.litellm.ai/docs/))
> [!IMPORTANT]
> LiteLLM v1.0.0 now requires `openai>=1.0.0`. Migration guide [here](https://docs.litellm.ai/docs/migration)
> LiteLLM v1.40.14+ now requires `pydantic>=2.0.0`. No changes required.
<a target="_blank" href="https://colab.research.google.com/github/BerriAI/litellm/blob/main/cookbook/liteLLM_Getting_Started.ipynb">
<img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open In Colab"/>
</a>
```shell
pip install litellm
```
```python
from litellm import completion
import os
## set ENV variables
os.environ["OPENAI_API_KEY"] = "your-openai-key"
os.environ["ANTHROPIC_API_KEY"] = "your-anthropic-key"
messages = [{ "content": "Hello, how are you?","role": "user"}]
# openai call
response = completion(model="openai/gpt-4o", messages=messages)
# anthropic call
response = completion(model="anthropic/claude-3-sonnet-20240229", messages=messages)
print(response)
```
### Response (OpenAI Format)
```json
{
"id": "chatcmpl-565d891b-a42e-4c39-8d14-82a1f5208885",
"created": 1734366691,
"model": "claude-3-sonnet-20240229",
"object": "chat.completion",
"system_fingerprint": null,
"choices": [
{
"finish_reason": "stop",
"index": 0,
"message": {
"content": "Hello! As an AI language model, I don't have feelings, but I'm operating properly and ready to assist you with any questions or tasks you may have. How can I help you today?",
"role": "assistant",
"tool_calls": null,
"function_call": null
}
}
],
"usage": {
"completion_tokens": 43,
"prompt_tokens": 13,
"total_tokens": 56,
"completion_tokens_details": null,
"prompt_tokens_details": {
"audio_tokens": null,
"cached_tokens": 0
},
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 0
}
}
```
Call any model supported by a provider, with `model=<provider_name>/<model_name>`. There might be provider-specific details here, so refer to [provider docs for more information](https://docs.litellm.ai/docs/providers)
## Async ([Docs](https://docs.litellm.ai/docs/completion/stream#async-completion))
```python
from litellm import acompletion
import asyncio
async def test_get_response():
user_message = "Hello, how are you?"
messages = [{"content": user_message, "role": "user"}]
response = await acompletion(model="openai/gpt-4o", messages=messages)
return response
response = asyncio.run(test_get_response())
print(response)
```
## Streaming ([Docs](https://docs.litellm.ai/docs/completion/stream))
liteLLM supports streaming the model response back, pass `stream=True` to get a streaming iterator in response.
Streaming is supported for all models (Bedrock, Huggingface, TogetherAI, Azure, OpenAI, etc.)
```python
from litellm import completion
response = completion(model="openai/gpt-4o", messages=messages, stream=True)
for part in response:
print(part.choices[0].delta.content or "")
# claude 2
response = completion('anthropic/claude-3-sonnet-20240229', messages, stream=True)
for part in response:
print(part)
```
### Response chunk (OpenAI Format)
```json
{
"id": "chatcmpl-2be06597-eb60-4c70-9ec5-8cd2ab1b4697",
"created": 1734366925,
"model": "claude-3-sonnet-20240229",
"object": "chat.completion.chunk",
"system_fingerprint": null,
"choices": [
{
"finish_reason": null,
"index": 0,
"delta": {
"content": "Hello",
"role": "assistant",
"function_call": null,
"tool_calls": null,
"audio": null
},
"logprobs": null
}
]
}
```
## Logging Observability ([Docs](https://docs.litellm.ai/docs/observability/callbacks))
LiteLLM exposes pre defined callbacks to send data to Lunary, MLflow, Langfuse, DynamoDB, s3 Buckets, Helicone, Promptlayer, Traceloop, Athina, Slack
```python
from litellm import completion
## set env variables for logging tools (when using MLflow, no API key set up is required)
os.environ["LUNARY_PUBLIC_KEY"] = "your-lunary-public-key"
os.environ["HELICONE_API_KEY"] = "your-helicone-auth-key"
os.environ["LANGFUSE_PUBLIC_KEY"] = ""
os.environ["LANGFUSE_SECRET_KEY"] = ""
os.environ["ATHINA_API_KEY"] = "your-athina-api-key"
os.environ["OPENAI_API_KEY"] = "your-openai-key"
# set callbacks
litellm.success_callback = ["lunary", "mlflow", "langfuse", "athina", "helicone"] # log input/output to lunary, langfuse, supabase, athina, helicone etc
#openai call
response = completion(model="openai/gpt-4o", messages=[{"role": "user", "content": "Hi 👋 - i'm openai"}])
```
# LiteLLM Proxy Server (LLM Gateway) - ([Docs](https://docs.litellm.ai/docs/simple_proxy))
Track spend + Load Balance across multiple projects
[Hosted Proxy (Preview)](https://docs.litellm.ai/docs/hosted)
The proxy provides:
1. [Hooks for auth](https://docs.litellm.ai/docs/proxy/virtual_keys#custom-auth)
2. [Hooks for logging](https://docs.litellm.ai/docs/proxy/logging#step-1---create-your-custom-litellm-callback-class)
3. [Cost tracking](https://docs.litellm.ai/docs/proxy/virtual_keys#tracking-spend)
4. [Rate Limiting](https://docs.litellm.ai/docs/proxy/users#set-rate-limits)
## 📖 Proxy Endpoints - [Swagger Docs](https://litellm-api.up.railway.app/)
## Updates
<details open>
<summary><strong>2023-03-16</strong></summary>
## Quick Start Proxy - CLI
```shell
pip install 'litellm[proxy]'
```
[Latest release (v0.0.4)](https://github.com/danny-avila/chatgpt-clone/releases/tag/v0.0.4) includes Resubmitting messages & Branching messages, which mirrors official ChatGPT feature of editing a sent message, that then branches the conversation into separate message paths (works only with ChatGPT)
### Step 1: Start litellm proxy
Full details and [example here](https://github.com/danny-avila/chatgpt-clone/releases/tag/v0.0.4). Message search is on the docket
```shell
$ litellm --model huggingface/bigcode/starcoder
</details>
#INFO: Proxy running on http://0.0.0.0:4000
```
### Step 2: Make ChatCompletions Request to Proxy
<details>
<details>
<summary><strong>2023-03-12</strong></summary>
> [!IMPORTANT]
> 💡 [Use LiteLLM Proxy with Langchain (Python, JS), OpenAI SDK (Python, JS) Anthropic SDK, Mistral SDK, LlamaIndex, Instructor, Curl](https://docs.litellm.ai/docs/proxy/user_keys)
```python
import openai # openai v1.0.0+
client = openai.OpenAI(api_key="anything",base_url="http://0.0.0.0:4000") # set proxy to base_url
# request sent to model set on litellm proxy, `litellm --model`
response = client.chat.completions.create(model="gpt-3.5-turbo", messages = [
{
"role": "user",
"content": "this is a test request, write a short poem"
}
])
print(response)
```
Really thankful for all the issues reported and contributions made, the project's features and improvements have accelerated as result. Honorable mention is [wtlyu](https://github.com/wtlyu) for contributing a lot of mindful code, namely hostname configuration and mobile styling. I will upload images on next release for faster docker setup, and starting updating them simultaneously with this repo.
## Proxy Key Management ([Docs](https://docs.litellm.ai/docs/proxy/virtual_keys))
Connect the proxy with a Postgres DB to create proxy keys
Many improvements across the board, the biggest is being able to start conversations simultaneously (again thanks to [wtlyu](https://github.com/wtlyu) for bringing it to my attention), as you can switch conversations or start a new chat without any response streaming from a prior one, as the backend will still process/save client responses. Just watch out for any rate limiting from OpenAI/Microsoft if this is done excessively.
Adding support for conversation search is next! Thank you [mysticaltech](https://github.com/mysticaltech) for bringing up a method I can use for this.
</details>
<details>
<summary><strong>2023-03-09</strong></summary>
Released v.0.0.2
Adds Sydney (jailbroken Bing AI) to the model menu. Thank you [DavesDevFails](https://github.com/DavesDevFails) for bringing it to my attention in this [issue](https://github.com/danny-avila/chatgpt-clone/issues/13). Bing/Sydney now correctly cite links, more styling to come. Fix some overlooked bugs, and model menu doesn't close upon deleting a customGpt.
I've re-enabled the ChatGPT browser client (free version) since it might be working for most people, it no longer works for me. Sydney is the best free route anyway.
</details>
<details>
<summary><strong>2023-03-07</strong></summary>
Due to increased interest in the repo, I've dockerized the app as of this update for quick setup! See setup instructions below. I realize this still takes some time with installing docker dependencies, so it's on the roadmap to have a deployed demo. Besides this, I've made major improvements for a lot of the existing features across the board, mainly UI/UX.
Also worth noting, the method to access the Free Version is no longer working, so I've removed it from model selection until further notice.
</details>
<summary><strong>Previous Updates</strong></summary>
<details>
<summary><strong>2023-03-04</strong></summary>
Custom prompt prefixing and labeling is now supported through the official API. This nets some interesting results when you need ChatGPT for specific uses or entertainment. Select 'CustomGPT' in the model menu to configure this, and you can choose to save the configuration or reference it by conversation. Model selection will change by conversation.
</details>
<details>
<summary><strong>2023-03-01</strong></summary>
Official ChatGPT API is out! Removed davinci since the official API is extremely fast and 10x less expensive. Since user labeling and prompt prefixing is officially supported, I will add a View feature so you can set this within chat, which gives the UI an added use case. I've kept the BrowserClient, since it's free to use like the official site.
The Messages UI correctly mirrors code syntax highlighting. The exact replication of the cursor is not 1-to-1 yet, but pretty close. Later on in the project, I'll implement tests for code edge cases and explore the possibility of running code in-browser. Right now, unknown code defaults to javascript, but will detect language as close as possible.
</details>
<details>
<summary><strong>2023-02-21</strong></summary>
BingAI is integrated (although sadly limited by Microsoft with the 5 msg/convo limit, 50 msgs/day). I will need to handle the case when Bing refuses to give more answers on top of the other styling features I have in mind. Official ChatGPT use is back with the new BrowserClient. Brainstorming how to handle the UI when the Ai model changes, since conversations can't be persisted between them (or perhaps build a way to achieve this at some level).
</details>
<details >
<summary><strong>2023-02-15</strong></summary>
Just got access to Bing AI so I'll be focusing on integrating that through waylaidwanderer's 'experimental' BingAIClient.
</details>
<details>
<summary><strong>2023-02-14</strong></summary>
Official ChatGPT use is no longer possible though I recently used it with waylaidwanderer's [reverse proxy method](https://github.com/waylaidwanderer/node-chatgpt-api/blob/main/README.md#using-a-reverse-proxy), and before that, through leaked models he also discovered.
Currently, this project is only functional with the `text-davinci-003` model.
</details>
</details>
# Table of Contents
- [ChatGPT Clone](#chatgpt-clone)
- [All AI Conversations under One Roof.](#all-ai-conversations-under-one-roof)
- [Updates](#updates)
- [Table of Contents](#table-of-contents)
- [Roadmap](#roadmap)
- [Features](#features)
- [Tech Stack](#tech-stack)
- [Getting Started](#getting-started)
- [Prerequisites](#prerequisites)
- [Usage](#usage)
- [Local](#local)
- [Docker](#docker)
- [Access Tokens](#access-tokens)
- [Proxy](#proxy)
- [User System](#user-system)
- [Updating](#updating)
- [Use Cases](#use-cases)
- [Origin](#origin)
- [Caveats](#caveats)
- [Regarding use of Official ChatGPT API](#regarding-use-of-official-chatgpt-api)
- [Contributing](#contributing)
- [License](#license)
## Roadmap
> **Warning**
> This is a work in progress. I'm building this in public. FYI there is still a lot of tech debt to cleanup. You can follow the progress here or on my [Linkedin](https://www.linkedin.com/in/danny-avila).
Here are my recently completed and planned features:
- [x] Persistent conversation
- [x] Rename, delete conversations
- [x] UI Error handling
- [x] Bing AI integration
- [x] AI model change handling (start new convos within existing, remembers last selected)
- [x] Code block handling (highlighting, markdown, clipboard, language detection)
- [x] Markdown handling
- [x] Customize prompt prefix/label (custom ChatGPT using official API)
- [x] Server convo pagination (limit fetch and load more with 'show more' button)
- [x] Config file for easy startup (docker compose)
- [x] Mobile styling (thanks to [wtlyu](https://github.com/wtlyu))
- [x] Resubmit/edit sent messages (thanks to [wtlyu](https://github.com/wtlyu))
- [ ] Message Search
- [ ] Custom params for ChatGPT API (temp, top_p, presence_penalty)
- [ ] Bing AI Styling (params, suggested responses, convo end, etc.) - **In progress**
- [ ] Add warning before clearing convos
- [ ] Build test suite for CI/CD
- [ ] Prompt Templates/Search
- [ ] Refactor/clean up code (tech debt)
- [ ] Optional use of local storage for credentials
- [ ] Deploy demo
### Features
- Response streaming identical to ChatGPT through server-sent events
- UI from original ChatGPT, including Dark mode
- AI model selection (official ChatGPT API, BingAI, ChatGPT Free)
- Create and Save custom ChatGPTs*
^* ChatGPT can be 'customized' by setting a system message or prompt prefix and alternate 'role' to the API request
[More info here](https://platform.openai.com/docs/guides/chat/instructing-chat-models). Here's an [example from this app.]()
### Tech Stack
- Utilizes [node-chatgpt-api](https://github.com/waylaidwanderer/node-chatgpt-api)
- No React boilerplate/toolchain/clone tutorials, created from scratch with react@latest
- Use of Tailwind CSS and [shadcn/ui](https://github.com/shadcn/ui) components
- Docker, useSWR, Redux, Express, MongoDB, [Keyv](https://www.npmjs.com/package/keyv)
## Getting Started
### Prerequisites
- npm
- Node.js >= 19.0.0
- MongoDB installed or [MongoDB Atlas](https://account.mongodb.com/account/login) (required if not using Docker)
- [Docker (optional)](https://www.docker.com/get-started/)
- [OpenAI API key](https://platform.openai.com/account/api-keys)
- BingAI, ChatGPT access tokens (optional, free AIs)
## Usage
- **Clone/download** the repo down where desired
```bash
# Get the code
git clone https://github.com/BerriAI/litellm
git clone https://github.com/danny-avila/chatgpt-clone.git
```
- If using MongoDB Atlas, remove `&w=majority` from default connection string.
# Go to folder
cd litellm
### Local
- **Run npm** install in both the api and client directories
- **Provide** all credentials, (API keys, access tokens, and Mongo Connection String) in api/.env [(see .env example)](api/.env.example)
- **Run** `npm run build` in /client/ dir, `npm start` in /api/ dir
- **Visit** http://localhost:3080 (default port) & enjoy
# Add the master key - you can change this after setup
echo 'LITELLM_MASTER_KEY="sk-1234"' > .env
By default, only local machine can access this server. To share within network or serve as a public server, set `HOST` to `0.0.0.0` in `.env` file
# Add the litellm salt key - you cannot change this after adding a model
# It is used to encrypt / decrypt your LLM API Key credentials
# We recommend - https://1password.com/password-generator/
# password generator to get a random hash for litellm salt key
echo 'LITELLM_SALT_KEY="sk-1234"' > .env
### Docker
source .env
- **Provide** all credentials, (API keys, access tokens, and Mongo Connection String) in [docker-compose.yml](docker-compose.yml) under api service
- **Run** `docker-compose up` to start the app
# Start
docker-compose up
### Access Tokens
<details>
<summary><strong>ChatGPT Free Instructions</strong></summary>
To get your Access token For ChatGPT 'Free Version', login to chat.openai.com, then visit https://chat.openai.com/api/auth/session.
**Warning:** There may be a high chance of your account being banned with this method. Continue doing so at your own risk.
</details>
<details>
<summary><strong>BingAI Instructions</strong></summary>
The Bing Access Token is the "_U" cookie from bing.com. Use dev tools or an extension while logged into the site to view it.
**Note:** Specific error handling and styling for this model is still in progress.
</details>
### Proxy
If your server cannot connect to the chatGPT API server by some reason, (eg in China). You can set a environment variable `PROXY`. This will be transmitted to `node-chatgpt-api` interface.
**Warning:** `PROXY` is not `reverseProxyUrl` in `node-chatgpt-api`
<details>
<summary><strong>Set up proxy in local environment </strong></summary>
Here is two ways to set proxy.
- Option 1: system level environment
`export PROXY="http://127.0.0.1:7890"`
- Option 2: set in .env file
`PROXY="http://127.0.0.1:7890"`
**Change `http://127.0.0.1:7890` to your proxy server**
</details>
<details>
<summary><strong>Set up proxy in docker environment </strong></summary>
set in docker-compose.yml file, under services - api - environment
```
api:
...
environment:
...
- "PROXY=http://127.0.0.1:7890"
# add this line ↑
```
**Change `http://127.0.0.1:7890` to your proxy server**
UI on `/ui` on your proxy server
![ui_3](https://github.com/BerriAI/litellm/assets/29436595/47c97d5e-b9be-4839-b28c-43d7f4f10033)
</details>
Set budgets and rate limits across multiple projects
`POST /key/generate`
### User System
### Request
By default, there is no user system enabled, so anyone can access your server.
```shell
curl 'http://0.0.0.0:4000/key/generate' \
--header 'Authorization: Bearer sk-1234' \
--header 'Content-Type: application/json' \
--data-raw '{"models": ["gpt-3.5-turbo", "gpt-4", "claude-2"], "duration": "20m","metadata": {"user": "ishaan@berri.ai", "team": "core-infra"}}'
```
**This project is not designed to provide a complete and full-featured user system.** It's not high priority task and might never be provided.
### Expected Response
[wtlyu](https://github.com/wtlyu) provide a sample user system structure, that you can implement your own user system. It's simple and not a ready-for-use edition.
```shell
{
"key": "sk-kdEXbIqZRwEeEiHwdg7sFA", # Bearer token
"expires": "2023-11-19T01:38:25.838000+00:00" # datetime object
}
```
(If you want to implement your user system, open this ↓)
## Supported Providers ([Docs](https://docs.litellm.ai/docs/providers))
<details>
<summary><strong>Implement your own user system </strong></summary>
| Provider | [Completion](https://docs.litellm.ai/docs/#basic-usage) | [Streaming](https://docs.litellm.ai/docs/completion/stream#streaming-responses) | [Async Completion](https://docs.litellm.ai/docs/completion/stream#async-completion) | [Async Streaming](https://docs.litellm.ai/docs/completion/stream#async-streaming) | [Async Embedding](https://docs.litellm.ai/docs/embedding/supported_embedding) | [Async Image Generation](https://docs.litellm.ai/docs/image_generation) |
|-------------------------------------------------------------------------------------|---------------------------------------------------------|---------------------------------------------------------------------------------|-------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------|-------------------------------------------------------------------------------|-------------------------------------------------------------------------|
| [openai](https://docs.litellm.ai/docs/providers/openai) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| [azure](https://docs.litellm.ai/docs/providers/azure) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| [AI/ML API](https://docs.litellm.ai/docs/providers/aiml) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| [aws - sagemaker](https://docs.litellm.ai/docs/providers/aws_sagemaker) | ✅ | ✅ | ✅ | ✅ | ✅ | |
| [aws - bedrock](https://docs.litellm.ai/docs/providers/bedrock) | ✅ | ✅ | ✅ | ✅ | ✅ | |
| [google - vertex_ai](https://docs.litellm.ai/docs/providers/vertex) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| [google - palm](https://docs.litellm.ai/docs/providers/palm) | ✅ | ✅ | ✅ | ✅ | | |
| [google AI Studio - gemini](https://docs.litellm.ai/docs/providers/gemini) | ✅ | ✅ | ✅ | ✅ | | |
| [mistral ai api](https://docs.litellm.ai/docs/providers/mistral) | ✅ | ✅ | ✅ | ✅ | ✅ | |
| [cloudflare AI Workers](https://docs.litellm.ai/docs/providers/cloudflare_workers) | ✅ | ✅ | ✅ | ✅ | | |
| [cohere](https://docs.litellm.ai/docs/providers/cohere) | ✅ | ✅ | ✅ | ✅ | ✅ | |
| [anthropic](https://docs.litellm.ai/docs/providers/anthropic) | ✅ | ✅ | ✅ | ✅ | | |
| [empower](https://docs.litellm.ai/docs/providers/empower) | ✅ | ✅ | ✅ | ✅ |
| [huggingface](https://docs.litellm.ai/docs/providers/huggingface) | ✅ | ✅ | ✅ | ✅ | ✅ | |
| [replicate](https://docs.litellm.ai/docs/providers/replicate) | ✅ | ✅ | ✅ | ✅ | | |
| [together_ai](https://docs.litellm.ai/docs/providers/togetherai) | ✅ | ✅ | ✅ | ✅ | | |
| [openrouter](https://docs.litellm.ai/docs/providers/openrouter) | ✅ | ✅ | ✅ | ✅ | | |
| [ai21](https://docs.litellm.ai/docs/providers/ai21) | ✅ | ✅ | ✅ | ✅ | | |
| [baseten](https://docs.litellm.ai/docs/providers/baseten) | ✅ | ✅ | ✅ | ✅ | | |
| [vllm](https://docs.litellm.ai/docs/providers/vllm) | ✅ | ✅ | ✅ | ✅ | | |
| [nlp_cloud](https://docs.litellm.ai/docs/providers/nlp_cloud) | ✅ | ✅ | ✅ | ✅ | | |
| [aleph alpha](https://docs.litellm.ai/docs/providers/aleph_alpha) | ✅ | ✅ | ✅ | ✅ | | |
| [petals](https://docs.litellm.ai/docs/providers/petals) | ✅ | ✅ | ✅ | ✅ | | |
| [ollama](https://docs.litellm.ai/docs/providers/ollama) | ✅ | ✅ | ✅ | ✅ | ✅ | |
| [deepinfra](https://docs.litellm.ai/docs/providers/deepinfra) | ✅ | ✅ | ✅ | ✅ | | |
| [perplexity-ai](https://docs.litellm.ai/docs/providers/perplexity) | ✅ | ✅ | ✅ | ✅ | | |
| [Groq AI](https://docs.litellm.ai/docs/providers/groq) | ✅ | ✅ | ✅ | ✅ | | |
| [Deepseek](https://docs.litellm.ai/docs/providers/deepseek) | ✅ | ✅ | ✅ | ✅ | | |
| [anyscale](https://docs.litellm.ai/docs/providers/anyscale) | ✅ | ✅ | ✅ | ✅ | | |
| [IBM - watsonx.ai](https://docs.litellm.ai/docs/providers/watsonx) | ✅ | ✅ | ✅ | ✅ | ✅ | |
| [voyage ai](https://docs.litellm.ai/docs/providers/voyage) | | | | | ✅ | |
| [xinference [Xorbits Inference]](https://docs.litellm.ai/docs/providers/xinference) | | | | | ✅ | |
| [FriendliAI](https://docs.litellm.ai/docs/providers/friendliai) | ✅ | ✅ | ✅ | ✅ | | |
| [Galadriel](https://docs.litellm.ai/docs/providers/galadriel) | ✅ | ✅ | ✅ | ✅ | | |
To enable the user system, set `ENABLE_USER_SYSTEM=1` in your `.env` file.
[**Read the Docs**](https://docs.litellm.ai/docs/)
The sample structure is simple. It provide three basic endpoint:
1. `/auth/login` will redirect to your own login url. In the sample code, it's `/auth/your_login_page`.
2. `/auth/logout` will redirect to your own logout url. In the sample code, it's `/auth/your_login_page/logout`.
3. `/api/me` will return the userinfo: `{ username, display }`.
1. `username` will be used in db, used to distinguish between users.
2. `display` will be displayed in UI.
The only one thing that drive user system work is `req.session.user`. Once it's set, the client will be trusted. Set to `null` if logout.
Please refer to `/api/server/routes/authYoutLogin.js` file. It's very clear and simple to tell you how to implement your user system.
</details>
### Updating
- As the project is still a work-in-progress, you should pull the latest and run the steps over. Reset your browser cache/clear site data.
## Use Cases ##
- One stop shop for all conversational AIs, with the added bonus of searching past conversations.
- Using the official API, you'd have to generate 7.5 million words to expense the same cost as ChatGPT Plus ($20).
- ChatGPT/Google Bard/Bing AI conversations are lost in space or
cannot be searched past a certain timeframe.
- **Customize ChatGPT**
![use case example](./images/use_case3.png "Make a Custom GPT")
- **API is not as limited as ChatGPT Free (at [chat.openai.com](https://chat.openai.com/chat))**
![use case example](./images/use_case2.png "chat.openai.com is getting more limited by the day!")
- **ChatGPT Free is down.**
![use case example](./images/use_case.png "GPT is down! Plus is too expensive!")
## Origin ##
This project was originally created as a Minimum Viable Product (or MVP) for the [@HackReactor](https://github.com/hackreactor/) Bootcamp. It was built with OpenAI response streaming and most of the UI completed in under 20 hours. During the end of that time, I had most of the UI and basic functionality done. This was created without using any boilerplates or templates, including create-react-app and other toolchains. I didn't follow any 'un-official chatgpt' video tutorials, and simply referenced the official site for the UI. The purpose of the exercise was to learn setting up a full stack project from scratch. Please feel free to give feedback, suggestions, or fork the project for your own use.
## Caveats
### Regarding use of Official ChatGPT API
From [@waylaidwanderer](https://github.com/waylaidwanderer/node-chatgpt-api/blob/main/README.md#caveats):
Since `gpt-3.5-turbo` is ChatGPT's underlying model, I had to do my best to replicate the way the official ChatGPT website uses it.
This means my implementation or the underlying model may not behave exactly the same in some ways:
- Conversations are not tied to any user IDs, so if that's important to you, you should implement your own user ID system.
- ChatGPT's model parameters (temperature, frequency penalty, etc.) are unknown, so I set some defaults that I thought would be reasonable.
- Conversations are limited to roughly the last 3000 tokens, so earlier messages may be forgotten during longer conversations.
- This works in a similar way to ChatGPT, except I'm pretty sure they have some additional way of retrieving context from earlier messages when needed (which can probably be achieved with embeddings, but I consider that out-of-scope for now).
## Contributing
If you'd like to contribute, please create a pull request with a detailed description of your changes.
Interested in contributing? Contributions to LiteLLM Python SDK, Proxy Server, and contributing LLM integrations are both accepted and highly encouraged! [See our Contribution Guide for more details](https://docs.litellm.ai/docs/extras/contributing_code)
# Enterprise
For companies that need better security, user management and professional support
[Talk to founders](https://calendly.com/d/4mp-gd3-k5k/litellm-1-1-onboarding-chat)
This covers:
-**Features under the [LiteLLM Commercial License](https://docs.litellm.ai/docs/proxy/enterprise):**
-**Feature Prioritization**
-**Custom Integrations**
-**Professional Support - Dedicated discord + slack**
-**Custom SLAs**
-**Secure access with Single Sign-On**
# Code Quality / Linting
LiteLLM follows the [Google Python Style Guide](https://google.github.io/styleguide/pyguide.html).
We run:
- Ruff for [formatting and linting checks](https://github.com/BerriAI/litellm/blob/e19bb55e3b4c6a858b6e364302ebbf6633a51de5/.circleci/config.yml#L320)
- Mypy + Pyright for typing [1](https://github.com/BerriAI/litellm/blob/e19bb55e3b4c6a858b6e364302ebbf6633a51de5/.circleci/config.yml#L90), [2](https://github.com/BerriAI/litellm/blob/e19bb55e3b4c6a858b6e364302ebbf6633a51de5/.pre-commit-config.yaml#L4)
- Black for [formatting](https://github.com/BerriAI/litellm/blob/e19bb55e3b4c6a858b6e364302ebbf6633a51de5/.circleci/config.yml#L79)
- isort for [import sorting](https://github.com/BerriAI/litellm/blob/e19bb55e3b4c6a858b6e364302ebbf6633a51de5/.pre-commit-config.yaml#L10)
If you have suggestions on how to improve the code quality feel free to open an issue or a PR.
# Support / talk with founders
- [Schedule Demo 👋](https://calendly.com/d/4mp-gd3-k5k/berriai-1-1-onboarding-litellm-hosted-version)
- [Community Discord 💭](https://discord.gg/wuPM9dRgDw)
- Our numbers 📞 +1 (770) 8783-106 / +1 (412) 618-6238
- Our emails ✉️ ishaan@berri.ai / krrish@berri.ai
# Why did we build this
- **Need for simplicity**: Our code started to get extremely complicated managing & translating calls between Azure, OpenAI and Cohere.
# Contributors
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->
<a href="https://github.com/BerriAI/litellm/graphs/contributors">
<img src="https://contrib.rocks/image?repo=BerriAI/litellm" />
</a>
## Run in Developer mode
### Services
1. Setup .env file in root
2. Run dependant services `docker-compose up db prometheus`
### Backend
1. (In root) create virtual environment `python -m venv .venv`
2. Activate virtual environment `source .venv/bin/activate`
3. Install dependencies `pip install -e ".[all]"`
4. Start proxy backend `uvicorn litellm.proxy.proxy_server:app --host localhost --port 4000 --reload`
### Frontend
1. Navigate to `ui/litellm-dashboard`
2. Install dependencies `npm install`
3. Run `npm run dev` to start the dashboard
## License
This project is licensed under the MIT License.
+2
View File
@@ -0,0 +1,2 @@
/node_modules
.env
+29
View File
@@ -0,0 +1,29 @@
# Server configuration.
# The server will listen to localhost:3080 request by default. You can set the target ip as you want.
# If you want this server can be used outside your local machine, for example to share with other
# machine or expose this from a docker container, set HOST=0.0.0.0 or your external ip interface.
#
# Tips: HOST=0.0.0.0 means listening on all interface. It's not a real ip. Use localhost:port rather
# than 0.0.0.0:port to open it.
HOST=localhost
PORT=3080
NODE_ENV=development
# Change this to proxy any API request. It's useful if your machine have difficulty calling the original API server.
# PROXY="http://YOUR_PROXY_SERVER"
# Change this to your MongoDB URI if different and I recommend appending chatgpt-clone
MONGO_URI="mongodb://127.0.0.1:27017/chatgpt-clone"
# API key configuration.
# Leave blank if you don't want them.
OPENAI_KEY=
CHATGPT_TOKEN=
BING_TOKEN=
# User System
# global enable/disable the sample user system.
# this is not a ready to use user system.
# dont't use it, unless you can write your own code.
ENABLE_USER_SYSTEM=
+16
View File
@@ -0,0 +1,16 @@
FROM node:19-alpine
WORKDIR /api
# copy package.json into the container at /api
COPY package*.json /api/
# install dependencies
RUN npm install
# Copy the current directory contents into the container at /api
COPY . /api/
# Make port 3080 available to the world outside this container
EXPOSE 3080
# Expose the server to 0.0.0.0
ENV HOST=0.0.0.0
# Run the app when the container launches
CMD ["npm", "start"]
# docker build -t node-api .
+55
View File
@@ -0,0 +1,55 @@
require('dotenv').config();
const { KeyvFile } = require('keyv-file');
const askBing = async ({ text, onProgress, convo }) => {
const { BingAIClient } = (await import('@waylaidwanderer/chatgpt-api'));
const bingAIClient = new BingAIClient({
// "_U" cookie from bing.com
userToken: process.env.BING_TOKEN,
// If the above doesn't work, provide all your cookies as a string instead
// cookies: '',
debug: false,
cache: { store: new KeyvFile({ filename: './data/cache.json' }) },
proxy: process.env.PROXY || null,
});
let options = { onProgress };
if (convo) {
options = { ...options, ...convo };
}
if (options?.jailbreakConversationId == 'false')
options.jailbreakConversationId = false
const res = await bingAIClient.sendMessage(text, options);
return res;
// Example response for reference
// {
// conversationSignature: 'wwZ2GC/qRgEqP3VSNIhbPGwtno5RcuBhzZFASOM+Sxg=',
// conversationId: '51D|BingProd|026D3A4017554DE6C446798144B6337F4D47D5B76E62A31F31D0B1D0A95ED868',
// clientId: '914800201536527',
// invocationId: 1,
// conversationExpiryTime: '2023-02-15T21:48:46.2892088Z',
// response: 'Hello, this is Bing. Nice to meet you. 😊',
// details: {
// text: 'Hello, this is Bing. Nice to meet you. 😊',
// author: 'bot',
// createdAt: '2023-02-15T15:48:43.0631898+00:00',
// timestamp: '2023-02-15T15:48:43.0631898+00:00',
// messageId: '9d0c9a80-91b1-49ab-b9b1-b457dc3fe247',
// requestId: '5b252ef8-4f09-4c08-b6f5-4499d2e12fba',
// offense: 'None',
// adaptiveCards: [ [Object] ],
// sourceAttributions: [],
// feedback: { tag: null, updatedOn: null, type: 'None' },
// contentOrigin: 'DeepLeo',
// privacy: null,
// suggestedResponses: [ [Object], [Object], [Object] ]
// }
// }
};
module.exports = { askBing };
+31
View File
@@ -0,0 +1,31 @@
require('dotenv').config();
const { KeyvFile } = require('keyv-file');
const clientOptions = {
// Warning: This will expose your access token to a third party. Consider the risks before using this.
reverseProxyUrl: 'https://bypass.duti.tech/api/conversation',
// Access token from https://chat.openai.com/api/auth/session
accessToken: process.env.CHATGPT_TOKEN,
// debug: true
proxy: process.env.PROXY || null,
};
const browserClient = async ({ text, onProgress, convo, abortController }) => {
const { ChatGPTBrowserClient } = await import('@waylaidwanderer/chatgpt-api');
const store = {
store: new KeyvFile({ filename: './data/cache.json' })
};
const client = new ChatGPTBrowserClient(clientOptions, store);
let options = { onProgress, abortController };
if (!!convo.parentMessageId && !!convo.conversationId) {
options = { ...options, ...convo };
}
const res = await client.sendMessage(text, options);
return res;
};
module.exports = { browserClient };
+29
View File
@@ -0,0 +1,29 @@
require('dotenv').config();
const { KeyvFile } = require('keyv-file');
const clientOptions = {
modelOptions: {
model: 'gpt-3.5-turbo'
},
proxy: process.env.PROXY || null,
debug: false
};
const askClient = async ({ text, onProgress, convo, abortController }) => {
const ChatGPTClient = (await import('@waylaidwanderer/chatgpt-api')).default;
const store = {
store: new KeyvFile({ filename: './data/cache.json' })
};
const client = new ChatGPTClient(process.env.OPENAI_KEY, clientOptions, store);
let options = { onProgress, abortController };
if (!!convo.parentMessageId && !!convo.conversationId) {
options = { ...options, ...convo };
}
const res = await client.sendMessage(text, options);
return res;
};
module.exports = { askClient };
+35
View File
@@ -0,0 +1,35 @@
require('dotenv').config();
const { KeyvFile } = require('keyv-file');
const clientOptions = {
modelOptions: {
model: 'gpt-3.5-turbo'
},
proxy: process.env.PROXY || null,
debug: false
};
const customClient = async ({ text, onProgress, convo, promptPrefix, chatGptLabel, abortController }) => {
const ChatGPTClient = (await import('@waylaidwanderer/chatgpt-api')).default;
const store = {
store: new KeyvFile({ filename: './data/cache.json' })
};
clientOptions.chatGptLabel = chatGptLabel;
if (promptPrefix?.length > 0) {
clientOptions.promptPrefix = promptPrefix;
}
const client = new ChatGPTClient(process.env.OPENAI_KEY, clientOptions, store);
let options = { onProgress, abortController };
if (!!convo.parentMessageId && !!convo.conversationId) {
options = { ...options, ...convo };
}
const res = await client.sendMessage(text, options);
return res;
};
module.exports = customClient;
+29
View File
@@ -0,0 +1,29 @@
const citationRegex = /\[\^\d+?\^]/g;
const citeText = (res, noLinks = false) => {
let result = res.text || res;
const citations = Array.from(new Set(result.match(citationRegex)));
if (citations?.length === 0) return result;
if (noLinks) {
citations.forEach((citation) => {
const digit = citation.match(/\d+?/g)[0];
result = result.replaceAll(citation, `<sup>[${digit}](#) </sup>`);
});
return result;
}
let sources = res.details.sourceAttributions;
if (sources?.length === 0) return result;
sources = sources.map((source) => source.seeMoreUrl);
citations.forEach((citation) => {
const digit = citation.match(/\d+?/g)[0];
result = result.replaceAll(citation, `<sup>[${digit}](${sources[digit - 1]}) </sup>`);
});
return result;
};
module.exports = citeText;
+52
View File
@@ -0,0 +1,52 @@
const { ModelOperations } = require('@vscode/vscode-languagedetection');
const languages = require('../utils/languages.js');
const codeRegex = /(```[\s\S]*?```)/g;
// const languageMatch = /```(\w+)/;
const replaceRegex = /```\w+\n/g;
const detectCode = async (input) => {
try {
let text = input;
if (!text.match(codeRegex)) {
return text;
}
const langMatches = text.match(replaceRegex);
if (langMatches?.length > 0) {
langMatches.forEach(match => {
let lang = match.split('```')[1].trim();
if (languages.has(lang)) {
return;
}
console.log('[detectCode.js] replacing', match, 'with', '```shell');
text = text.replace(match, '```shell\n');
});
return text;
}
const modelOperations = new ModelOperations();
const regexSplit = (await import('./regexSplit.mjs')).default;
const parts = regexSplit(text, codeRegex);
const output = parts.map(async (part) => {
if (part.match(codeRegex)) {
const code = part.slice(3, -3);
let lang = (await modelOperations.runModel(code))[0].languageId;
return part.replace(/^```/, `\`\`\`${languages.has(lang) ? lang : 'shell'}`);
} else {
return part;
}
});
return (await Promise.all(output)).join('');
} catch (e) {
console.log('Error in detectCode function\n', e);
return input;
}
};
module.exports = detectCode;
+13
View File
@@ -0,0 +1,13 @@
// const regex = / \[\d+\..*?\]\(.*?\)/g;
const regex = / \[.*?]\(.*?\)/g;
const getCitations = (res) => {
const textBlocks = res.details.adaptiveCards[0].body;
if (!textBlocks) return '';
let links = textBlocks[textBlocks.length - 1]?.text.match(regex);
if (links?.length === 0 || !links) return '';
links = links.map((link) => link.trim());
return links.join('\n');
};
module.exports = getCitations;
+21
View File
@@ -0,0 +1,21 @@
const { askClient } = require('./chatgpt-client');
const { browserClient } = require('./chatgpt-browser');
const customClient = require('./chatgpt-custom');
const { askBing } = require('./bingai');
const { askSydney } = require('./sydney');
const titleConvo = require('./titleConvo');
const getCitations = require('./getCitations');
const citeText = require('./citeText');
const detectCode = require('./detectCode');
module.exports = {
askClient,
browserClient,
customClient,
askBing,
askSydney,
titleConvo,
getCitations,
citeText,
detectCode
};
+46
View File
@@ -0,0 +1,46 @@
const primaryRegex = /```([^`\n]*?)\n([\s\S]*?)\n```/g;
const secondaryRegex = /```([^`\n]*?)\n?([\s\S]*?)\n?```/g;
const unenclosedCodeTest = (text) => {
let workingText = text;
// if (workingText.startsWith('<') || (!workingText.startsWith('`') && workingText.match(/```/g)?.length === 1)) {
// workingText = `\`\`\`${workingText}`
// }
return workingText.trim();
};
export default function regexSplit(string) {
let matches = [...string.matchAll(primaryRegex)];
if (!matches[0]) {
matches = [...string.matchAll(secondaryRegex)];
}
const output = [matches[0].input.slice(0, matches[0].index)];
// console.log(matches);
for (let i = 0; i < matches.length; i++) {
const [fullMatch, language, code] = matches[i];
// const formattedCode = code.replace(/`+/g, '\\`');
output.push(`\`\`\`${language}\n${code}\n\`\`\``);
if (i < matches.length - 1) {
let nextText = string.slice(matches[i].index + fullMatch.length, matches[i + 1].index);
nextText = unenclosedCodeTest(nextText);
output.push(nextText);
} else {
const lastMatch = matches[matches.length - 1][0];
// console.log(lastMatch);
// console.log(matches[0].input.split(lastMatch));
let rest = matches[0].input.split(lastMatch)[1]
if (rest) {
rest = unenclosedCodeTest(rest);
output.push(rest);
}
}
}
return output;
}
+36
View File
@@ -0,0 +1,36 @@
require('dotenv').config();
const { KeyvFile } = require('keyv-file');
const askSydney = async ({ text, onProgress, convo }) => {
const { BingAIClient } = (await import('@waylaidwanderer/chatgpt-api'));
const sydneyClient = new BingAIClient({
// "_U" cookie from bing.com
userToken: process.env.BING_TOKEN,
// If the above doesn't work, provide all your cookies as a string instead
// cookies: '',
debug: false,
cache: { store: new KeyvFile({ filename: './data/cache.json' }) }
});
let options = {
jailbreakConversationId: true,
onProgress,
};
if (convo.jailbreakConversationId) {
options = { ...options, jailbreakConversationId: convo.jailbreakConversationId, parentMessageId: convo.parentMessageId };
}
console.log('sydney options', options);
const res = await sydneyClient.sendMessage(text, options
);
return res;
// for reference:
// https://github.com/waylaidwanderer/node-chatgpt-api/blob/main/demos/use-bing-client.js
};
module.exports = { askSydney };
+62
View File
@@ -0,0 +1,62 @@
const { Configuration, OpenAIApi } = require('openai');
const _ = require('lodash');
const proxyEnvToAxiosProxy = (proxyString) => {
if (!proxyString) return null;
const regex = /^([^:]+):\/\/(?:([^:@]*):?([^:@]*)@)?([^:]+)(?::(\d+))?/;
const [, protocol, username, password, host, port] = proxyString.match(regex);
const proxyConfig = {
protocol,
host,
port: port ? parseInt(port) : undefined,
auth: username && password ? { username, password } : undefined
};
return proxyConfig;
};
const titleConvo = async ({ model, text, response }) => {
let title = 'New Chat';
try {
const configuration = new Configuration({
apiKey: process.env.OPENAI_KEY
});
const openai = new OpenAIApi(configuration);
const completion = await openai.createChatCompletion(
{
model: 'gpt-3.5-turbo',
messages: [
{
role: 'system',
content:
'You are a title-generator with one job: giving a conversation, detect the language and titling the conversation provided by a user in title case, using the same language.'
},
{
role: 'user',
content: `In 5 words or less, summarize the conversation below with a title in title case using the language the user writes in. Don't refer to the participants of the conversation nor the language. Do not include punctuation or quotation marks. Your response should be in title case, exclusively containing the title. Conversation:\n\nUser: "${text}"\n\n${model}: "${JSON.stringify(
response?.text
)}"\n\nTitle: `
}
],
temperature: 0,
presence_penalty: 0,
frequency_penalty: 0,
},
{ proxy: proxyEnvToAxiosProxy(process.env.PROXY || null) }
);
//eslint-disable-next-line
title = completion.data.choices[0].message.content.replace(/["\.]/g, '');
} catch (e) {
console.error(e);
console.log('There was an issue generating title, see error above');
}
console.log('CONVERSATION TITLE', title);
return title;
};
const throttledTitleConvo = _.throttle(titleConvo, 1000);
module.exports = throttledTitleConvo;
+194
View File
@@ -0,0 +1,194 @@
const mongoose = require('mongoose');
const crypto = require('crypto');
const { getMessages, deleteMessages } = require('./Message');
const convoSchema = mongoose.Schema(
{
conversationId: {
type: String,
unique: true,
required: true
},
parentMessageId: {
type: String,
required: true
},
title: {
type: String,
default: 'New Chat'
},
jailbreakConversationId: {
type: String,
default: null
},
conversationSignature: {
type: String,
default: null
},
clientId: {
type: String
},
invocationId: {
type: String
},
chatGptLabel: {
type: String,
default: null
},
promptPrefix: {
type: String,
default: null
},
model: {
type: String,
required: true
},
user: {
type: String
},
suggestions: [{ type: String }],
messages: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Message' }]
},
{ timestamps: true }
);
const Conversation =
mongoose.models.Conversation || mongoose.model('Conversation', convoSchema);
const getConvo = async (user, conversationId) => {
try {
return await Conversation.findOne({ user, conversationId }).exec();
} catch (error) {
console.log(error);
return { message: 'Error getting single conversation' };
}
};
module.exports = {
saveConvo: async (user, { conversationId, newConversationId, title, ...convo }) => {
try {
const messages = await getMessages({ conversationId });
const update = { ...convo, messages };
if (title) {
update.title = title;
update.user = user
}
if (newConversationId) {
update.conversationId = newConversationId;
}
if (!update.jailbreakConversationId) {
update.jailbreakConversationId = null;
}
if (update.model !== 'chatgptCustom' && update.chatGptLabel && update.promptPrefix) {
console.log('Validation error: resetting chatgptCustom fields', update);
update.chatGptLabel = null;
update.promptPrefix = null;
}
return await Conversation.findOneAndUpdate(
{ conversationId: conversationId, user },
{ $set: update },
{ new: true, upsert: true }
).exec();
} catch (error) {
console.log(error);
return { message: 'Error saving conversation' };
}
},
updateConvo: async (user, { conversationId, ...update }) => {
try {
return await Conversation.findOneAndUpdate({ conversationId: conversationId, user }, update, {
new: true
}).exec();
} catch (error) {
console.log(error);
return { message: 'Error updating conversation' };
}
},
getConvosByPage: async (user, pageNumber = 1, pageSize = 12) => {
try {
const totalConvos = (await Conversation.countDocuments({ user })) || 1;
const totalPages = Math.ceil(totalConvos / pageSize);
const convos = await Conversation.find({ user })
.sort({ createdAt: -1, created: -1 })
.skip((pageNumber - 1) * pageSize)
.limit(pageSize)
.exec();
return { conversations: convos, pages: totalPages, pageNumber, pageSize };
} catch (error) {
console.log(error);
return { message: 'Error getting conversations' };
}
},
getConvo,
getConvoTitle: async (user, conversationId) => {
try {
const convo = await getConvo(user, conversationId);
return convo.title;
} catch (error) {
console.log(error);
return { message: 'Error getting conversation title' };
}
},
deleteConvos: async (user, filter) => {
let deleteCount = await Conversation.deleteMany({...filter, user}).exec();
deleteCount.messages = await deleteMessages(filter);
return deleteCount;
},
migrateDb: async () => {
try {
const conversations = await Conversation.find({ model: null }).exec();
if (!conversations || conversations.length === 0)
return { message: '[Migrate] No conversations to migrate' };
for (let convo of conversations) {
const messages = await getMessages({
conversationId: convo.conversationId,
messageId: { $exists: false }
});
let model;
let oldId;
const promises = [];
messages.forEach((message, i) => {
const msgObj = message.toObject();
const newId = msgObj.id;
if (i === 0) {
message.parentMessageId = '00000000-0000-0000-0000-000000000000';
} else {
message.parentMessageId = oldId;
}
oldId = newId;
message.messageId = newId;
if (message.sender.toLowerCase() !== 'user' && !model) {
model = message.sender.toLowerCase();
}
if (message.sender.toLowerCase() === 'user') {
message.isCreatedByUser = true;
}
promises.push(message.save());
});
await Promise.all(promises);
await Conversation.findOneAndUpdate(
{ conversationId: convo.conversationId },
{ model },
{ new: true }
).exec();
}
try {
await mongoose.connection.db.collection('messages').dropIndex('id_1');
} catch (error) {
console.log("[Migrate] Index doesn't exist or already dropped");
}
} catch (error) {
console.log(error);
return { message: '[Migrate] Error migrating conversations' };
}
}
};
+82
View File
@@ -0,0 +1,82 @@
const mongoose = require('mongoose');
const customGptSchema = mongoose.Schema({
chatGptLabel: {
type: String,
required: true
},
promptPrefix: {
type: String
},
value: {
type: String,
required: true
},
user: {
type: String
},
}, { timestamps: true });
const CustomGpt = mongoose.models.CustomGpt || mongoose.model('CustomGpt', customGptSchema);
const createCustomGpt = async ({ chatGptLabel, promptPrefix, value, user }) => {
try {
await CustomGpt.create({
chatGptLabel,
promptPrefix,
value,
user
});
return { chatGptLabel, promptPrefix, value };
} catch (error) {
console.error(error);
return { customGpt: 'Error saving customGpt' };
}
};
module.exports = {
getCustomGpts: async (user, filter) => {
try {
return await CustomGpt.find({ ...filter, user }).exec();
} catch (error) {
console.error(error);
return { customGpt: 'Error getting customGpts' };
}
},
updateCustomGpt: async (user, { value, ...update }) => {
try {
const customGpt = await CustomGpt.findOne({ value, user }).exec();
if (!customGpt) {
return await createCustomGpt({ value, ...update, user });
} else {
return await CustomGpt.findOneAndUpdate({ value, user }, update, {
new: true,
upsert: true
}).exec();
}
} catch (error) {
console.log(error);
return { message: 'Error updating customGpt' };
}
},
updateByLabel: async (user, { prevLabel, ...update }) => {
try {
return await CustomGpt.findOneAndUpdate({ chatGptLabel: prevLabel, user }, update, {
new: true,
upsert: true
}).exec();
} catch (error) {
console.log(error);
return { message: 'Error updating customGpt' };
}
},
deleteCustomGpts: async (user, filter) => {
try {
return await CustomGpt.deleteMany({ ...filter, user }).exec();
} catch (error) {
console.error(error);
return { customGpt: 'Error deleting customGpts' };
}
}
};
+92
View File
@@ -0,0 +1,92 @@
const mongoose = require('mongoose');
const messageSchema = mongoose.Schema({
messageId: {
type: String,
unique: true,
required: true
},
conversationId: {
type: String,
required: true
},
conversationSignature: {
type: String,
// required: true
},
clientId: {
type: String,
},
invocationId: {
type: String,
},
parentMessageId: {
type: String,
// required: true
},
sender: {
type: String,
required: true
},
text: {
type: String,
required: true
},
isCreatedByUser: {
type: Boolean,
required: true,
default: false
},
error: {
type: Boolean,
default: false
},
}, { timestamps: true });
const Message = mongoose.models.Message || mongoose.model('Message', messageSchema);
module.exports = {
saveMessage: async ({ messageId, conversationId, parentMessageId, sender, text, isCreatedByUser=false, error }) => {
try {
await Message.findOneAndUpdate({ messageId }, {
conversationId,
parentMessageId,
sender,
text,
isCreatedByUser,
error
}, { upsert: true, new: true });
return { messageId, conversationId, parentMessageId, sender, text, isCreatedByUser };
} catch (error) {
console.error(error);
return { message: 'Error saving message' };
}
},
deleteMessagesSince: async ({ messageId, conversationId }) => {
try {
const message = await Message.findOne({ messageId }).exec()
if (message)
return await Message.find({ conversationId }).deleteMany({ createdAt: { $gt: message.createdAt } }).exec();
} catch (error) {
console.error(error);
return { message: 'Error deleting messages' };
}
},
getMessages: async (filter) => {
try {
return await Message.find(filter).sort({createdAt: 1}).exec()
} catch (error) {
console.error(error);
return { message: 'Error getting messages' };
}
},
deleteMessages: async (filter) => {
try {
return await Message.deleteMany(filter).exec()
} catch (error) {
console.error(error);
return { message: 'Error deleting messages' };
}
}
}
+48
View File
@@ -0,0 +1,48 @@
const mongoose = require('mongoose');
const promptSchema = mongoose.Schema({
title: {
type: String,
required: true
},
prompt: {
type: String,
required: true
},
category: {
type: String,
},
}, { timestamps: true });
const Prompt = mongoose.models.Prompt || mongoose.model('Prompt', promptSchema);
module.exports = {
savePrompt: async ({ title, prompt }) => {
try {
await Prompt.create({
title,
prompt
});
return { title, prompt };
} catch (error) {
console.error(error);
return { prompt: 'Error saving prompt' };
}
},
getPrompts: async (filter) => {
try {
return await Prompt.find(filter).exec()
} catch (error) {
console.error(error);
return { prompt: 'Error getting prompts' };
}
},
deletePrompts: async (filter) => {
try {
return await Prompt.deleteMany(filter).exec()
} catch (error) {
console.error(error);
return { prompt: 'Error deleting prompts' };
}
}
}
+44
View File
@@ -0,0 +1,44 @@
require('dotenv').config();
const mongoose = require('mongoose');
const MONGO_URI = process.env.MONGO_URI;
if (!MONGO_URI) {
throw new Error('Please define the MONGO_URI environment variable inside .env.local');
}
/**
* Global is used here to maintain a cached connection across hot reloads
* in development. This prevents connections growing exponentially
* during API Route usage.
*/
let cached = global.mongoose;
if (!cached) {
cached = global.mongoose = { conn: null, promise: null };
}
async function dbConnect() {
if (cached.conn) {
return cached.conn;
}
if (!cached.promise) {
const opts = {
useNewUrlParser: true,
useUnifiedTopology: true,
bufferCommands: false
// bufferMaxEntries: 0,
// useFindAndModify: true,
// useCreateIndex: true
};
mongoose.set('strictQuery', true);
cached.promise = mongoose.connect(MONGO_URI, opts).then((mongoose) => {
return mongoose;
});
}
cached.conn = await cached.promise;
return cached.conn;
}
module.exports = dbConnect;
+17
View File
@@ -0,0 +1,17 @@
const { saveMessage, deleteMessagesSince, deleteMessages } = require('./Message');
const { getCustomGpts, updateCustomGpt, updateByLabel, deleteCustomGpts } = require('./CustomGpt');
const { getConvoTitle, getConvo, saveConvo, migrateDb } = require('./Conversation');
module.exports = {
saveMessage,
deleteMessagesSince,
deleteMessages,
getConvoTitle,
getConvo,
saveConvo,
migrateDb,
getCustomGpts,
updateCustomGpt,
updateByLabel,
deleteCustomGpts
};
+6
View File
@@ -0,0 +1,6 @@
{
"ignore": [
"api/data/",
"data"
]
}
+8319
View File
File diff suppressed because it is too large Load Diff
+42
View File
@@ -0,0 +1,42 @@
{
"name": "chatgpt-clone",
"version": "1.0.0",
"description": "",
"main": "server/index.js",
"scripts": {
"start": "node server/index.js",
"server-dev": "npx nodemon server/index.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/danny-avila/chatgpt-clone.git"
},
"keywords": [],
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/danny-avila/chatgpt-clone/issues"
},
"homepage": "https://github.com/danny-avila/chatgpt-clone#readme",
"dependencies": {
"@keyv/mongo": "^2.1.8",
"@vscode/vscode-languagedetection": "^1.0.22",
"@waylaidwanderer/chatgpt-api": "^1.28.2",
"axios": "^1.3.4",
"cors": "^2.8.5",
"crypto": "^1.0.1",
"dotenv": "^16.0.3",
"express": "^4.18.2",
"express-session": "^1.17.3",
"keyv": "^4.5.2",
"keyv-file": "^0.2.0",
"lodash": "^4.17.21",
"mongoose": "^6.9.0",
"openai": "^3.1.0",
"sanitize-html": "^2.10.0"
},
"devDependencies": {
"nodemon": "^2.0.20",
"path": "^0.12.7"
}
}
+67
View File
@@ -0,0 +1,67 @@
const express = require('express');
const session = require('express-session')
const dbConnect = require('../models/dbConnect');
const { migrateDb } = require('../models');
const path = require('path');
const cors = require('cors');
const routes = require('./routes');
const app = express();
const port = process.env.PORT || 3080;
const host = process.env.HOST || 'localhost'
const userSystemEnabled = process.env.ENABLE_USER_SYSTEM || false
const projectPath = path.join(__dirname, '..', '..', 'client');
dbConnect().then(() => {
console.log('Connected to MongoDB');
migrateDb();
});
app.use(cors());
app.use(express.json());
app.use(express.static(path.join(projectPath, 'public')));
app.set('trust proxy', 1) // trust first proxy
app.use(session({
secret: 'chatgpt-clone-random-secrect',
resave: false,
saveUninitialized: true,
}))
app.get('/', routes.authenticatedOrRedirect, function (req, res) {
console.log(path.join(projectPath, 'public', 'index.html'));
res.sendFile(path.join(projectPath, 'public', 'index.html'));
});
app.get('/api/me', function (req, res) {
if (userSystemEnabled) {
const user = req?.session?.user
if (user)
res.send(JSON.stringify({username: user?.username, display: user?.display}));
else
res.send(JSON.stringify(null));
} else {
res.send(JSON.stringify({username: 'anonymous_user', display: 'Anonymous User'}));
}
});
app.use('/api/ask', routes.authenticatedOr401, routes.ask);
app.use('/api/messages', routes.authenticatedOr401, routes.messages);
app.use('/api/convos', routes.authenticatedOr401, routes.convos);
app.use('/api/customGpts', routes.authenticatedOr401, routes.customGpts);
app.use('/api/prompts', routes.authenticatedOr401, routes.prompts);
app.use('/auth', routes.auth);
app.get('/api/models', function (req, res) {
const hasOpenAI = !!process.env.OPENAI_KEY;
const hasChatGpt = !!process.env.CHATGPT_TOKEN;
const hasBing = !!process.env.BING_TOKEN;
res.send(JSON.stringify({ hasOpenAI, hasChatGpt, hasBing }));
});
app.listen(port, host, () => {
if (host=='0.0.0.0')
console.log(`Server listening on all interface at port ${port}. Use http://localhost:${port} to access it`);
else
console.log(`Server listening at http://${host=='0.0.0.0'?'localhost':host}:${port}`);
});
+195
View File
@@ -0,0 +1,195 @@
const express = require('express');
const crypto = require('crypto');
const router = express.Router();
const askBing = require('./askBing');
const askSydney = require('./askSydney');
const { titleConvo, askClient, browserClient, customClient } = require('../../app/');
const { getConvo, saveMessage, getConvoTitle, saveConvo } = require('../../models');
const { handleError, sendMessage, createOnProgress, handleText } = require('./handlers');
const { getMessages } = require('../../models/Message');
router.use('/bing', askBing);
router.use('/sydney', askSydney);
router.post('/', async (req, res) => {
let { model, text, overrideParentMessageId=null, parentMessageId, conversationId: oldConversationId, ...convo } = req.body;
if (text.length === 0) {
return handleError(res, { text: 'Prompt empty or too short' });
}
const conversationId = oldConversationId || crypto.randomUUID();
const userMessageId = crypto.randomUUID();
const userParentMessageId = parentMessageId || '00000000-0000-0000-0000-000000000000';
let userMessage = {
messageId: userMessageId,
sender: 'User',
text,
parentMessageId: userParentMessageId,
conversationId,
isCreatedByUser: true
};
console.log('ask log', {
model,
...userMessage,
...convo
});
if (!overrideParentMessageId) {
await saveMessage(userMessage);
await saveConvo(req?.session?.user?.username, { ...userMessage, model, ...convo });
}
return await ask({
userMessage,
model,
convo,
preSendRequest: true,
overrideParentMessageId,
req,
res
});
});
const ask = async ({
userMessage,
overrideParentMessageId = null,
model,
convo,
preSendRequest = true,
req,
res
}) => {
let {
text,
parentMessageId: userParentMessageId,
conversationId,
messageId: userMessageId
} = userMessage;
let client;
if (model === 'chatgpt') {
client = askClient;
} else if (model === 'chatgptCustom') {
client = customClient;
} else {
client = browserClient;
}
res.writeHead(200, {
Connection: 'keep-alive',
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache, no-transform',
'Access-Control-Allow-Origin': '*',
'X-Accel-Buffering': 'no'
});
if (preSendRequest) sendMessage(res, { message: userMessage, created: true });
try {
const progressCallback = createOnProgress();
const abortController = new AbortController();
res.on('close', () => {
console.log('The client has disconnected.');
// 执行其他操作
abortController.abort();
})
let gptResponse = await client({
text,
onProgress: progressCallback.call(null, model, { res, text }),
convo: {
parentMessageId: userParentMessageId,
conversationId,
...convo
},
...convo,
abortController
});
console.log('CLIENT RESPONSE', gptResponse);
if (!gptResponse.parentMessageId) {
gptResponse.text = gptResponse.response;
// gptResponse.id = gptResponse.messageId;
gptResponse.parentMessageId = overrideParentMessageId || userMessageId;
// userMessage.conversationId = conversationId
// ? conversationId
// : gptResponse.conversationId;
// await saveMessage(userMessage);
delete gptResponse.response;
}
if (
(gptResponse.text.includes('2023') && !gptResponse.text.trim().includes(' ')) ||
gptResponse.text.toLowerCase().includes('no response') ||
gptResponse.text.toLowerCase().includes('no answer')
) {
await saveMessage({
messageId: crypto.randomUUID(),
sender: model,
conversationId,
parentMessageId: overrideParentMessageId || userMessageId,
error: true,
text: 'Prompt empty or too short'
});
return handleError(res, { text: 'Prompt empty or too short' });
}
gptResponse.sender = model === 'chatgptCustom' ? convo.chatGptLabel : model;
gptResponse.model = model;
// gptResponse.final = true;
gptResponse.text = await handleText(gptResponse);
if (convo.chatGptLabel?.length > 0 && model === 'chatgptCustom') {
gptResponse.chatGptLabel = convo.chatGptLabel;
}
if (convo.promptPrefix?.length > 0 && model === 'chatgptCustom') {
gptResponse.promptPrefix = convo.promptPrefix;
}
// override the parentMessageId, for the regeneration.
gptResponse.parentMessageId = overrideParentMessageId || userMessageId;
await saveMessage(gptResponse);
await saveConvo(req?.session?.user?.username, gptResponse);
sendMessage(res, {
title: await getConvoTitle(req?.session?.user?.username, conversationId),
final: true,
requestMessage: userMessage,
responseMessage: gptResponse
});
res.end();
if (userParentMessageId == '00000000-0000-0000-0000-000000000000') {
const title = await titleConvo({ model, text, response: gptResponse });
await saveConvo(
req?.session?.user?.username,
{
conversationId,
title
}
);
}
} catch (error) {
console.log(error);
// await deleteMessages({ messageId: userMessageId });
const errorMessage = {
messageId: crypto.randomUUID(),
sender: model,
conversationId,
parentMessageId: overrideParentMessageId || userMessageId,
error: true,
text: error.message
};
await saveMessage(errorMessage);
handleError(res, errorMessage);
}
};
module.exports = router;
+186
View File
@@ -0,0 +1,186 @@
const express = require('express');
const crypto = require('crypto');
const router = express.Router();
const { titleConvo, askBing } = require('../../app/');
const { saveMessage, getConvoTitle, saveConvo } = require('../../models');
const { handleError, sendMessage, createOnProgress, handleText } = require('./handlers');
router.post('/', async (req, res) => {
const {
model,
text,
overrideParentMessageId=null,
parentMessageId,
conversationId: oldConversationId,
...convo
} = req.body;
if (text.length === 0) {
return handleError(res, { text: 'Prompt empty or too short' });
}
const conversationId = oldConversationId || crypto.randomUUID();
const isNewConversation = !oldConversationId;
const userMessageId = crypto.randomUUID();
const userParentMessageId = parentMessageId || '00000000-0000-0000-0000-000000000000';
let userMessage = {
messageId: userMessageId,
sender: 'User',
text,
parentMessageId: userParentMessageId,
conversationId,
isCreatedByUser: true
};
console.log('ask log', {
model,
...userMessage,
...convo
});
if (!overrideParentMessageId) {
await saveMessage(userMessage);
await saveConvo(req?.session?.user?.username, { ...userMessage, model, ...convo });
}
return await ask({
isNewConversation,
userMessage,
model,
convo,
preSendRequest: true,
overrideParentMessageId,
req,
res
});
});
const ask = async ({
isNewConversation,
overrideParentMessageId = null,
userMessage,
model,
convo,
preSendRequest = true,
req,
res
}) => {
let {
text,
parentMessageId: userParentMessageId,
conversationId,
messageId: userMessageId
} = userMessage;
res.writeHead(200, {
Connection: 'keep-alive',
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache, no-transform',
'Access-Control-Allow-Origin': '*',
'X-Accel-Buffering': 'no'
});
if (preSendRequest) sendMessage(res, { message: userMessage, created: true });
try {
const progressCallback = createOnProgress();
const abortController = new AbortController();
res.on('close', () => {
console.log('The client has disconnected.');
// 执行其他操作
abortController.abort();
})
let response = await askBing({
text,
onProgress: progressCallback.call(null, model, {
res,
text,
parentMessageId: overrideParentMessageId || userMessageId
}),
convo: {
...convo,
parentMessageId: userParentMessageId,
conversationId
},
abortController
});
console.log('BING RESPONSE', response);
// console.dir(response, { depth: null });
userMessage.conversationSignature =
convo.conversationSignature || response.conversationSignature;
userMessage.conversationId = response.conversationId || conversationId;
userMessage.invocationId = response.invocationId;
if (!overrideParentMessageId)
await saveMessage(userMessage);
// Bing API will not use our conversationId at the first time,
// so change the placeholder conversationId to the real one.
// Attition: the api will also create new conversationId while using invalid userMessage.parentMessageId,
// but in this situation, don't change the conversationId, but create new convo.
if (conversationId != userMessage.conversationId && isNewConversation)
await saveConvo(
req?.session?.user?.username,
{
conversationId: conversationId,
newConversationId: userMessage.conversationId
}
);
conversationId = userMessage.conversationId;
response.text = response.response || response.details.spokenText || '**Bing refused to answer.**';
// delete response.response;
// response.id = response.details.messageId;
response.suggestions =
response.details.suggestedResponses &&
response.details.suggestedResponses.map((s) => s.text);
response.sender = model;
// response.final = true;
// override the parentMessageId, for the regeneration.
response.parentMessageId =
overrideParentMessageId || response.parentMessageId || userMessageId;
response.text = await handleText(response, true);
await saveMessage(response);
await saveConvo(req?.session?.user?.username, { ...response, model, chatGptLabel: null, promptPrefix: null, ...convo });
sendMessage(res, {
title: await getConvoTitle(req?.session?.user?.username, conversationId),
final: true,
requestMessage: userMessage,
responseMessage: response
});
res.end();
if (userParentMessageId == '00000000-0000-0000-0000-000000000000') {
const title = await titleConvo({ model, text, response });
await saveConvo(
req?.session?.user?.username,
{
conversationId,
title
}
);
}
} catch (error) {
console.log(error);
// await deleteMessages({ messageId: userMessageId });
const errorMessage = {
messageId: crypto.randomUUID(),
sender: model,
conversationId,
parentMessageId: overrideParentMessageId || userMessageId,
error: true,
text: error.message
};
await saveMessage(errorMessage);
handleError(res, errorMessage);
}
};
module.exports = router;
+199
View File
@@ -0,0 +1,199 @@
const express = require('express');
const crypto = require('crypto');
const router = express.Router();
const { titleConvo, askSydney } = require('../../app/');
const { saveMessage, saveConvo, getConvoTitle } = require('../../models');
const { handleError, sendMessage, createOnProgress, handleText } = require('./handlers');
router.post('/', async (req, res) => {
const {
model,
text,
overrideParentMessageId=null,
parentMessageId,
conversationId: oldConversationId,
...convo
} = req.body;
if (text.length === 0) {
return handleError(res, { text: 'Prompt empty or too short' });
}
const conversationId = oldConversationId || crypto.randomUUID();
const isNewConversation = !oldConversationId;
const userMessageId = crypto.randomUUID();
const userParentMessageId = parentMessageId || '00000000-0000-0000-0000-000000000000';
let userMessage = {
messageId: userMessageId,
sender: 'User',
text,
parentMessageId: userParentMessageId,
conversationId,
isCreatedByUser: true
};
console.log('ask log', {
model,
...userMessage,
...convo
});
if (!overrideParentMessageId) {
await saveMessage(userMessage);
await saveConvo(req?.session?.user?.username, { ...userMessage, model, ...convo });
}
return await ask({
isNewConversation,
userMessage,
model,
convo,
preSendRequest: true,
overrideParentMessageId,
req,
res
});
});
const ask = async ({
isNewConversation,
overrideParentMessageId = null,
userMessage,
model,
convo,
preSendRequest = true,
req,
res
}) => {
let {
text,
parentMessageId: userParentMessageId,
conversationId,
messageId: userMessageId
} = userMessage;
res.writeHead(200, {
Connection: 'keep-alive',
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache, no-transform',
'Access-Control-Allow-Origin': '*',
'X-Accel-Buffering': 'no'
});
if (preSendRequest) sendMessage(res, { message: userMessage, created: true });
try {
const progressCallback = createOnProgress();
const abortController = new AbortController();
res.on('close', () => {
console.log('The client has disconnected.');
// 执行其他操作
abortController.abort();
})
let response = await askSydney({
text,
onProgress: progressCallback.call(null, model, {
res,
text,
parentMessageId: overrideParentMessageId || userMessageId
}),
convo: {
parentMessageId: userParentMessageId,
conversationId,
...convo
},
abortController
});
console.log('SYDNEY RESPONSE', response);
// console.dir(response, { depth: null });
userMessage.conversationSignature =
convo.conversationSignature || response.conversationSignature;
userMessage.conversationId = response.conversationId || conversationId;
userMessage.invocationId = response.invocationId;
// Unlike gpt and bing, Sydney will never accept our given userMessage.messageId, it will generate its own one.
if (!overrideParentMessageId)
await saveMessage(userMessage);
// Save sydney response
// response.id = response.messageId;
response.invocationId = convo.invocationId ? convo.invocationId + 1 : 1;
response.conversationId = conversationId ? conversationId : crypto.randomUUID();
response.conversationSignature = convo.conversationSignature
? convo.conversationSignature
: crypto.randomUUID();
response.text = response.response || response.details.spokenText || '**Bing refused to answer.**';
// delete response.response;
response.suggestions =
response.details.suggestedResponses &&
response.details.suggestedResponses.map((s) => s.text);
response.sender = model;
// response.final = true;
// override the parentMessageId, for the regeneration.
response.parentMessageId =
overrideParentMessageId || response.parentMessageId || userMessageId;
// Save user message
userMessage.conversationId = response.conversationId || conversationId;
if (!overrideParentMessageId)
await saveMessage(userMessage);
// Bing API will not use our conversationId at the first time,
// so change the placeholder conversationId to the real one.
// Attition: the api will also create new conversationId while using invalid userMessage.parentMessageId,
// but in this situation, don't change the conversationId, but create new convo.
if (conversationId != userMessage.conversationId && isNewConversation)
await saveConvo(
req?.session?.user?.username,
{
conversationId: conversationId,
newConversationId: userMessage.conversationId
}
);
conversationId = userMessage.conversationId;
response.text = await handleText(response, true);
// Save sydney response & convo, then send
await saveMessage(response);
await saveConvo(req?.session?.user?.username, { ...response, model, chatGptLabel: null, promptPrefix: null, ...convo });
sendMessage(res, {
title: await getConvoTitle(req?.session?.user?.username, conversationId),
final: true,
requestMessage: userMessage,
responseMessage: response
});
res.end();
if (userParentMessageId == '00000000-0000-0000-0000-000000000000') {
const title = await titleConvo({ model, text, response });
await saveConvo(
req?.session?.user?.username,
{
conversationId,
title
}
);
}
} catch (error) {
console.log(error);
// await deleteMessages({ messageId: userMessageId });
const errorMessage = {
messageId: crypto.randomUUID(),
sender: model,
conversationId,
parentMessageId: overrideParentMessageId || userMessageId,
error: true,
text: error.message
};
await saveMessage(errorMessage);
handleError(res, errorMessage);
}
};
module.exports = router;
+46
View File
@@ -0,0 +1,46 @@
const express = require('express');
const router = express.Router();
const authYourLogin = require('./authYourLogin');
const userSystemEnabled = process.env.ENABLE_USER_SYSTEM || false
router.get('/login', function (req, res) {
if (userSystemEnabled)
res.redirect('/auth/your_login_page')
else
res.redirect('/')
})
router.get('/logout', function (req, res) {
// clear the session
req.session.user = null
req.session.save(function (error) {
if (userSystemEnabled)
res.redirect('/auth/your_login_page/logout')
else
res.redirect('/')
})
})
const authenticatedOr401 = (req, res, next) => {
if (userSystemEnabled) {
const user = req?.session?.user;
if (user) next();
else res.status(401).end();
} else next();
}
const authenticatedOrRedirect = (req, res, next) => {
if (userSystemEnabled) {
const user = req?.session?.user;
if (user) next();
else res.redirect('/auth/login').end();
} else next();
}
if (userSystemEnabled)
router.use('/your_login_page', authYourLogin);
module.exports = { router, authenticatedOr401, authenticatedOrRedirect };
+40
View File
@@ -0,0 +1,40 @@
const express = require('express');
const router = express.Router();
// WARNING!
// THIS IS NOT A READY TO USE USER SYSTEM
// PLEASE IMPLEMENT YOUR OWN USER SYSTEM
const userSystemEnabled = process.env.ENABLE_USER_SYSTEM || false
// Logout
router.get('/logout', (req, res) => {
// Do anything you want
console.warn('logout not implemented!')
// finish
res.redirect('/')
});
// Login
router.get('/', async (req, res) => {
// Do anything you want
console.warn('login not implemented! Automatic passed as sample user')
// save the user info into session
// username will be used in db
// display will be used in UI
req.session.user = {
username: 'sample_user',
display: 'Sample User',
}
req.session.save(function (error) {
if (error) {
console.log(error);
res.send(`<h1>Login Failed. An error occurred. Please see the server logs for details.</h1>`);
} else res.redirect('/')
})
});
module.exports = router;
+67
View File
@@ -0,0 +1,67 @@
const express = require('express');
const router = express.Router();
const { titleConvo } = require('../../app/');
const { getConvo, saveConvo, getConvoTitle } = require('../../models');
const { getConvosByPage, deleteConvos, updateConvo } = require('../../models/Conversation');
const { getMessages } = require('../../models/Message');
router.get('/', async (req, res) => {
const pageNumber = req.query.pageNumber || 1;
res.status(200).send(await getConvosByPage(req?.session?.user?.username, pageNumber));
});
router.post('/gen_title', async (req, res) => {
const { conversationId } = req.body.arg;
const convo = await getConvo(req?.session?.user?.username, conversationId)
const firstMessage = (await getMessages({ conversationId }))[0]
const secondMessage = (await getMessages({ conversationId }))[1]
const title = convo.jailbreakConversationId
? await getConvoTitle(req?.session?.user?.username, conversationId)
: await titleConvo({
model: convo?.model,
message: firstMessage?.text,
response: JSON.stringify(secondMessage?.text || '')
});
await saveConvo(
req?.session?.user?.username,
{
conversationId,
title
}
)
res.status(200).send(title);
});
router.post('/clear', async (req, res) => {
let filter = {};
const { conversationId } = req.body.arg;
if (conversationId) {
filter = { conversationId };
}
try {
const dbResponse = await deleteConvos(req?.session?.user?.username, filter);
res.status(201).send(dbResponse);
} catch (error) {
console.error(error);
res.status(500).send(error);
}
});
router.post('/update', async (req, res) => {
const update = req.body.arg;
try {
const dbResponse = await updateConvo(req?.session?.user?.username, update);
res.status(201).send(dbResponse);
} catch (error) {
console.error(error);
res.status(500).send(error);
}
});
module.exports = router;
+67
View File
@@ -0,0 +1,67 @@
const express = require('express');
const router = express.Router();
const {
getCustomGpts,
updateCustomGpt,
updateByLabel,
deleteCustomGpts
} = require('../../models');
router.get('/', async (req, res) => {
const models = (await getCustomGpts(req?.session?.user?.username)).map((model) => {
model = model.toObject();
model._id = model._id.toString();
return model;
});
res.status(200).send(models);
});
router.post('/delete', async (req, res) => {
const { arg } = req.body;
try {
await deleteCustomGpts(req?.session?.user?.username, arg);
const models = (await getCustomGpts(req?.session?.user?.username)).map((model) => {
model = model.toObject();
model._id = model._id.toString();
return model;
});
res.status(201).send(models);
// res.status(201).send(dbResponse);
} catch (error) {
console.error(error);
res.status(500).send(error);
}
});
// router.post('/create', async (req, res) => {
// const payload = req.body.arg;
// try {
// const dbResponse = await createCustomGpt(payload);
// res.status(201).send(dbResponse);
// } catch (error) {
// console.error(error);
// res.status(500).send(error);
// }
// });
router.post('/', async (req, res) => {
const update = req.body.arg;
let setter = updateCustomGpt;
if (update.prevLabel) {
setter = updateByLabel;
}
try {
const dbResponse = await setter(req?.session?.user?.username, update);
res.status(201).send(dbResponse);
} catch (error) {
console.error(error);
res.status(500).send(error);
}
});
module.exports = router;
+62
View File
@@ -0,0 +1,62 @@
const _ = require('lodash');
const citationRegex = /\[\^\d+?\^]/g;
const { getCitations, citeText, detectCode } = require('../../app/');
const handleError = (res, message) => {
res.write(`event: error\ndata: ${JSON.stringify(message)}\n\n`);
res.end();
};
const sendMessage = (res, message) => {
if (message.length === 0) {
return;
}
res.write(`event: message\ndata: ${JSON.stringify(message)}\n\n`);
};
const createOnProgress = () => {
let i = 0;
let tokens = '';
const progressCallback = async (partial, { res, text, bing = false, ...rest }) => {
tokens += partial === text ? '' : partial;
tokens = tokens.replaceAll('[DONE]', '');
if (tokens.match(/^\n/)) {
tokens = tokens.replace(/^\n/, '');
}
if (bing) {
tokens = citeText(tokens, true);
}
sendMessage(res, { text: tokens, message: true, initial: i === 0, ...rest });
i++;
};
const onProgress = (model, opts) => {
const bingModels = new Set(['bingai', 'sydney']);
return _.partialRight(progressCallback, { ...opts, bing: bingModels.has(model) });
};
return onProgress;
};
const handleText = async (response, bing = false) => {
let { text } = response;
// text = await detectCode(text);
response.text = text;
if (bing) {
// const hasCitations = response.response.match(citationRegex)?.length > 0;
const links = getCitations(response);
if (response.text.match(citationRegex)?.length > 0) {
text = citeText(response);
}
text += links?.length > 0 ? `\n<small>${links}</small>` : '';
}
return text;
};
module.exports = { handleError, sendMessage, createOnProgress, handleText };
+8
View File
@@ -0,0 +1,8 @@
const ask = require('./ask');
const messages = require('./messages');
const convos = require('./convos');
const customGpts = require('./customGpts');
const prompts = require('./prompts');
const { router: auth, authenticatedOr401, authenticatedOrRedirect } = require('./auth');
module.exports = { ask, messages, convos, customGpts, prompts, auth, authenticatedOr401, authenticatedOrRedirect };
+10
View File
@@ -0,0 +1,10 @@
const express = require('express');
const router = express.Router();
const { getMessages } = require('../../models/Message');
router.get('/:conversationId', async (req, res) => {
const { conversationId } = req.params;
res.status(200).send(await getMessages({ conversationId }));
});
module.exports = router;
+14
View File
@@ -0,0 +1,14 @@
const express = require('express');
const router = express.Router();
const { savePrompt, getPrompts, deletePrompts } = require('../../models/Prompt');
router.get('/', async (req, res) => {
let filter = {};
// const { search } = req.body.arg;
// if (!!search) {
// filter = { conversationId };
// }
res.status(200).send(await getPrompts(filter));
});
module.exports = router;
+318
View File
@@ -0,0 +1,318 @@
const languages = new Set([
'adoc',
'apacheconf',
'arm',
'as',
'asc',
'atom',
'bat',
'bf',
'bind',
'c++',
'capnp',
'cc',
'clj',
'cls',
'cmake.in',
'cmd',
'coffee',
'console',
'cr',
'craftcms',
'crm',
'cs',
'cson',
'cts',
'cxx',
'dfm',
'docker',
'dst',
'erl',
'f90',
'f95',
'fs',
'gawk',
'gemspec',
'gms',
'golang',
'gololang',
'gss',
'gyp',
'h',
'h++',
'hbs',
'hh',
'hpp',
'hs',
'html',
'html.handlebars',
'html.hbs',
'https',
'hx',
'hxx',
'hylang',
'i7',
'iced',
'ino',
'instances',
'irb',
'jinja',
'js',
'jsp',
'jsx',
'julia-repl',
'kdb',
'kt',
'lassoscript',
'ls',
'ls',
'mak',
'make',
'mawk',
'md',
'mipsasm',
'mk',
'mkd',
'mkdown',
'ml',
'ml',
'mm',
'mma',
'moon',
'mts',
'nawk',
'nc',
'nginxconf',
'nimrod',
'objc',
'obj-c',
'obj-c++',
'objective-c++',
'osascript',
'pas',
'pascal',
'patch',
'pcmk',
'pf.conf',
'pl',
'plist',
'pm',
'podspec',
'postgres',
'postgresql',
'pp',
'ps',
'ps1',
'py',
'pycon',
'rb',
're',
'rs',
'rss',
'sas',
'scad',
'sci',
'sh',
'st',
'stanfuncs',
'step',
'stp',
'styl',
'svg',
'tao',
'text',
'thor',
'tk',
'toml',
'ts',
'tsx',
'txt',
'v',
'vb',
'vbs',
'wl',
'x++',
'xhtml',
'xjb',
'xls',
'xlsx',
'xpath',
'xq',
'xsd',
'xsl',
'yaml',
'zep',
'zone',
'zsh',
'1c',
'abnf',
'accesslog',
'actionscript',
'ada',
'angelscript',
'apache',
'applescript',
'arcade',
'arduino',
'armasm',
'asciidoc',
'aspectj',
'autohotkey',
'autoit',
'avrasm',
'awk',
'axapta',
'bash',
'basic',
'bnf',
'brainfuck',
'c',
'cal',
'capnproto',
'clojure',
'cmake',
'coffeescript',
'coq',
'cos',
'cpp',
'crmsh',
'crystal',
'csharp',
'csp',
'css',
'd',
'dart',
'diff',
'django',
'dns',
'dockerfile',
'dos',
'dpr',
'dsconfig',
'dts',
'dust',
'ebnf',
'elixir',
'elm',
'erlang',
'excel',
'fix',
'fortran',
'fsharp',
'gams',
'gauss',
'gcode',
'gherkin',
'glsl',
'go',
'golo',
'gradle',
'graph',
'graphql',
'groovy',
'haml',
'handlebars',
'haskell',
'haxe',
'http',
'hy',
'inform7',
'ini',
'irpf90',
'java',
'javascript',
'json',
'julia',
'k',
'kotlin',
'lasso',
'ldif',
'leaf',
'less',
'lisp',
'livecodeserver',
'livescript',
'lua',
'makefile',
'markdown',
'mathematica',
'matlab',
'maxima',
'mel',
'mercury',
'mips',
'mizar',
'mojolicious',
'monkey',
'moonscript',
'n1ql',
'nginx',
'nim',
'nix',
'nsis',
'objectivec',
'ocaml',
'openscad',
'oxygene',
'p21',
'parser3',
'perl',
'pf',
'pgsql',
'php',
'plaintext',
'pony',
'powershell',
'processing',
'profile',
'prolog',
'properties',
'protobuf',
'puppet',
'python',
'python-repl',
'qml',
'r',
'reasonml',
'rib',
'rsl',
'ruby',
'ruleslanguage',
'rust',
'SAS',
'scala' ,
'scheme',
'scilab',
'scss',
'shell',
'smali',
'smalltalk',
'sml',
'sql',
'stan',
'stata',
'stylus',
'subunit',
'swift',
'tap',
'tcl',
'tex',
'thrift',
'tp',
'twig',
'typescript',
'vala',
'vbnet',
'vbscript',
'verilog',
'vhdl',
'vim',
'x86asm',
'xl',
'xml',
'xquery',
'yml',
'zephir',
]);
module.exports = languages;
-28
View File
@@ -1,28 +0,0 @@
import sys
def check_file_length(max_lines, filenames):
bad_files = []
for filename in filenames:
with open(filename, "r") as file:
lines = file.readlines()
if len(lines) > max_lines:
bad_files.append((filename, len(lines)))
return bad_files
if __name__ == "__main__":
max_lines = int(sys.argv[1])
filenames = sys.argv[2:]
bad_files = check_file_length(max_lines, filenames)
if bad_files:
bad_files.sort(
key=lambda x: x[1], reverse=True
) # Sort files by length in descending order
for filename, length in bad_files:
print(f"{filename}: {length} lines")
sys.exit(1)
else:
sys.exit(0)
-32
View File
@@ -1,32 +0,0 @@
import sys
import filecmp
import shutil
def main(argv=None):
print(
"Comparing model_prices_and_context_window and litellm/model_prices_and_context_window_backup.json files... checking if they match."
)
file1 = "model_prices_and_context_window.json"
file2 = "litellm/model_prices_and_context_window_backup.json"
cmp_result = filecmp.cmp(file1, file2, shallow=False)
if cmp_result:
print(f"Passed! Files {file1} and {file2} match.")
return 0
else:
print(
f"Failed! Files {file1} and {file2} do not match. Copying content from {file1} to {file2}."
)
copy_content(file1, file2)
return 1
def copy_content(source, destination):
shutil.copy2(source, destination)
if __name__ == "__main__":
sys.exit(main())
+23
View File
@@ -0,0 +1,23 @@
{
/*
a preset is a set of plugins used to support particular language features.
The two presets Babel uses by default: es2015, react
*/
"presets": [
"@babel/preset-env", //compiling ES2015+ syntax
"@babel/preset-react" //for react
],
/*
Babel's code transformations are enabled by applying plugins (or presets) to your configuration file.
*/
"plugins": [
"@babel/plugin-transform-runtime",
[
"babel-plugin-root-import",
{
"rootPathPrefix": "~/",
"rootPathSuffix": "./src"
}
]
]
}
+2
View File
@@ -0,0 +1,2 @@
/node_modules
.env
+22
View File
@@ -0,0 +1,22 @@
# Stage 1
FROM node:19-alpine as builder
WORKDIR /client
# copy package.json into the container at /client
COPY package*.json /client/
# install dependencies
RUN npm install
# Copy the current directory contents into the container at /client
COPY . /client/
# Build webpack artifacts
RUN npm run build
# Stage 2
FROM nginx:stable-alpine
WORKDIR /usr/share/nginx/html
RUN rm -rf ./*
COPY --from=builder /client/public /usr/share/nginx/html
# Add your nginx.conf
COPY nginx.conf /etc/nginx/conf.d/default.conf
ENTRYPOINT ["nginx", "-g", "daemon off;"]
# docker build -t react-client .
+19
View File
@@ -0,0 +1,19 @@
import React from 'react';
import { createRoot } from 'react-dom/client';
import { Provider } from 'react-redux';
import { store } from './src/store';
import { ThemeProvider } from './src/hooks/ThemeContext';
import App from './src/App';
import './src/style.css';
import './src/mobile.css'
const container = document.getElementById('root');
const root = createRoot(container);
root.render(
<Provider store={store}>
<ThemeProvider>
<App />
</ThemeProvider>
</Provider>
);
+15
View File
@@ -0,0 +1,15 @@
server {
listen 80;
server_name localhost;
location / {
# Serve your React app
root /usr/share/nginx/html;
index index.html;
}
location /api {
# Proxy requests to the API service
proxy_pass http://api:3080/api;
}
}
+19680
View File
File diff suppressed because it is too large Load Diff
+79
View File
@@ -0,0 +1,79 @@
{
"name": "chatgpt-clone",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "webpack",
"build-dev": "Webpack . --watch"
},
"repository": {
"type": "git",
"url": "git+https://github.com/danny-avila/chatgpt-clone.git"
},
"keywords": [],
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/danny-avila/chatgpt-clone/issues"
},
"homepage": "https://github.com/danny-avila/chatgpt-clone#readme",
"dependencies": {
"@radix-ui/react-alert-dialog": "^1.0.2",
"@radix-ui/react-dialog": "^1.0.2",
"@radix-ui/react-dropdown-menu": "^2.0.2",
"@radix-ui/react-label": "^2.0.0",
"@radix-ui/react-tabs": "^1.0.2",
"@reduxjs/toolkit": "^1.9.2",
"axios": "^1.3.4",
"class-variance-authority": "^0.4.0",
"clsx": "^1.2.1",
"crypto-browserify": "^3.12.0",
"highlight.js": "^11.7.0",
"lucide-react": "^0.113.0",
"markdown-to-jsx": "^7.1.9",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-redux": "^8.0.5",
"react-textarea-autosize": "^8.4.0",
"react-transition-group": "^4.4.5",
"swr": "^2.0.3",
"tailwind-merge": "^1.9.1",
"tailwindcss-animate": "^1.0.5",
"url": "^0.11.0"
},
"devDependencies": {
"@babel/cli": "^7.20.7",
"@babel/core": "^7.20.12",
"@babel/eslint-parser": "^7.19.1",
"@babel/plugin-transform-runtime": "^7.19.6",
"@babel/preset-env": "^7.20.2",
"@babel/preset-react": "^7.18.6",
"@babel/runtime": "^7.20.13",
"autoprefixer": "^10.4.13",
"babel-loader": "^9.1.2",
"babel-plugin-root-import": "^6.6.0",
"babel-preset-react": "^6.24.1",
"css-loader": "^6.7.3",
"eslint": "^8.33.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-jest": "^27.2.1",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"path": "^0.12.7",
"postcss": "^8.4.21",
"postcss-loader": "^7.0.2",
"postcss-preset-env": "^8.0.1",
"prettier": "^2.8.3",
"prettier-plugin-tailwindcss": "^0.2.2",
"source-map-loader": "^1.1.3",
"style-loader": "^3.3.1",
"tailwindcss": "^3.2.6",
"ts-loader": "^9.4.2",
"typescript": "^4.9.5",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.1",
"webpack-dev-server": "^4.11.1"
}
}
+2
View File
@@ -0,0 +1,2 @@
const tailwindcss = require('tailwindcss');
module.exports = { plugins: ['postcss-preset-env', tailwindcss] };
Binary file not shown.

After

Width:  |  Height:  |  Size: 828 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

+40
View File
@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="theme-color" content="#343541">
<title>ChatGPT Clone</title>
<link
rel="shortcut icon"
href="#"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="/assets/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="/assets/favicon-16x16.png"
/>
<meta
name="viewport"
content="width=device-width, initial-scale=1"
/>
<script
defer
src="main.js"
></script>
</head>
<body>
<div id="root"></div>
<script
type="text/javascript"
src="main.js"
></script>
</body>
</html>
+71
View File
@@ -0,0 +1,71 @@
import React, { useEffect, useState } from 'react';
import Messages from './components/Messages';
import Landing from './components/Main/Landing';
import TextChat from './components/Main/TextChat';
import Nav from './components/Nav';
import MobileNav from './components/Nav/MobileNav';
import useDocumentTitle from '~/hooks/useDocumentTitle';
import { useSelector, useDispatch } from 'react-redux';
import { setUser } from './store/userReducer';
import axios from 'axios';
const App = () => {
const dispatch = useDispatch();
const { messages, messageTree } = useSelector((state) => state.messages);
const { user } = useSelector((state) => state.user);
const { title } = useSelector((state) => state.convo);
const { conversationId } = useSelector((state) => state.convo);
const [ navVisible, setNavVisible ]= useState(false)
useDocumentTitle(title);
useEffect(() => {
axios.get('/api/me', {
timeout: 1000,
withCredentials: true
}).then((res) => {
return res.data
}).then((user) => {
if (user)
dispatch(setUser(user))
else {
console.log('Not login!')
window.location.href = "/auth/login";
}
}).catch((error) => {
console.error(error)
console.log('Not login!')
window.location.href = "/auth/login";
})
// setUser
}, [])
if (user)
return (
<div className="flex h-screen">
<Nav navVisible={navVisible} setNavVisible={setNavVisible} />
<div className="flex h-full w-full flex-1 flex-col bg-gray-50 md:pl-[260px]">
<div className="transition-width relative flex h-full w-full flex-1 flex-col items-stretch overflow-hidden bg-white dark:bg-gray-800">
<MobileNav setNavVisible={setNavVisible} />
{messages.length === 0 ? (
<Landing title={title} />
) : (
<Messages
messages={messages}
messageTree={messageTree}
/>
)}
<TextChat messages={messages} />
</div>
</div>
</div>
);
else
return (
<div className="flex h-screen">
</div>
)
};
export default App;
@@ -0,0 +1,184 @@
import React, { useState, useRef } from 'react';
import RenameButton from './RenameButton';
import DeleteButton from './DeleteButton';
import { useSelector, useDispatch } from 'react-redux';
import { setConversation } from '~/store/convoSlice';
import { setSubmission, setStopStream, setCustomGpt, setModel, setCustomModel } from '~/store/submitSlice';
import { setMessages, setEmptyMessage } from '~/store/messageSlice';
import { setText } from '~/store/textSlice';
import manualSWR from '~/utils/fetchers';
import ConvoIcon from '../svg/ConvoIcon';
import { refreshConversation } from '../../store/convoSlice';
export default function Conversation({
id,
model,
parentMessageId,
conversationId,
title,
chatGptLabel = null,
promptPrefix = null,
bingData,
retainView,
}) {
const [renaming, setRenaming] = useState(false);
const [titleInput, setTitleInput] = useState(title);
const { modelMap } = useSelector((state) => state.models);
const { stopStream } = useSelector((state) => state.submit);
const inputRef = useRef(null);
const dispatch = useDispatch();
const { trigger } = manualSWR(`/api/messages/${id}`, 'get');
const rename = manualSWR(`/api/convos/update`, 'post');
const clickHandler = async () => {
if (conversationId === id) {
return;
}
if (!stopStream) {
dispatch(setStopStream(true));
dispatch(setSubmission({}));
}
dispatch(setEmptyMessage());
const convo = { title, error: false, conversationId: id, chatGptLabel, promptPrefix };
if (bingData) {
const {
parentMessageId,
conversationSignature,
jailbreakConversationId,
clientId,
invocationId
} = bingData;
dispatch(
setConversation({
...convo,
parentMessageId,
jailbreakConversationId,
conversationSignature,
clientId,
invocationId,
latestMessage: null
})
);
} else {
dispatch(
setConversation({
...convo,
parentMessageId,
jailbreakConversationId: null,
conversationSignature: null,
clientId: null,
invocationId: null,
latestMessage: null
})
);
}
const data = await trigger();
if (chatGptLabel) {
dispatch(setModel('chatgptCustom'));
dispatch(setCustomModel(chatGptLabel.toLowerCase()));
} else {
dispatch(setModel(model));
dispatch(setCustomModel(null));
}
// if (modelMap[chatGptLabel.toLowerCase()]) {
// console.log('custom model', chatGptLabel);
// dispatch(setCustomModel(chatGptLabel.toLowerCase()));
// } else {
// dispatch(setCustomModel(null));
// }
dispatch(setMessages(data));
dispatch(setCustomGpt(convo));
dispatch(setText(''));
dispatch(setStopStream(false));
};
const renameHandler = (e) => {
e.preventDefault();
setTitleInput(title);
setRenaming(true);
setTimeout(() => {
inputRef.current.focus();
}, 25);
};
const cancelHandler = (e) => {
e.preventDefault();
setRenaming(false);
};
const onRename = (e) => {
e.preventDefault();
setRenaming(false);
if (titleInput === title) {
return;
}
rename.trigger({ conversationId, title: titleInput })
.then(() => {
dispatch(refreshConversation())
});
};
const handleKeyDown = (e) => {
if (e.key === 'Enter') {
onRename(e);
}
};
const aProps = {
className:
'animate-flash group relative flex cursor-pointer items-center gap-3 break-all rounded-md bg-gray-800 py-3 px-3 pr-14 hover:bg-gray-800'
};
if (conversationId !== id) {
aProps.className =
'group relative flex cursor-pointer items-center gap-3 break-all rounded-md py-3 px-3 hover:bg-[#2A2B32] hover:pr-4';
}
return (
<a
onClick={() => clickHandler()}
{...aProps}
>
<ConvoIcon />
<div className="relative max-h-5 flex-1 overflow-hidden text-ellipsis break-all">
{renaming === true ? (
<input
ref={inputRef}
type="text"
className="m-0 mr-0 w-full border border-blue-500 bg-transparent p-0 text-sm leading-tight outline-none"
value={titleInput}
onChange={(e) => setTitleInput(e.target.value)}
onBlur={onRename}
onKeyDown={handleKeyDown}
/>
) : (
title
)}
</div>
{conversationId === id ? (
<div className="visible absolute right-1 z-10 flex text-gray-300">
<RenameButton
conversationId={id}
renaming={renaming}
renameHandler={renameHandler}
onRename={onRename}
/>
<DeleteButton
conversationId={id}
renaming={renaming}
cancelHandler={cancelHandler}
retainView={retainView}
/>
</div>
) : (
<div className="absolute inset-y-0 right-0 z-10 w-8 bg-gradient-to-l from-gray-900 group-hover:from-[#2A2B32]" />
)}
</a>
);
}
@@ -0,0 +1,35 @@
import React from 'react';
import TrashIcon from '../svg/TrashIcon';
import CrossIcon from '../svg/CrossIcon';
import manualSWR from '~/utils/fetchers';
import { useDispatch } from 'react-redux';
import { setNewConvo, removeConvo } from '~/store/convoSlice';
import { setMessages } from '~/store/messageSlice';
import { setSubmission } from '~/store/submitSlice';
export default function DeleteButton({ conversationId, renaming, cancelHandler, retainView }) {
const dispatch = useDispatch();
const { trigger } = manualSWR(
`/api/convos/clear`,
'post',
() => {
dispatch(setMessages([]));
dispatch(removeConvo(conversationId));
dispatch(setNewConvo());
dispatch(setSubmission({}));
retainView();
}
);
const clickHandler = () => trigger({ conversationId });
const handler = renaming ? cancelHandler : clickHandler;
return (
<button
className="p-1 hover:text-white"
onClick={handler}
>
{ renaming ? <CrossIcon/> : <TrashIcon />}
</button>
);
}
@@ -0,0 +1,16 @@
import React from 'react';
import RenameIcon from '../svg/RenameIcon';
import CheckMark from '../svg/CheckMark';
export default function RenameButton({ renaming, renameHandler, onRename, twcss }) {
const handler = renaming ? onRename : renameHandler;
const classProp = { className: "p-1 hover:text-white" };
if (twcss) {
classProp.className = twcss;
}
return (
<button {...classProp} onClick={handler}>
{renaming ? <CheckMark /> : <RenameIcon />}
</button>
);
}
@@ -0,0 +1,61 @@
import React from 'react';
import Conversation from './Conversation';
export default function Conversations({ conversations, conversationId, pageNumber, pages, nextPage, previousPage, moveToTop }) {
const clickHandler = (func) => async (e) => {
e.preventDefault();
await func();
};
return (
<>
{conversations &&
conversations.length > 0 &&
conversations.map((convo) => {
const bingData = convo.conversationSignature
? {
jailbreakConversationId: convo.jailbreakConversationId,
conversationSignature: convo.conversationSignature,
parentMessageId: convo.parentMessageId || null,
clientId: convo.clientId,
invocationId: convo.invocationId
}
: null;
return (
<Conversation
key={convo.conversationId}
id={convo.conversationId}
model={convo.model}
parentMessageId={convo.parentMessageId}
title={convo.title}
conversationId={conversationId}
chatGptLabel={convo.chatGptLabel}
promptPrefix={convo.promptPrefix}
bingData={bingData}
retainView={moveToTop}
/>
);
})}
<div className="m-auto mt-4 mb-2 flex justify-center items-center gap-2">
<button
onClick={clickHandler(previousPage)}
className={"flex btn btn-small transition bg-transition dark:text-white disabled:text-gray-300 dark:disabled:text-gray-400 m-auto gap-2 hover:bg-gray-800" + (pageNumber<=1?" hidden-visibility":"")}
disabled={pageNumber<=1}
>
&lt;&lt;
</button>
<span className="flex-none text-gray-400">
{pageNumber} / {pages}
</span>
<button
onClick={clickHandler(nextPage)}
className={"flex btn btn-small transition bg-transition dark:text-white disabled:text-gray-300 dark:disabled:text-gray-400 m-auto gap-2 hover:bg-gray-800" + (pageNumber>=pages?" hidden-visibility":"")}
disabled={pageNumber>=pages}
>
&gt;&gt;
</button>
</div>
</>
);
}
+18
View File
@@ -0,0 +1,18 @@
import React from 'react';
export default function Footer() {
return (
<div className="hidden md:block px-3 pt-2 pb-1 text-center text-xs text-black/50 dark:text-white/50 md:px-4 md:pt-3 md:pb-4">
<a
href="https://github.com/danny-avila/chatgpt-clone"
target="_blank"
rel="noreferrer"
className="underline"
>
ChatGPT Clone
</a>
. Serves and searches all conversations reliably. All AI convos under one house. Pay per
call and not per month (cents compared to dollars).
</div>
);
}
+112
View File
@@ -0,0 +1,112 @@
import React, { useState } from 'react';
import { useDispatch } from 'react-redux';
import { setText } from '~/store/textSlice';
import useDocumentTitle from '~/hooks/useDocumentTitle';
import Templates from '../Prompts/Templates';
import SunIcon from '../svg/SunIcon';
import LightningIcon from '../svg/LightningIcon';
import CautionIcon from '../svg/CautionIcon';
import ChatIcon from '../svg/ChatIcon';
export default function Landing({ title }) {
const [showingTemplates, setShowingTemplates] = useState(false);
const dispatch = useDispatch();
useDocumentTitle(title);
const clickHandler = (e) => {
e.preventDefault();
const { innerText } = e.target;
const quote = innerText.split('"')[1].trim();
dispatch(setText(quote));
};
const showTemplates = (e) => {
e.preventDefault();
setShowingTemplates(!showingTemplates);
};
return (
<div className="flex pt-10 md:pt-0 h-full flex-col items-center overflow-y-auto text-sm dark:bg-gray-800">
<div className="w-full px-6 text-gray-800 dark:text-gray-100 md:flex md:max-w-2xl md:flex-col lg:max-w-3xl">
<h1 className="mt-6 ml-auto mr-auto mb-10 flex items-center justify-center gap-2 text-center text-4xl font-semibold md:mt-[20vh] sm:mb-16">
ChatGPT Clone
</h1>
<div className="items-start gap-3.5 text-center md:flex">
<div className="mb-8 flex flex-1 flex-col gap-3.5 md:mb-auto">
<h2 className="m-auto flex items-center gap-3 text-lg font-normal md:flex-col md:gap-2">
<SunIcon />
Examples
</h2>
<ul className="m-auto flex w-full flex-col gap-3.5 sm:max-w-md">
<button
onClick={clickHandler}
className="w-full rounded-md bg-gray-50 p-3 hover:bg-gray-200 dark:bg-white/5 dark:hover:bg-gray-900"
>
&quot;Explain quantum computing in simple terms&quot;
</button>
<button
onClick={clickHandler}
className="w-full rounded-md bg-gray-50 p-3 hover:bg-gray-200 dark:bg-white/5 dark:hover:bg-gray-900"
>
&quot;Got any creative ideas for a 10 year old&apos;s birthday?&quot;
</button>
<button
onClick={clickHandler}
className="w-full rounded-md bg-gray-50 p-3 hover:bg-gray-200 dark:bg-white/5 dark:hover:bg-gray-900"
>
&quot;How do I make an HTTP request in Javascript?&quot;
</button>
</ul>
</div>
<div className="mb-8 flex flex-1 flex-col gap-3.5 md:mb-auto">
<h2 className="m-auto flex items-center gap-3 text-lg font-normal md:flex-col md:gap-2">
<LightningIcon />
Capabilities
</h2>
<ul className="m-auto flex w-full flex-col gap-3.5 sm:max-w-md">
<li className="w-full rounded-md bg-gray-50 p-3 dark:bg-white/5">
Remembers what user said earlier in the conversation
</li>
<li className="w-full rounded-md bg-gray-50 p-3 dark:bg-white/5">
Allows user to provide follow-up corrections
</li>
<li className="w-full rounded-md bg-gray-50 p-3 dark:bg-white/5">
Trained to decline inappropriate requests
</li>
</ul>
</div>
<div className="mb-8 flex flex-1 flex-col gap-3.5 md:mb-auto">
<h2 className="m-auto flex items-center gap-3 text-lg font-normal md:flex-col md:gap-2">
<CautionIcon />
Limitations
</h2>
<ul className="m-auto flex w-full flex-col gap-3.5 sm:max-w-md">
<li className="w-full rounded-md bg-gray-50 p-3 dark:bg-white/5">
May occasionally generate incorrect information
</li>
<li className="w-full rounded-md bg-gray-50 p-3 dark:bg-white/5">
May occasionally produce harmful instructions or biased content
</li>
<li className="w-full rounded-md bg-gray-50 p-3 dark:bg-white/5">
Limited knowledge of world and events after 2021
</li>
</ul>
</div>
</div>
{/* {!showingTemplates && (
<div className="mt-8 mb-4 flex flex-col items-center gap-3.5 md:mt-16">
<button
onClick={showTemplates}
className="btn btn-neutral justify-center gap-2 border-0 md:border"
>
<ChatIcon />
Show Prompt Templates
</button>
</div>
)}
{!!showingTemplates && <Templates showTemplates={showTemplates}/>} */}
<div className="group h-32 w-full flex-shrink-0 dark:border-gray-900/50 dark:bg-gray-800 md:h-48" />
</div>
</div>
);
}
+35
View File
@@ -0,0 +1,35 @@
import React from 'react';
import RegenerateIcon from '../svg/RegenerateIcon';
export default function Regenerate({ submitMessage, tryAgain, errorMessage }) {
const clickHandler = (e) => {
e.preventDefault();
submitMessage();
};
return (
<>
<span className="mb-2 block flex justify-center text-xs text-black dark:text-white/50 md:mb-2">
There was an error generating a response
</span>
<span className="m-auto flex justify-center">
{!errorMessage.includes('short') && (
<button
onClick={clickHandler}
className="btn btn-primary m-auto flex justify-center gap-2"
>
<RegenerateIcon />
Regenerate response
</button>
)}
<button
onClick={tryAgain}
className="btn btn-neutral flex justify-center gap-2 border-0 md:border"
>
<RegenerateIcon />
Try another message
</button>
</span>
</>
);
}
@@ -0,0 +1,61 @@
import React from 'react';
import { useSelector } from 'react-redux';
export default function SubmitButton({ submitMessage, disabled }) {
const { isSubmitting } = useSelector((state) => state.submit);
const { error, latestMessage } = useSelector((state) => state.convo);
const clickHandler = (e) => {
e.preventDefault();
submitMessage();
};
if (isSubmitting) {
return (
<button
className="absolute bottom-0 right-1 h-[100%] w-[30px] rounded-md p-1 text-gray-500 hover:bg-gray-100 disabled:hover:bg-transparent dark:hover:bg-gray-900 dark:hover:text-gray-400 dark:disabled:hover:bg-transparent md:right-2"
disabled
>
<div className="text-2xl">
<span>·</span>
<span className="blink">·</span>
<span className="blink2">·</span>
</div>
</button>
);
}
return (
<button
onClick={clickHandler}
disabled={disabled}
className="group absolute bottom-0 right-0 flex h-[100%] w-[50px] items-center justify-center bg-transparent p-1 text-gray-500"
>
<div className="m-1 mr-0 rounded-md p-2 pt-[10px] pb-[10px] group-hover:bg-gray-100 group-disabled:hover:bg-transparent dark:group-hover:bg-gray-900 dark:group-hover:text-gray-400 dark:group-disabled:hover:bg-transparent">
<svg
stroke="currentColor"
fill="none"
strokeWidth="2"
viewBox="0 0 24 24"
strokeLinecap="round"
strokeLinejoin="round"
className="mr-1 h-4 w-4 "
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<line
x1="22"
y1="2"
x2="11"
y2="13"
/>
<polygon points="22 2 15 22 11 13 2 9 22 2" />
</svg>
</div>
</button>
);
}
{
/* <div class="text-2xl"><span class="">·</span><span class="">·</span><span class="invisible">·</span></div> */
}
+411
View File
@@ -0,0 +1,411 @@
import React, { useEffect, useRef, useState } from 'react';
import { SSE } from '~/utils/sse';
import SubmitButton from './SubmitButton';
import Regenerate from './Regenerate';
import ModelMenu from '../Models/ModelMenu';
import Footer from './Footer';
import TextareaAutosize from 'react-textarea-autosize';
import createPayload from '~/utils/createPayload';
import resetConvo from '~/utils/resetConvo';
import RegenerateIcon from '../svg/RegenerateIcon';
import StopGeneratingIcon from '../svg/StopGeneratingIcon';
import { useSelector, useDispatch } from 'react-redux';
import {
setConversation,
setNewConvo,
setError,
refreshConversation
} from '~/store/convoSlice';
import { setMessages } from '~/store/messageSlice';
import { setSubmitState, setSubmission } from '~/store/submitSlice';
import { setText } from '~/store/textSlice';
import { useMessageHandler } from '../../utils/handleSubmit';
export default function TextChat({ messages }) {
const [errorMessage, setErrorMessage] = useState('');
const inputRef = useRef(null);
const isComposing = useRef(false);
const dispatch = useDispatch();
const { user } = useSelector((state) => state.user);
const convo = useSelector((state) => state.convo);
const { initial } = useSelector((state) => state.models);
const { isSubmitting, stopStream, submission, disabled, model, chatGptLabel, promptPrefix } =
useSelector((state) => state.submit);
const { text } = useSelector((state) => state.text);
const { error, latestMessage } = convo;
const { ask, regenerate, stopGenerating } = useMessageHandler();
const isNotAppendable = latestMessage?.cancelled || latestMessage?.error;
// auto focus to input, when enter a conversation.
useEffect(() => {
inputRef.current?.focus();
}, [convo?.conversationId]);
const messageHandler = (data, currentState, currentMsg) => {
const { messages, _currentMsg, message, sender, isRegenerate } = currentState;
if (isRegenerate)
dispatch(
setMessages([
...messages,
{
sender,
text: data,
parentMessageId: message?.overrideParentMessageId,
messageId: message?.overrideParentMessageId + '_',
submitting: true
}
])
);
else
dispatch(
setMessages([
...messages,
currentMsg,
{
sender,
text: data,
parentMessageId: currentMsg?.messageId,
messageId: currentMsg?.messageId + '_',
submitting: true
}
])
);
};
const cancelHandler = (data, currentState, currentMsg) => {
const { messages, _currentMsg, message, sender, isRegenerate } = currentState;
if (isRegenerate)
dispatch(
setMessages([
...messages,
{
sender,
text: data,
parentMessageId: message?.overrideParentMessageId,
messageId: message?.overrideParentMessageId + '_',
cancelled: true
}
])
);
else
dispatch(
setMessages([
...messages,
currentMsg,
{
sender,
text: data,
parentMessageId: currentMsg?.messageId,
messageId: currentMsg?.messageId + '_',
cancelled: true
}
])
);
};
const createdHandler = (data, currentState, currentMsg) => {
const { conversationId } = currentMsg;
dispatch(
setConversation({
conversationId,
latestMessage: null
})
);
};
const convoHandler = (data, currentState, currentMsg) => {
const { requestMessage, responseMessage } = data;
const { conversationId } = requestMessage;
const { messages, _currentMsg, message, isCustomModel, sender, isRegenerate } =
currentState;
const { model, chatGptLabel, promptPrefix } = message;
if (isRegenerate) dispatch(setMessages([...messages, responseMessage]));
else dispatch(setMessages([...messages, requestMessage, responseMessage]));
dispatch(setSubmitState(false));
const isBing = model === 'bingai' || model === 'sydney';
// refresh title
if (requestMessage.parentMessageId == '00000000-0000-0000-0000-000000000000') {
setTimeout(() => {
dispatch(refreshConversation());
}, 2000);
// in case it takes too long.
setTimeout(() => {
dispatch(refreshConversation());
}, 5000);
}
if (!isBing && convo.conversationId === null && convo.parentMessageId === null) {
const { title } = data;
const { conversationId, messageId } = responseMessage;
dispatch(
setConversation({
title,
conversationId,
parentMessageId: messageId,
jailbreakConversationId: null,
conversationSignature: null,
clientId: null,
invocationId: null,
chatGptLabel: model === isCustomModel ? chatGptLabel : null,
promptPrefix: model === isCustomModel ? promptPrefix : null,
latestMessage: null
})
);
} else if (model === 'bingai') {
console.log('Bing data:', data);
const { title } = data;
const { conversationSignature, clientId, conversationId, invocationId } =
responseMessage;
dispatch(
setConversation({
title,
parentMessageId: null,
conversationSignature,
clientId,
conversationId,
invocationId,
latestMessage: null
})
);
} else if (model === 'sydney') {
const { title } = data;
const {
jailbreakConversationId,
parentMessageId,
conversationSignature,
clientId,
conversationId,
invocationId
} = responseMessage;
dispatch(
setConversation({
title,
jailbreakConversationId,
parentMessageId,
conversationSignature,
clientId,
conversationId,
invocationId,
latestMessage: null
})
);
}
};
const errorHandler = (data, currentState, currentMsg) => {
const { initialResponse, messages, _currentMsg, message } = currentState;
console.log('Error:', data);
const errorResponse = {
...data,
error: true,
parentMessageId: currentMsg?.messageId
};
setErrorMessage(data?.text);
dispatch(setSubmitState(false));
dispatch(setMessages([...messages, currentMsg, errorResponse]));
dispatch(setText(message?.text));
dispatch(setError(true));
return;
};
const submitMessage = () => {
ask({ text });
};
useEffect(() => {
inputRef.current?.focus();
if (Object.keys(submission).length === 0) {
return;
}
const currentState = submission;
let currentMsg = { ...currentState.message };
let latestResponseText = '';
const { server, payload } = createPayload(submission);
const onMessage = (e) => {
if (stopStream) {
return;
}
const data = JSON.parse(e.data);
if (data.final) {
convoHandler(data, currentState, currentMsg);
console.log('final', data);
}
if (data.created) {
currentMsg = data.message;
createdHandler(data, currentState, currentMsg);
} else {
let text = data.text || data.response;
if (data.initial) {
console.log(data);
}
if (data.message) {
latestResponseText = text;
messageHandler(text, currentState, currentMsg);
}
// console.log('dataStream', data);
}
};
const events = new SSE(server, {
payload: JSON.stringify(payload),
headers: { 'Content-Type': 'application/json' }
});
events.onopen = function () {
console.log('connection is opened');
};
events.onmessage = onMessage;
events.oncancel = (e) => {
cancelHandler(latestResponseText, currentState, currentMsg);
};
events.onerror = function (e) {
console.log('error in opening conn.');
events.close();
const data = JSON.parse(e.data);
errorHandler(data, currentState, currentMsg);
};
events.stream();
return () => {
events.removeEventListener('message', onMessage);
const isCancelled = events.readyState <= 1;
events.close();
if (isCancelled) {
const e = new Event('cancel');
events.dispatchEvent(e);
}
};
}, [submission]);
const handleRegenerate = () => {
if (latestMessage && !latestMessage?.isCreatedByUser) regenerate(latestMessage);
};
const handleStopGenerating = () => {
stopGenerating();
};
const handleKeyDown = (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
}
if (e.key === 'Enter' && !e.shiftKey) {
if (!isComposing.current) submitMessage();
}
};
const handleKeyUp = (e) => {
if (e.key === 'Enter' && e.shiftKey) {
return console.log('Enter + Shift');
}
if (isSubmitting) {
return;
}
};
const handleCompositionStart = (e) => {
isComposing.current = true;
};
const handleCompositionEnd = (e) => {
isComposing.current = false;
};
const changeHandler = (e) => {
const { value } = e.target;
// if (isSubmitting && (value === '' || value === '\n')) {
// return;
// }
dispatch(setText(value));
};
const tryAgain = (e) => {
e.preventDefault();
dispatch(setError(false));
};
return (
<div className="input-panel md:bg-vert-light-gradient dark:md:bg-vert-dark-gradient fixed bottom-0 left-0 w-full border-t bg-white py-2 dark:border-white/20 dark:bg-gray-800 md:absolute md:border-t-0 md:border-transparent md:bg-transparent md:dark:border-transparent md:dark:bg-transparent">
<form className="stretch mx-2 flex flex-row gap-3 last:mb-2 md:pt-2 md:last:mb-6 lg:mx-auto lg:max-w-3xl lg:pt-6">
<div className="relative flex h-full flex-1 md:flex-col">
<span className="order-last ml-1 flex justify-center gap-0 md:order-none md:m-auto md:mb-2 md:w-full md:gap-2">
{isSubmitting ? (
<button
onClick={handleStopGenerating}
className="input-panel-button btn btn-neutral flex justify-center gap-2 border-0 md:border"
type="button"
>
<StopGeneratingIcon />
<span className="hidden md:block">Stop generating</span>
</button>
) : latestMessage && !latestMessage?.isCreatedByUser ? (
<button
onClick={handleRegenerate}
className="input-panel-button btn btn-neutral flex justify-center gap-2 border-0 md:border"
type="button"
>
<RegenerateIcon />
<span className="hidden md:block">Regenerate response</span>
</button>
) : null}
</span>
<div
className={`relative flex flex-grow flex-col rounded-md border border-black/10 ${
disabled ? 'bg-gray-100' : 'bg-white'
} py-2 shadow-[0_0_10px_rgba(0,0,0,0.10)] dark:border-gray-900/50 ${
disabled ? 'dark:bg-gray-900' : 'dark:bg-gray-700'
} dark:text-white dark:shadow-[0_0_15px_rgba(0,0,0,0.10)] md:py-3 md:pl-4`}
>
<ModelMenu />
<TextareaAutosize
tabIndex="0"
autoFocus
ref={inputRef}
// style={{maxHeight: '200px', height: '24px', overflowY: 'hidden'}}
rows="1"
value={disabled || isNotAppendable ? '' : text}
onKeyUp={handleKeyUp}
onKeyDown={handleKeyDown}
onChange={changeHandler}
onCompositionStart={handleCompositionStart}
onCompositionEnd={handleCompositionEnd}
placeholder={
disabled
? 'Choose another model or customize GPT again'
: isNotAppendable
? 'Edit your message or Regenerate.'
: ''
}
disabled={disabled || isNotAppendable}
className="m-0 h-auto max-h-52 resize-none overflow-auto border-0 bg-transparent p-0 pl-12 pr-8 leading-6 placeholder:text-sm focus:outline-none focus:ring-0 focus-visible:ring-0 dark:bg-transparent md:pl-8"
/>
<SubmitButton
submitMessage={submitMessage}
disabled={disabled || isNotAppendable}
/>
</div>
</div>
</form>
<Footer />
</div>
);
}
+35
View File
@@ -0,0 +1,35 @@
import React, { useState } from 'react';
import Clipboard from '../svg/Clipboard';
import CheckMark from '../svg/CheckMark';
export default function Embed({ children, language = '', code, matched }) {
const [buttonText, setButtonText] = useState('Copy code');
const isClicked = buttonText === 'Copy code';
const clickHandler = () => {
navigator.clipboard.writeText(code.trim());
setButtonText('Copied!');
setTimeout(() => {
setButtonText('Copy code');
}, 3000);
};
return (
<pre>
<div className="mb-4 rounded-md bg-black">
<div className="relative flex items-center rounded-tl-md rounded-tr-md bg-gray-800 px-4 py-2 font-sans text-xs text-gray-200">
<span className="">{language === 'javascript' && !matched ? '' : language}</span>
<button
className="ml-auto flex gap-2"
onClick={clickHandler}
disabled={!isClicked}
>
{isClicked ? <Clipboard /> : <CheckMark />}
{buttonText}
</button>
</div>
<div className="overflow-y-auto p-4">{children}</div>
</div>
</pre>
);
}
@@ -0,0 +1,18 @@
import React, { useState, useEffect } from 'react';
import hljs from 'highlight.js';
import languages from '~/utils/languages';
export default function Highlight({language, code}) {
const [highlightedCode, setHighlightedCode] = useState(code);
const lang = languages.has(language) ? language : 'shell';
useEffect(() => {
setHighlightedCode(hljs.highlight(code, { language: lang }).value);
}, [code, lang]);
return (
<pre>
<code className={`language-${lang}`} dangerouslySetInnerHTML={{__html: highlightedCode}}/>
</pre>
);
}

Some files were not shown because too many files have changed in this diff Show More