diff --git a/.gitignore b/.gitignore index 0e474a7..5378cea 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ go.work # IDE .idea +config diff --git a/go.mod b/go.mod index f876215..1cef9ac 100644 --- a/go.mod +++ b/go.mod @@ -3,24 +3,25 @@ module github.com/kkkunny/HuggingChatAPI go 1.22.2 require ( - github.com/go-chi/chi/v5 v5.1.0 github.com/imroc/req/v3 v3.48.0 - github.com/kkkunny/stl v0.0.0-20241014160331-79c369318400 + github.com/kkkunny/stl v0.0.0-20241105013004-33daba72dee7 + github.com/labstack/echo/v4 v4.12.0 + github.com/labstack/gommon v0.4.2 github.com/sashabaranov/go-openai v1.32.3 github.com/satori/go.uuid v1.2.0 + golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c ) require ( github.com/andybalholm/brotli v1.1.1 // indirect github.com/cloudflare/circl v1.5.0 // indirect - github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/google/pprof v0.0.0-20241023014458-598669927662 // indirect github.com/gookit/color v1.5.4 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/klauspost/compress v1.17.11 // indirect - github.com/kr/pretty v0.3.1 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/onsi/ginkgo/v2 v2.20.2 // indirect @@ -28,14 +29,16 @@ require ( github.com/quic-go/qpack v0.5.1 // indirect github.com/quic-go/quic-go v0.48.1 // indirect github.com/refraction-networking/utls v1.6.7 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect go.uber.org/mock v0.5.0 // indirect golang.org/x/crypto v0.28.0 // indirect - golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect golang.org/x/mod v0.21.0 // indirect golang.org/x/net v0.30.0 // indirect golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.26.0 // indirect golang.org/x/text v0.19.0 // indirect golang.org/x/tools v0.26.0 // indirect + gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 // indirect ) diff --git a/go.sum b/go.sum index 83d2843..6bbf874 100644 --- a/go.sum +++ b/go.sum @@ -1,31 +1,19 @@ -github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= -github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= -github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= -github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys= github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s= -github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= -github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= -github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/elastic/go-freelru v0.13.0 h1:TKKY6yCfNNNky7Pj9xZAOEpBcdNgZJfihEftOb55omg= +github.com/elastic/go-freelru v0.13.0/go.mod h1:bSdWT4M0lW79K8QbX6XY2heQYSCqD7THoYf82pT/H3I= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 h1:y3N7Bm7Y9/CtpiVkw/ZWj6lSlDF3F74SfKwfTCer72Q= -github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/google/pprof v0.0.0-20241023014458-598669927662 h1:SKMkD83p7FwUqKmBsPdLHF5dNyxq3jOWwu9w9UyH5vA= github.com/google/pprof v0.0.0-20241023014458-598669927662/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= @@ -35,104 +23,76 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/imroc/req/v3 v3.43.3 h1:WdZhpUev9THtuwEZsW2LOYacl12fm7IkB7OgACv40+k= -github.com/imroc/req/v3 v3.43.3/go.mod h1:SQIz5iYop16MJxbo8ib+4LnostGCok8NQf8ToyQc2xA= github.com/imroc/req/v3 v3.48.0 h1:IYuMGetuwLzOOTzDCquDqs912WNwpsPK0TBXWPIvoqg= github.com/imroc/req/v3 v3.48.0/go.mod h1:weam9gmyb00QnOtu6HXSnk44dNFkIUQb5QdMx13FeUU= -github.com/kkkunny/stl v0.0.0-20240430010551-9000ae3cf956 h1:ATZaOWmZ9zB65S/5BI5DHLq5IK9hqBVhJG7njhScLTI= -github.com/kkkunny/stl v0.0.0-20240430010551-9000ae3cf956/go.mod h1:/1aKBCEXG7ldA2kGdP4itHcqwORm99D/nb7BXXWvc4g= -github.com/kkkunny/stl v0.0.0-20241014160331-79c369318400 h1:unQsfsdsKXInPSOh/7x+BfGLrg6thXwWG9A5/xrAmSo= -github.com/kkkunny/stl v0.0.0-20241014160331-79c369318400/go.mod h1:K0PMQJ5hIq9qCNhfTR9UTXUh2njVCbM/elaBT7GS2i8= -github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= -github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/kkkunny/stl v0.0.0-20241105013004-33daba72dee7 h1:ZSSwBX8sWhRRTYgWLBWsSXwm5mpNm7WOGqFhDr2/MEc= +github.com/kkkunny/stl v0.0.0-20241105013004-33daba72dee7/go.mod h1:K0PMQJ5hIq9qCNhfTR9UTXUh2njVCbM/elaBT7GS2i8= github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0= +github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM= +github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= +github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/onsi/ginkgo/v2 v2.16.0 h1:7q1w9frJDzninhXxjZd+Y/x54XNjG/UlRLIYPZafsPM= -github.com/onsi/ginkgo/v2 v2.16.0/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs= github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4= github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag= -github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= -github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= -github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= -github.com/quic-go/quic-go v0.41.0 h1:aD8MmHfgqTURWNJy48IYFg2OnxwHT3JL7ahGs73lb4k= -github.com/quic-go/quic-go v0.41.0/go.mod h1:qCkNjqczPEvgsOnxZ0eCD14lv+B2LHlFAB++CNOh9hA= github.com/quic-go/quic-go v0.48.1 h1:y/8xmfWI9qmGTc+lBr4jKRUWLGSlSigv847ULJ4hYXA= github.com/quic-go/quic-go v0.48.1/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs= -github.com/refraction-networking/utls v1.6.3 h1:MFOfRN35sSx6K5AZNIoESsBuBxS2LCgRilRIdHb6fDc= -github.com/refraction-networking/utls v1.6.3/go.mod h1:yil9+7qSl+gBwJqztoQseO6Pr3h62pQoY1lXiNR/FPs= github.com/refraction-networking/utls v1.6.7 h1:zVJ7sP1dJx/WtVuITug3qYUq034cDq9B2MR1K67ULZM= github.com/refraction-networking/utls v1.6.7/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/sashabaranov/go-openai v1.23.0 h1:KYW97r5yc35PI2MxeLZ3OofecB/6H+yxvSNqiT9u8is= -github.com/sashabaranov/go-openai v1.23.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= github.com/sashabaranov/go-openai v1.32.3 h1:6xZ393PbZFoJrgwveBXVZggmyH7zdp4joUdnCy7FFD8= github.com/sashabaranov/go-openai v1.32.3/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= +github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= -go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= -go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= -golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= -golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY= golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= -golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= -golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= -golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= -google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/handler/chat_completions.go b/handler/chat_completions.go index 417f64e..72f525d 100644 --- a/handler/chat_completions.go +++ b/handler/chat_completions.go @@ -3,13 +3,14 @@ package handler import ( "encoding/json" "fmt" - "io" "net/http" "strings" "time" stlslices "github.com/kkkunny/stl/container/slices" + stlerr "github.com/kkkunny/stl/error" stlval "github.com/kkkunny/stl/value" + "github.com/labstack/echo/v4" "github.com/satori/go.uuid" @@ -19,33 +20,23 @@ import ( "github.com/kkkunny/HuggingChatAPI/internal/config" ) -func ChatCompletions(w http.ResponseWriter, r *http.Request) { - token := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ") +func ChatCompletions(reqCtx echo.Context) error { + token := strings.TrimPrefix(reqCtx.Request().Header.Get("Authorization"), "Bearer ") cli, err := api.NewAPI(config.HuggingChatDomain, token) if err != nil { - config.Logger.Error(err) - http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) - return + _ = config.Logger.Error(err) + return echo.ErrUnauthorized } - err = cli.RefreshCookie(r.Context()) + err = cli.RefreshCookie(reqCtx.Request().Context()) if err != nil { - config.Logger.Error(err) - http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) - return + _ = config.Logger.Error(err) + return echo.ErrUnauthorized } var req openai.ChatCompletionRequest - body, err := io.ReadAll(r.Body) - if err != nil { - config.Logger.Error(err) - http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) - return - } - err = json.Unmarshal(body, &req) - if err != nil { - config.Logger.Error(err) - http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) - return + if err = stlerr.ErrorWrap(reqCtx.Bind(&req)); err != nil { + _ = config.Logger.Error(err) + return echo.ErrBadRequest } // createConvResp, err := cli.CreateConversation(r.Context(), &api.CreateConversationRequest{ @@ -65,26 +56,21 @@ func ChatCompletions(w http.ResponseWriter, r *http.Request) { // }() }() - convs, err := cli.ListConversations(r.Context()) + convs, err := cli.ListConversations(reqCtx.Request().Context()) if err != nil { - config.Logger.Error(err) - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - return + return err } convs = stlslices.Filter(convs, func(_ int, conv *api.SimpleConversationInfo) bool { return conv.Model == req.Model }) if len(convs) == 0 { - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - return + return stlerr.Errorf("not found valid conversation") } convID := stlslices.Random(convs).ID - convInfo, err := cli.ConversationInfo(r.Context(), &api.ConversationInfoRequest{ConversationID: convID}) + convInfo, err := cli.ConversationInfo(reqCtx.Request().Context(), &api.ConversationInfoRequest{ConversationID: convID}) if err != nil { - config.Logger.Error(err) - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - return + return err } msgStrList := make([]string, len(req.Messages)+1) @@ -95,27 +81,20 @@ func ChatCompletions(w http.ResponseWriter, r *http.Request) { prompt := fmt.Sprintf("%s\nassistant: ", strings.Join(msgStrList, "")) msgID := stlval.Ternary(stlslices.Last(convInfo.Messages).From != "system", stlslices.Last(convInfo.Messages).ID, uuid.NewV4().String()) - chatResp, err := cli.ChatConversation(r.Context(), &api.ChatConversationRequest{ + chatResp, err := cli.ChatConversation(reqCtx.Request().Context(), &api.ChatConversationRequest{ ConversationID: convID, ID: msgID, Inputs: prompt, }) if err != nil { - config.Logger.Error(err) - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - return + return err } handler := stlval.Ternary(req.Stream, chatCompletionsWithStream, chatCompletionsNoStream) - err = handler(w, msgID, convInfo, chatResp) - if err != nil { - config.Logger.Error(err) - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - return - } + return handler(reqCtx, msgID, convInfo, chatResp) } -func chatCompletionsNoStream(w http.ResponseWriter, msgID string, convInfo *api.ConversationInfoResponse, resp *api.ChatConversationResponse) error { +func chatCompletionsNoStream(reqCtx echo.Context, msgID string, convInfo *api.ConversationInfoResponse, resp *api.ChatConversationResponse) error { var tokenCount uint64 var contents []openai.ChatMessagePart for msg := range resp.Stream { @@ -144,7 +123,7 @@ func chatCompletionsNoStream(w http.ResponseWriter, msgID string, convInfo *api. } case api.StreamMessageTypeStatus, api.StreamMessageTypeTool: default: - config.Logger.Warnf("unknown stream msg type `%s`", msg.Type) + _ = config.Logger.Warnf("unknown stream msg type `%s`", msg.Type) } } @@ -153,7 +132,8 @@ func chatCompletionsNoStream(w http.ResponseWriter, msgID string, convInfo *api. reply = contents[0].Text contents = nil } - data, err := json.Marshal(&openai.ChatCompletionResponse{ + + return stlerr.ErrorWrap(reqCtx.JSONPretty(http.StatusOK, &openai.ChatCompletionResponse{ ID: msgID, Object: "chat.completion", Created: time.Now().Unix(), @@ -174,29 +154,23 @@ func chatCompletionsNoStream(w http.ResponseWriter, msgID string, convInfo *api. CompletionTokens: int(tokenCount), TotalTokens: int(tokenCount), }, - }) - if err != nil { - return err - } - w.Header().Set("Content-Type", "application/json") - _, err = fmt.Fprint(w, string(data)) - return err + }, " ")) } -func chatCompletionsWithStream(w http.ResponseWriter, msgID string, convInfo *api.ConversationInfoResponse, resp *api.ChatConversationResponse) error { - w.Header().Set("Content-Type", "text/event-stream") - w.Header().Set("Cache-Control", "no-cache") - w.Header().Set("Connection", "keep-alive") - w.Header().Set("Transfer-Encoding", "chunked") +func chatCompletionsWithStream(reqCtx echo.Context, msgID string, convInfo *api.ConversationInfoResponse, resp *api.ChatConversationResponse) error { + reqCtx.Response().Header().Set("Content-Type", "text/event-stream") + reqCtx.Response().Header().Set("Cache-Control", "no-cache") + reqCtx.Response().Header().Set("Connection", "keep-alive") + reqCtx.Response().Header().Set("Transfer-Encoding", "chunked") - flusher := w.(http.Flusher) + flusher := reqCtx.Response().Writer.(http.Flusher) for msg := range resp.Stream { switch msg.Type { case api.StreamMessageTypeError: return msg.Error case api.StreamMessageTypeFinalAnswer: - data, err := json.Marshal(&openai.ChatCompletionStreamResponse{ + data, err := stlerr.ErrorWith(json.Marshal(&openai.ChatCompletionStreamResponse{ ID: msgID, Object: "chat.completion", Created: time.Now().Unix(), @@ -207,16 +181,16 @@ func chatCompletionsWithStream(w http.ResponseWriter, msgID string, convInfo *ap FinishReason: "stop", }, }, - }) + })) if err != nil { return err } - _, err = fmt.Fprint(w, "data: "+string(data)+"\n\n") + _, err = stlerr.ErrorWith(fmt.Fprint(reqCtx.Response().Writer, "data: "+string(data)+"\n\n")) if err != nil { return err } flusher.Flush() - _, err = fmt.Fprint(w, "data: [DONE]\n\n") + _, err = stlerr.ErrorWith(fmt.Fprint(reqCtx.Response().Writer, "data: [DONE]\n\n")) if err != nil { return err } @@ -226,7 +200,7 @@ func chatCompletionsWithStream(w http.ResponseWriter, msgID string, convInfo *ap if msg.Token != nil { reply = *msg.Token } - data, err := json.Marshal(&openai.ChatCompletionStreamResponse{ + data, err := stlerr.ErrorWith(json.Marshal(&openai.ChatCompletionStreamResponse{ ID: msgID, Object: "chat.completion", Created: time.Now().Unix(), @@ -240,18 +214,18 @@ func chatCompletionsWithStream(w http.ResponseWriter, msgID string, convInfo *ap }, }, }, - }) + })) if err != nil { return err } - _, err = fmt.Fprint(w, "data: "+string(data)+"\n") + _, err = stlerr.ErrorWith(fmt.Fprint(reqCtx.Response().Writer, "data: "+string(data)+"\n")) if err != nil { return err } flusher.Flush() case api.StreamMessageTypeStatus, api.StreamMessageTypeTool, api.StreamMessageTypeFile: default: - config.Logger.Warnf("unknown stream msg type `%s`", msg.Type) + _ = config.Logger.Warnf("unknown stream msg type `%s`", msg.Type) } } return nil diff --git a/handler/list_models.go b/handler/list_models.go index 7c43d8f..a3b718c 100644 --- a/handler/list_models.go +++ b/handler/list_models.go @@ -1,41 +1,37 @@ package handler import ( - "encoding/json" - "fmt" "net/http" "strings" stlslices "github.com/kkkunny/stl/container/slices" + stlerr "github.com/kkkunny/stl/error" + "github.com/labstack/echo/v4" "github.com/sashabaranov/go-openai" "github.com/kkkunny/HuggingChatAPI/internal/api" "github.com/kkkunny/HuggingChatAPI/internal/config" ) -func ListModels(w http.ResponseWriter, r *http.Request) { - token := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ") +func ListModels(reqCtx echo.Context) error { + token := strings.TrimPrefix(reqCtx.Request().Header.Get("Authorization"), "Bearer ") cli, err := api.NewAPI(config.HuggingChatDomain, token) if err != nil { - config.Logger.Error(err) - http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) - return + _ = config.Logger.Error(err) + return echo.ErrUnauthorized } - err = cli.RefreshCookie(r.Context()) + err = cli.RefreshCookie(reqCtx.Request().Context()) if err != nil { - config.Logger.Error(err) - http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) - return + _ = config.Logger.Error(err) + return echo.ErrUnauthorized } - models, err := cli.ListModels(r.Context()) + models, err := cli.ListModels(reqCtx.Request().Context()) if err != nil { - config.Logger.Error(err) - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - return + return err } - data, err := json.Marshal(&openai.ModelsList{ + return stlerr.ErrorWrap(reqCtx.JSONPretty(http.StatusOK, &openai.ModelsList{ Models: stlslices.Map(models, func(_ int, model *api.ModelInfo) openai.Model { return openai.Model{ CreatedAt: 1692901427, @@ -44,17 +40,5 @@ func ListModels(w http.ResponseWriter, r *http.Request) { OwnedBy: "system", } }), - }) - if err != nil { - config.Logger.Error(err) - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - return - } - w.Header().Set("Content-Type", "application/json") - _, err = fmt.Fprint(w, string(data)) - if err != nil { - config.Logger.Error(err) - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - return - } + }, "")) } diff --git a/internal/api/api.go b/internal/api/api.go index 11d6d04..5a4ba50 100644 --- a/internal/api/api.go +++ b/internal/api/api.go @@ -15,6 +15,7 @@ import ( "github.com/imroc/req/v3" stlslices "github.com/kkkunny/stl/container/slices" + stlerr "github.com/kkkunny/stl/error" ) type Api struct { @@ -38,7 +39,7 @@ func (api *Api) SetToken(token string) error { if err == nil { res := regexp.MustCompile(`username=(.+?)&password=(.+)`).FindStringSubmatch(string(account)) if len(res) != 3 { - return errors.New("invalid token") + return stlerr.Errorf("invalid token") } api.cookieMgr = newAccountCookieMgr(res[1], res[2]) return nil @@ -73,20 +74,20 @@ func (api *Api) RefreshCookie(ctx context.Context) error { } else if isLogin { return nil } - return errors.New("login failed") + return stlerr.Errorf("login failed") } func (api *Api) CheckLogin(ctx context.Context) (bool, error) { - httpResp, err := api.client.R(). + httpResp, err := stlerr.ErrorWith(api.client.R(). SetContext(ctx). SetHeaders(map[string]string{ "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", }). - Get(fmt.Sprintf("%s/chat/", api.domain)) + Get(fmt.Sprintf("%s/chat/", api.domain))) if err != nil { return false, err } else if httpResp.GetStatusCode() != http.StatusOK { - return false, fmt.Errorf("http error: code=%d, status=%s", httpResp.GetStatusCode(), httpResp.GetStatus()) + return false, stlerr.Errorf("http error: code=%d, status=%s", httpResp.GetStatusCode(), httpResp.GetStatus()) } return !strings.Contains(httpResp.String(), "action=\"/chat/login\""), nil } @@ -100,19 +101,19 @@ type ModelInfo struct { } func (api *Api) ListModels(ctx context.Context) (resp []*ModelInfo, err error) { - httpResp, err := api.client.R(). + httpResp, err := stlerr.ErrorWith(api.client.R(). SetContext(ctx). SetSuccessResult(make(map[string]any)). - Get(fmt.Sprintf("%s/chat/models/__data.json?x-sveltekit-invalidated=10", api.domain)) + Get(fmt.Sprintf("%s/chat/models/__data.json?x-sveltekit-invalidated=10", api.domain))) if err != nil { return nil, err } else if httpResp.GetStatusCode() != http.StatusOK { - return nil, fmt.Errorf("http error: code=%d, status=%s", httpResp.GetStatusCode(), httpResp.String()) + return nil, stlerr.Errorf("http error: code=%d, status=%s", httpResp.GetStatusCode(), httpResp.String()) } defer func() { if panicErr := recover(); panicErr != nil { - err = fmt.Errorf("parse resp error: err=%+v", panicErr) + err = stlerr.Errorf("parse resp error: err=%+v", panicErr) } }() @@ -165,15 +166,15 @@ type CreateConversationResponse struct { } func (api *Api) CreateConversation(ctx context.Context, req *CreateConversationRequest) (*CreateConversationResponse, error) { - httpResp, err := api.client.R(). + httpResp, err := stlerr.ErrorWith(api.client.R(). SetContext(ctx). SetBodyJsonMarshal(req). SetSuccessResult(CreateConversationResponse{}). - Post(fmt.Sprintf("%s/chat/conversation", api.domain)) + Post(fmt.Sprintf("%s/chat/conversation", api.domain))) if err != nil { return nil, err } else if httpResp.GetStatusCode() != http.StatusOK { - return nil, fmt.Errorf("http error: code=%d, status=%s", httpResp.GetStatusCode(), httpResp.String()) + return nil, stlerr.Errorf("http error: code=%d, status=%s", httpResp.GetStatusCode(), httpResp.String()) } conversation := httpResp.SuccessResult().(*CreateConversationResponse) return conversation, nil @@ -184,13 +185,13 @@ type DeleteConversationRequest struct { } func (api *Api) DeleteConversation(ctx context.Context, req *DeleteConversationRequest) error { - httpResp, err := api.client.R(). + httpResp, err := stlerr.ErrorWith(api.client.R(). SetContext(ctx). - Delete(fmt.Sprintf("%s/chat/conversation/%s", api.domain, req.ConversationID)) + Delete(fmt.Sprintf("%s/chat/conversation/%s", api.domain, req.ConversationID))) if err != nil { return err } else if httpResp.GetStatusCode() != http.StatusOK { - return fmt.Errorf("http error: code=%d, status=%s", httpResp.GetStatusCode(), httpResp.String()) + return stlerr.Errorf("http error: code=%d, status=%s", httpResp.GetStatusCode(), httpResp.String()) } return nil } @@ -203,24 +204,24 @@ type SimpleConversationInfo struct { } func (api *Api) ListConversations(ctx context.Context) (resp []*SimpleConversationInfo, err error) { - httpResp, err := api.client.R(). + httpResp, err := stlerr.ErrorWith(api.client.R(). SetContext(ctx). - Get(fmt.Sprintf("%s/chat/models/__data.json?x-sveltekit-invalidated=10", api.domain)) + Get(fmt.Sprintf("%s/chat/models/__data.json?x-sveltekit-invalidated=10", api.domain))) if err != nil { return nil, err } else if httpResp.GetStatusCode() != http.StatusOK { - return nil, fmt.Errorf("http error: code=%d, status=%s", httpResp.GetStatusCode(), httpResp.String()) + return nil, stlerr.Errorf("http error: code=%d, status=%s", httpResp.GetStatusCode(), httpResp.String()) } defer func() { if panicErr := recover(); panicErr != nil { - err = fmt.Errorf("parse resp error: err=%+v", panicErr) + err = stlerr.Errorf("parse resp error: err=%+v", panicErr) } }() rawStr := "[" + regexp.MustCompile(`}\s*{`).ReplaceAllString(httpResp.String(), "},{") + "]" var rawResp []map[string]any - err = json.Unmarshal([]byte(rawStr), &rawResp) + err = stlerr.ErrorWrap(json.Unmarshal([]byte(rawStr), &rawResp)) if err != nil { return nil, err } @@ -255,7 +256,7 @@ func (api *Api) ListConversations(ctx context.Context) (resp []*SimpleConversati }) return conversationInfos, nil } - return nil, errors.New("not found conversion data") + return nil, stlerr.Errorf("not found conversion data") } type ConversationInfoRequest struct { @@ -280,19 +281,19 @@ type Message struct { } func (api *Api) ConversationInfo(ctx context.Context, req *ConversationInfoRequest) (resp *ConversationInfoResponse, err error) { - httpResp, err := api.client.R(). + httpResp, err := stlerr.ErrorWith(api.client.R(). SetContext(ctx). SetSuccessResult(make(map[string]any)). - Get(fmt.Sprintf("%s/chat/conversation/%s/__data.json?x-sveltekit-invalidated=01", api.domain, req.ConversationID)) + Get(fmt.Sprintf("%s/chat/conversation/%s/__data.json?x-sveltekit-invalidated=01", api.domain, req.ConversationID))) if err != nil { return nil, err } else if httpResp.GetStatusCode() != http.StatusOK { - return nil, fmt.Errorf("http error: code=%d, status=%s", httpResp.GetStatusCode(), httpResp.String()) + return nil, stlerr.Errorf("http error: code=%d, status=%s", httpResp.GetStatusCode(), httpResp.String()) } defer func() { if panicErr := recover(); panicErr != nil { - err = fmt.Errorf("parse resp error: err=%+v", panicErr) + err = stlerr.Errorf("parse resp error: err=%+v", panicErr) } }() @@ -424,12 +425,12 @@ func (api *Api) ChatConversation(ctx context.Context, req *ChatConversationReque if len(req.Tools) == 0 { req.Tools = make([]string, 0) } - reqBody, err := json.Marshal(req) + reqBody, err := stlerr.ErrorWith(json.Marshal(req)) if err != nil { return nil, err } - resp, err := api.client.R(). + resp, err := stlerr.ErrorWith(api.client.R(). SetContext(ctx). SetHeaders(map[string]string{ "authority": "huggingface.co", @@ -445,11 +446,11 @@ func (api *Api) ChatConversation(ctx context.Context, req *ChatConversationReque }). SetFormData(map[string]string{"data": string(reqBody)}). DisableAutoReadResponse(). - Post(fmt.Sprintf("%s/chat/conversation/%s", api.domain, req.ConversationID)) + Post(fmt.Sprintf("%s/chat/conversation/%s", api.domain, req.ConversationID))) if err != nil { return nil, err } else if resp.GetStatusCode() != http.StatusOK { - return nil, fmt.Errorf("http error: code=%d, status=%s", resp.GetStatusCode(), resp.String()) + return nil, stlerr.Errorf("http error: code=%d, status=%s", resp.GetStatusCode(), resp.String()) } reader := bufio.NewReader(resp.Body) @@ -461,7 +462,7 @@ func (api *Api) ChatConversation(ctx context.Context, req *ChatConversationReque }() for !resp.Close { - line, err := reader.ReadString('\n') + line, err := stlerr.ErrorWith(reader.ReadString('\n')) if err != nil && errors.Is(err, io.EOF) { break } else if err != nil { @@ -474,7 +475,7 @@ func (api *Api) ChatConversation(ctx context.Context, req *ChatConversationReque } var msg StreamMessage - err = json.Unmarshal([]byte(data), &msg) + err = stlerr.ErrorWrap(json.Unmarshal([]byte(data), &msg)) if err != nil { msgChan <- StreamMessage{Type: StreamMessageTypeError, Error: err} break diff --git a/internal/api/cookie_cache.go b/internal/api/cookie_cache.go index bd88bda..2db36af 100644 --- a/internal/api/cookie_cache.go +++ b/internal/api/cookie_cache.go @@ -2,6 +2,7 @@ package api import ( "encoding/json" + "errors" "net/http" "os" "path/filepath" @@ -27,32 +28,32 @@ func newCookieCache() *cookieCache { } func (cache *cookieCache) Load() error { - data, err := os.ReadFile(config.CookieCachePath) - if err != nil && os.IsNotExist(err) { + data, err := stlerr.ErrorWith(os.ReadFile(config.CookieCachePath)) + if err != nil && errors.Is(err, os.ErrNotExist) { return nil } else if err != nil { return err } - err = json.Unmarshal(data, &cache.data) + err = stlerr.ErrorWrap(json.Unmarshal(data, &cache.data)) return err } func (cache *cookieCache) Save() error { - err := os.MkdirAll(filepath.Dir(config.CookieCachePath), 0750) + err := stlerr.ErrorWrap(os.MkdirAll(filepath.Dir(config.CookieCachePath), 0750)) if err != nil { return err } - file, err := os.OpenFile(config.CookieCachePath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666) + file, err := stlerr.ErrorWith(os.OpenFile(config.CookieCachePath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)) if err != nil { return err } defer file.Close() - data, err := json.MarshalIndent(cache.data, "", " ") + data, err := stlerr.ErrorWith(json.MarshalIndent(cache.data, "", " ")) if err != nil { return err } - _, err = file.Write(data) + _, err = stlerr.ErrorWith(file.Write(data)) return err } diff --git a/internal/api/login.go b/internal/api/login.go index e08cffd..2a2853e 100644 --- a/internal/api/login.go +++ b/internal/api/login.go @@ -7,6 +7,7 @@ import ( "net/url" "github.com/imroc/req/v3" + stlerr "github.com/kkkunny/stl/error" "golang.org/x/exp/maps" "github.com/kkkunny/HuggingChatAPI/internal/config" @@ -61,15 +62,15 @@ type loginResponse struct { } func login(ctx context.Context, cli *req.Client, req *loginRequest) (*loginResponse, error) { - resp, err := cli.R(). + resp, err := stlerr.ErrorWith(cli.R(). SetContext(ctx). SetContentType("application/x-www-form-urlencoded"). SetBodyString(fmt.Sprintf("username=%s&password=%s", req.Username, req.Password)). - Post(fmt.Sprintf("%s/login", config.HuggingChatDomain)) + Post(fmt.Sprintf("%s/login", config.HuggingChatDomain))) if err != nil { return nil, err } else if resp.GetStatusCode() != http.StatusFound { - return nil, fmt.Errorf("http error: code=%d, status=%s", resp.GetStatusCode(), resp.GetStatus()) + return nil, stlerr.Errorf("http error: code=%d, status=%s", resp.GetStatusCode(), resp.GetStatus()) } return &loginResponse{Cookies: resp.Cookies()}, nil } @@ -80,19 +81,19 @@ type chatLoginResponse struct { } func chatLogin(ctx context.Context, cli *req.Client) (*chatLoginResponse, error) { - resp, err := cli.R(). + resp, err := stlerr.ErrorWith(cli.R(). SetContext(ctx). SetContentType("application/x-www-form-urlencoded"). SetHeaders(map[string]string{ "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", }). - Post(fmt.Sprintf("%s/chat/login", config.HuggingChatDomain)) + Post(fmt.Sprintf("%s/chat/login", config.HuggingChatDomain))) if err != nil { return nil, err } else if resp.GetStatusCode() != http.StatusSeeOther { - return nil, fmt.Errorf("http error: code=%d, status=%s", resp.GetStatusCode(), resp.GetStatus()) + return nil, stlerr.Errorf("http error: code=%d, status=%s", resp.GetStatusCode(), resp.GetStatus()) } - location, err := resp.Location() + location, err := stlerr.ErrorWith(resp.Location()) if err != nil { return nil, err } @@ -107,18 +108,18 @@ type authorizeOauthResponse struct { } func authorizeOauth(ctx context.Context, cli *req.Client, urlStr string) (*authorizeOauthResponse, error) { - resp, err := cli.R(). + resp, err := stlerr.ErrorWith(cli.R(). SetContext(ctx). SetHeaders(map[string]string{ "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", }). - Get(urlStr) + Get(urlStr)) if err != nil { return nil, err } else if resp.GetStatusCode() != http.StatusSeeOther { - return nil, fmt.Errorf("http error: code=%d, status=%s", resp.GetStatusCode(), resp.GetStatus()) + return nil, stlerr.Errorf("http error: code=%d, status=%s", resp.GetStatusCode(), resp.GetStatus()) } - location, err := resp.Location() + location, err := stlerr.ErrorWith(resp.Location()) if err != nil { return nil, err } @@ -130,9 +131,9 @@ type loginCallbackResponse struct { } func loginCallback(ctx context.Context, cli *req.Client, urlStr string) (*loginCallbackResponse, error) { - resp, err := cli.R(). + resp, err := stlerr.ErrorWith(cli.R(). SetContext(ctx). - Get(urlStr) + Get(urlStr)) if err != nil { return nil, err } else if resp.GetStatusCode() != http.StatusFound { diff --git a/main.go b/main.go index 5b1fae5..0370875 100644 --- a/main.go +++ b/main.go @@ -1,29 +1,26 @@ package main import ( - "net/http" - - "github.com/go-chi/chi/v5" + stlerr "github.com/kkkunny/stl/error" + "github.com/labstack/echo/v4" + "github.com/labstack/gommon/log" "github.com/kkkunny/HuggingChatAPI/handler" "github.com/kkkunny/HuggingChatAPI/internal/config" + "github.com/kkkunny/HuggingChatAPI/middleware" ) func main() { - svr := chi.NewRouter() + svr := echo.New() + svr.HideBanner, svr.HidePort = true, true + svr.Logger.SetLevel(log.OFF) + svr.IPExtractor = echo.ExtractIPFromRealIPHeader() - svr.Use(func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - config.Logger.Infof("Method [%s] %s --> %s", r.Method, r.RemoteAddr, r.URL.Path) - next.ServeHTTP(w, r) - }) - }) + svr.Use(middleware.ErrorHandler, middleware.Logger) - svr.Get("/v1/models", handler.ListModels) - svr.Post("/v1/chat/completions", handler.ChatCompletions) + svr.GET("/v1/models", handler.ListModels) + svr.POST("/v1/chat/completions", handler.ChatCompletions) - config.Logger.Keywordf("listen http: 0.0.0.0:80") - if err := http.ListenAndServe(":80", svr); err != nil { - panic(err) - } + _ = config.Logger.Keywordf("listen http: 0.0.0.0:80") + stlerr.Must(svr.Start(":80")) } diff --git a/middleware/error_handler.go b/middleware/error_handler.go new file mode 100644 index 0000000..e78de1d --- /dev/null +++ b/middleware/error_handler.go @@ -0,0 +1,45 @@ +package middleware + +import ( + "errors" + "net/http" + + stlerr "github.com/kkkunny/stl/error" + "github.com/labstack/echo/v4" + + "github.com/kkkunny/HuggingChatAPI/internal/config" +) + +func ErrorHandler(next echo.HandlerFunc) echo.HandlerFunc { + return func(reqCtx echo.Context) (err error) { + var isPanic bool + + defer func() { + if err != nil { + if !isPanic { + _ = config.Logger.Error(err) + } + var httpErr *echo.HTTPError + if errors.As(err, &httpErr) { + err = httpErr + } else { + err = echo.NewHTTPError(http.StatusInternalServerError) + } + } + }() + + defer func() { + if errObj := recover(); errObj != nil { + isPanic = true + _ = config.Logger.Panic(errObj) + var ok bool + err, ok = errObj.(error) + if !ok { + err = stlerr.Errorf("%v", errObj) + } + } + }() + + return next(reqCtx) + } +} diff --git a/middleware/logger.go b/middleware/logger.go new file mode 100644 index 0000000..b70c6dc --- /dev/null +++ b/middleware/logger.go @@ -0,0 +1,14 @@ +package middleware + +import ( + "github.com/labstack/echo/v4" + + "github.com/kkkunny/HuggingChatAPI/internal/config" +) + +func Logger(next echo.HandlerFunc) echo.HandlerFunc { + return func(reqCtx echo.Context) error { + _ = config.Logger.Infof("Method [%s] %s --> %s", reqCtx.Request().Method, reqCtx.RealIP(), reqCtx.Path()) + return next(reqCtx) + } +}