From 0954632b65607c7b1d47b6475b5676fcee708baf Mon Sep 17 00:00:00 2001 From: Simon Ding Date: Tue, 7 Jan 2025 10:25:52 +0800 Subject: [PATCH] AI intergration WIP --- db/const.go | 17 +- db/db.go | 21 +++ go.mod | 27 +++ go.sum | 86 +++++++++ pkg/gemini/gemini.go | 266 +++++++++++++++++++++++++++ pkg/gemini/genimi_test.go | 8 + server/core/torrent.go | 57 ++++++ server/server.go | 2 + server/setting.go | 19 ++ ui/lib/providers/APIs.dart | 1 + ui/lib/providers/settings.dart | 50 ++++++ ui/lib/settings/general.dart | 319 ++++++++++++++++++--------------- 12 files changed, 725 insertions(+), 148 deletions(-) create mode 100644 pkg/gemini/gemini.go create mode 100644 pkg/gemini/genimi_test.go diff --git a/db/const.go b/db/const.go index e6264ef..adffc76 100644 --- a/db/const.go +++ b/db/const.go @@ -24,6 +24,8 @@ const ( SettingMovieSizeLimiter = "movie_size_limiter" SettingAcceptedVideoFormats = "accepted_video_formats" SettingAcceptedSubtitleFormats = "accepted_subtitle_formats" + + SettingAIConfig = "ai_config" ) const ( @@ -46,17 +48,18 @@ const ( const DefaultNamingFormat = "{{.NameCN}} {{.NameEN}} {{if .Year}} ({{.Year}}) {{end}}" -//https://en.wikipedia.org/wiki/Video_file_format +// https://en.wikipedia.org/wiki/Video_file_format var defaultAcceptedVideoFormats = []string{ - ".webm", ".mkv", ".flv", ".vob", ".ogv", ".ogg", ".drc", ".mng", ".avi", ".mts", ".m2ts",".ts", - ".mov", ".qt", ".wmv", ".yuv", ".rm", ".rmvb", ".viv", ".amv", ".mp4", ".m4p", ".m4v", + ".webm", ".mkv", ".flv", ".vob", ".ogv", ".ogg", ".drc", ".mng", ".avi", ".mts", ".m2ts", ".ts", + ".mov", ".qt", ".wmv", ".yuv", ".rm", ".rmvb", ".viv", ".amv", ".mp4", ".m4p", ".m4v", ".mpg", ".mp2", ".mpeg", ".mpe", ".mpv", ".m2v", ".m4v", ".svi", ".3gp", ".3g2", ".nsv", } var defaultAcceptedSubtitleFormats = []string{ - ".ass", ".srt",".vtt", ".webvtt", ".sub", ".idx", + ".ass", ".srt", ".vtt", ".webvtt", ".sub", ".idx", } + type NamingInfo struct { NameCN string NameEN string @@ -96,3 +99,9 @@ type ProwlarrSetting struct { ApiKey string `json:"api_key"` URL string `json:"url"` } + +type AIConfig struct { + Enabled bool `json:"enabled"` + GeminiApiKey string `json:"gemini_api_key"` + GeminiModelName string `json:"gemini_model_name"` +} diff --git a/db/db.go b/db/db.go index 7c85bc9..7c30830 100644 --- a/db/db.go +++ b/db/db.go @@ -760,4 +760,25 @@ func (c *Client) GetAcceptedSubtitleFormats() ([]string, error) { func (c *Client) SetAcceptedSubtitleFormats(key string, v []string) error { return c.setAcceptedFormats(SettingAcceptedSubtitleFormats, v) +} + +func (c *Client) GetAIConfig() (AIConfig, error) { + cfg := c.GetSetting(SettingAIConfig) + var ai AIConfig + if cfg == "" { + return ai, nil + } + err := json.Unmarshal([]byte(cfg), &ai) + if err != nil { + return AIConfig{}, err + } + return ai, nil +} + +func (c *Client) SetAIConfig(cfg *AIConfig) error { + if data, err := json.Marshal(cfg); err != nil { + return err + } else { + return c.SetSetting(SettingAIConfig, string(data)) + } } \ No newline at end of file diff --git a/go.mod b/go.mod index 39b8446..f57e57c 100644 --- a/go.mod +++ b/go.mod @@ -25,6 +25,12 @@ require ( ) require ( + cloud.google.com/go v0.115.0 // indirect + cloud.google.com/go/ai v0.8.0 // indirect + cloud.google.com/go/auth v0.7.1 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.3 // indirect + cloud.google.com/go/compute/metadata v0.5.0 // indirect + cloud.google.com/go/longrunning v0.5.10 // indirect github.com/BurntSushi/toml v1.4.0 // indirect github.com/DATA-DOG/go-sqlmock v1.5.2 // indirect github.com/anacrolix/generics v0.0.3-0.20240902042256-7fb2702ef0ca // indirect @@ -33,8 +39,17 @@ require ( github.com/andybalholm/cascadia v1.3.2 // indirect github.com/blinkbean/dingtalk v1.1.3 // indirect github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible // indirect github.com/go-test/deep v1.0.4 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/generative-ai-go v0.19.0 // indirect + github.com/google/s2a-go v0.1.7 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect + github.com/googleapis/gax-go/v2 v2.12.5 // indirect github.com/gregdel/pushover v1.3.1 // indirect github.com/huandu/xstrings v1.3.2 // indirect github.com/minio/sha256-simd v1.0.0 // indirect @@ -47,7 +62,19 @@ require ( github.com/stretchr/objx v0.5.2 // indirect github.com/technoweenie/multipartstreamer v1.0.1 // indirect github.com/tetratelabs/wazero v1.8.0 // indirect + go.opencensus.io v0.24.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect + go.opentelemetry.io/otel v1.28.0 // indirect + go.opentelemetry.io/otel/metric v1.28.0 // indirect + go.opentelemetry.io/otel/trace v1.28.0 // indirect + golang.org/x/oauth2 v0.21.0 // indirect golang.org/x/sync v0.10.0 // indirect + golang.org/x/time v0.5.0 // indirect + google.golang.org/api v0.188.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d // indirect + google.golang.org/grpc v1.65.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect lukechampine.com/blake3 v1.1.6 // indirect diff --git a/go.sum b/go.sum index 3f91f36..db60607 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,19 @@ ariga.io/atlas v0.19.1-0.20240203083654-5948b60a8e43 h1:GwdJbXydHCYPedeeLt4x/lrl ariga.io/atlas v0.19.1-0.20240203083654-5948b60a8e43/go.mod h1:uj3pm+hUTVN/X5yfdBexHlZv+1Xu5u5ZbZx7+CDavNU= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14= +cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU= +cloud.google.com/go/ai v0.8.0 h1:rXUEz8Wp2OlrM8r1bfmpF2+VKqc1VJpafE3HgzRnD/w= +cloud.google.com/go/ai v0.8.0/go.mod h1:t3Dfk4cM61sytiggo2UyGsDVW3RF1qGZaUKDrZFyqkE= +cloud.google.com/go/auth v0.7.1 h1:Iv1bbpzJ2OIg16m94XI9/tlzZZl3cdeR3nGVGj78N7s= +cloud.google.com/go/auth v0.7.1/go.mod h1:VEc4p5NNxycWQTMQEDQF0bd6aTMb6VgYDXEwiJJQAbs= +cloud.google.com/go/auth/oauth2adapt v0.2.3 h1:MlxF+Pd3OmSudg/b1yZ5lJwoXCEaeedAguodky1PcKI= +cloud.google.com/go/auth/oauth2adapt v0.2.3/go.mod h1:tMQXOfZzFuNuUxOypHlQEXgdfX5cuhwU+ffUuXRJE8I= +cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg= +cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= +cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= +cloud.google.com/go/longrunning v0.5.10 h1:eB/BniENNRKhjz/xgiillrdcH3G74TGSl3BXinGlI7E= +cloud.google.com/go/longrunning v0.5.10/go.mod h1:tljz5guTr5oc/qhlUjBlk7UAIFMOGuPNxkNDZXlLics= crawshaw.io/iox v0.0.0-20181124134642-c51c3df30797/go.mod h1:sXBiorCo8c46JlQV3oXPKINnZ8mcqnye1EkVkqsectk= crawshaw.io/sqlite v0.3.2/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4= entgo.io/ent v0.13.1 h1:uD8QwN1h6SNphdCCzmkMN3feSUzNnVvV/WIkHKMbzOE= @@ -68,12 +81,14 @@ github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cyruzin/golang-tmdb v1.6.3 h1:TKK9h+uuwiDOaFlsVispG1KxqhsSM5Y4ZELnUF3GlqU= github.com/cyruzin/golang-tmdb v1.6.3/go.mod h1:ZSryJLCcY+9TiKU+LbouXKns++YBrM8Tizannr05c+I= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -86,6 +101,12 @@ github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25Kn github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -111,6 +132,11 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +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-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4= github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= @@ -135,6 +161,8 @@ github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVI github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -145,19 +173,34 @@ github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:x github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/generative-ai-go v0.19.0 h1:R71szggh8wHMCUlEMsW2A/3T+5LdEIkiaHSYgSpUgdg= +github.com/google/generative-ai-go v0.19.0/go.mod h1:JYolL13VG7j79kM5BtHz4qwONHkeJQzOCkKXnpqtS/E= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= +github.com/googleapis/gax-go/v2 v2.12.5 h1:8gw9KZK8TiVKB6q3zHY3SBzLnrGp6HQjyfYBYGmXdxA= +github.com/googleapis/gax-go/v2 v2.12.5/go.mod h1:BUDKcWo+RaKq5SC9vVYL0wLADa3VcfswbOMMRmB9H3E= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20190309154008-847fc94819f9/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -271,6 +314,7 @@ github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3O github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= @@ -350,6 +394,18 @@ github.com/zclconf/go-cty v1.8.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUA go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 h1:9G6E0TXzGFVfTnawRzrPl83iHOAV7L8NJiR8RSGYV1g= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0/go.mod h1:azvtTADFQJA8mX80jIH/akaE7h+dbm/sVuaHqN13w74= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= +go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= +go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= +go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= +go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= +go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -361,6 +417,7 @@ golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= @@ -383,10 +440,12 @@ golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= @@ -395,6 +454,8 @@ golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= +golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -411,11 +472,13 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -438,12 +501,15 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +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.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= @@ -452,20 +518,39 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golift.io/starr v1.0.0 h1:IDSaSL+ZYxdLT/Lg//dg/iwZ39LHO3D5CmbLCOgSXbI= golift.io/starr v1.0.0/go.mod h1:xnUwp4vK62bDvozW9QHUYc08m6kjwaZnGw3Db65fQHw= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/api v0.188.0 h1:51y8fJ/b1AaaBRJr4yWm96fPcuxSo0JcegXE3DaHQHw= +google.golang.org/api v0.188.0/go.mod h1:VR0d+2SIiWOYG3r/jdm7adPW9hI2aRv9ETOSCQ9Beag= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20240711142825-46eb208f015d h1:/hmn0Ku5kWij/kjGsrcJeC1T/MrJi2iNWwgAqrihFwc= +google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d h1:kHjw/5UfflP/L5EbledDrcG4C2597RtymmGRZvHiCuY= +google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d/go.mod h1:mw8MG/Qz5wfgYr6VqVCiZcHe/GJEfI+oGGDCohaVgB0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d h1:JU0iKnSg02Gmb5ZdV8nYsKEKsP6o/FGVWTrw4i1DA9A= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= +google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= @@ -491,6 +576,7 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= lukechampine.com/blake3 v1.1.6 h1:H3cROdztr7RCfoaTpGZFQsrqvweFLrqS73j7L7cmR5c= lukechampine.com/blake3 v1.1.6/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= diff --git a/pkg/gemini/gemini.go b/pkg/gemini/gemini.go new file mode 100644 index 0000000..ee8bb02 --- /dev/null +++ b/pkg/gemini/gemini.go @@ -0,0 +1,266 @@ +package gemini + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "polaris/log" + "strings" + + "github.com/google/generative-ai-go/genai" + "google.golang.org/api/option" +) + +func NewClient(apiKey, modelName string) (*Client, error) { + ctx := context.Background() + client, err := genai.NewClient(ctx, option.WithAPIKey(apiKey)) + if err != nil { + return nil, err + } + return &Client{apiKey: apiKey, modelName: modelName, c: client}, nil + +} + +type Client struct { + apiKey string + modelName string + c *genai.Client +} + +type TvInfo struct { + TitleEnglish string `json:"title_english"` + TitleChinses string `json:"title_chinese"` + Season int `json:"season"` + StartEpisode int `json:"start_episode"` + EndEpisode int `json:"end_episode"` + Resolution string `json:"resolution"` + Subtitle string `json:"subtitle"` + ReleaseGroup string `json:"release_group"` + Year int `json:"year"` + AudioLanguage string `json:"audio_language"` + IsCompleteSeason bool `json:"is_complete_season"` +} + +func (c *Client) ParseTvInfo(q string) (*TvInfo, error) { + log.Info(q) + ctx := context.Background() + + model := c.c.GenerativeModel(c.modelName) + + model.ResponseMIMEType = "application/json" + model.ResponseSchema = &genai.Schema{ + Type: genai.TypeObject, + Properties: map[string]*genai.Schema{ + "title_english": {Type: genai.TypeString}, + "title_chinese": {Type: genai.TypeString}, + "season": {Type: genai.TypeInteger, Description: "season number"}, + "start_episode": {Type: genai.TypeInteger}, + "end_episode": {Type: genai.TypeInteger}, + //"episodes": {Type: genai.TypeString}, + "resolution": {Type: genai.TypeString}, + "subtitle": {Type: genai.TypeString}, + "release_group": {Type: genai.TypeString}, + "year": {Type: genai.TypeInteger}, + "audio_language": {Type: genai.TypeString}, + "is_complete_season": {Type: genai.TypeBoolean}, + }, + Required: []string{"title_english", "title_chinese", "season", "start_episode", "resolution"}, + } + + resp, err := model.GenerateContent(ctx, genai.Text(q)) + if err != nil { + return nil, err + } + for _, part := range resp.Candidates[0].Content.Parts { + if txt, ok := part.(genai.Text); ok { + var info TvInfo + if err := json.Unmarshal([]byte(txt), &info); err != nil { + return nil, err + } + return &info, nil + } + } + return nil, fmt.Errorf("not found") + +} + +type MovieInfo struct { + TitleEnglish string `json:"title_english"` + TitleChinses string `json:"title_chinese"` + Resolution string `json:"resolution"` + Subtitle string `json:"subtitle"` + ReleaseGroup string `json:"release_group"` + Year int `json:"year"` + AudioLanguage string `json:"audio_language"` +} + +func (c *Client) ParseMovieInfo(q string) (*MovieInfo, error) { + log.Info(q) + ctx := context.Background() + + model := c.c.GenerativeModel(c.modelName) + + model.ResponseMIMEType = "application/json" + model.ResponseSchema = &genai.Schema{ + Type: genai.TypeObject, + Properties: map[string]*genai.Schema{ + "title_english": {Type: genai.TypeString}, + "title_chinese": {Type: genai.TypeString}, + "resolution": {Type: genai.TypeString}, + "subtitle": {Type: genai.TypeString}, + "release_group": {Type: genai.TypeString}, + "year": {Type: genai.TypeInteger}, + "audio_language": {Type: genai.TypeString}, + }, + Required: []string{"title_english", "title_chinese", "resolution"}, + } + + resp, err := model.GenerateContent(ctx, genai.Text(q)) + if err != nil { + return nil, err + } + for _, part := range resp.Candidates[0].Content.Parts { + if txt, ok := part.(genai.Text); ok { + var info MovieInfo + if err := json.Unmarshal([]byte(txt), &info); err != nil { + return nil, err + } + return &info, nil + } + } + return nil, fmt.Errorf("not found") + +} + +func (c *Client) isTvSeries(q string) (bool, error) { + ctx := context.Background() + + model := c.c.GenerativeModel(c.modelName) + + model.ResponseMIMEType = "application/json" + model.ResponseSchema = &genai.Schema{ + Type: genai.TypeBoolean, Nullable: true, Description: "whether the input text implies a tv series", + } + + resp, err := model.GenerateContent(ctx, genai.Text(q)) + if err != nil { + return false, err + } + for _, part := range resp.Candidates[0].Content.Parts { + if txt, ok := part.(genai.Text); ok { + return strings.ToLower(string(txt)) == "true", nil + } + } + return false, fmt.Errorf("error") +} + +func (c *Client) ImpliesSameTvOrMovie(torrentName, mediaName string) bool { + ctx := context.Background() + + model := c.c.GenerativeModel(c.modelName) + + model.ResponseMIMEType = "application/json" + model.ResponseSchema = &genai.Schema{ + Type: genai.TypeBoolean, Nullable: true, + } + q := fmt.Sprintf("whether this file name \"%s\" implies the same TV series or movie with name \"%s\"?", torrentName, mediaName) + resp, err := model.GenerateContent(ctx, genai.Text(q)) + if err != nil { + return false + } + for _, part := range resp.Candidates[0].Content.Parts { + if txt, ok := part.(genai.Text); ok { + return strings.ToLower(string(txt)) == "true" + } + } + return false + +} + +func (c *Client) FilterTvOrMovies(resourcesNames []string, titles ...string) ([]string, error) { + ctx := context.Background() + + model := c.c.GenerativeModel(c.modelName) + + model.ResponseMIMEType = "application/json" + model.ResponseSchema = &genai.Schema{ + Type: genai.TypeArray, + Items: &genai.Schema{Type: genai.TypeString}, + } + for i, s := range titles { + titles[i] = "\"" + s + "\"" + } + p := &bytes.Buffer{} + p.WriteString(`the following list of file names, list all of which implies the same TV series or movie of name`) + p.WriteString(strings.Join(titles, " or ")) + p.WriteString(":\n") + + for _, r := range resourcesNames { + p.WriteString(" * ") + p.WriteString(r) + p.WriteString("\n") + } + log.Debugf("FilterTvOrMovies prompt is %s", p.String()) + + resp, err := model.GenerateContent(ctx, genai.Text(p.String())) + if err != nil { + return nil, err + } + for _, part := range resp.Candidates[0].Content.Parts { + if txt, ok := part.(genai.Text); ok { + + var s []string + if err := json.Unmarshal([]byte(txt), &s); err != nil { + return nil, err + } + return s, nil + } + } + return nil, fmt.Errorf("nothing found") + +} + + +func (c *Client) FilterMovies(resourcesNames []string, year int, titles ...string) ([]string, error) { + ctx := context.Background() + + model := c.c.GenerativeModel(c.modelName) + + model.ResponseMIMEType = "application/json" + model.ResponseSchema = &genai.Schema{ + Type: genai.TypeArray, + Items: &genai.Schema{Type: genai.TypeString}, + } + for i, s := range titles { + titles[i] = "\"" + s + "\"" + } + p := &bytes.Buffer{} + p.WriteString( fmt.Sprint("the following list of file names, list all of which match following criteria: 1. Is movie 2. Released in year %d 3. Have name of ", year)) + p.WriteString(strings.Join(titles, " or ")) + p.WriteString(":\n") + + for _, r := range resourcesNames { + p.WriteString(" * ") + p.WriteString(r) + p.WriteString("\n") + } + log.Debugf("FilterTvOrMovies prompt is %s", p.String()) + + resp, err := model.GenerateContent(ctx, genai.Text(p.String())) + if err != nil { + return nil, err + } + for _, part := range resp.Candidates[0].Content.Parts { + if txt, ok := part.(genai.Text); ok { + + var s []string + if err := json.Unmarshal([]byte(txt), &s); err != nil { + return nil, err + } + return s, nil + } + } + return nil, fmt.Errorf("nothing found") + +} diff --git a/pkg/gemini/genimi_test.go b/pkg/gemini/genimi_test.go new file mode 100644 index 0000000..dffd5e9 --- /dev/null +++ b/pkg/gemini/genimi_test.go @@ -0,0 +1,8 @@ +package gemini + +import ( + "testing" +) + +func Test_any1(t *testing.T) { +} diff --git a/server/core/torrent.go b/server/core/torrent.go index 7713deb..8e6835a 100644 --- a/server/core/torrent.go +++ b/server/core/torrent.go @@ -6,6 +6,7 @@ import ( "polaris/ent" "polaris/ent/media" "polaris/log" + "polaris/pkg/gemini" "polaris/pkg/metadata" "polaris/pkg/prowlarr" "polaris/pkg/torznab" @@ -72,6 +73,58 @@ func names2Query(media *ent.Media) []string { return names } +func filterBasedOnGemini(cfg db.AIConfig, res []torznab.Result, names ...string) []torznab.Result { + + var torrentNames []string + for _, r := range res { + torrentNames = append(torrentNames, r.Name) + } + g, err := gemini.NewClient(cfg.GeminiApiKey, cfg.GeminiModelName) + if err != nil { + log.Warnf("create gemini client: %v", err) + return res + } + resf, err := g.FilterTvOrMovies(torrentNames, names...) + if err != nil { + log.Warnf("filter with gemini: %v", err) + return res + } + var newRes []torznab.Result + for _, r := range res { + if slices.Contains(resf, r.Name) { + newRes = append(newRes, r) + } + } + return newRes +} + +func filterBasedOnRules(res []torznab.Result, names ...string) []torznab.Result { + var filtered []torznab.Result + for _, r := range res { + meta := metadata.ParseTv(r.Name) + if meta.IsAcceptable(names...) { + filtered = append(filtered, r) + } + } + return filtered +} + +func filterResourceNames(db1 *db.Client, res []torznab.Result, names ...string) []torznab.Result { + n1 := len(res) + cfg, err := db1.GetAIConfig() + if err != nil { + log.Warnf("get ai config: %v", err) + } + if cfg.Enabled { + res = filterBasedOnGemini(cfg, res, names...) + } else { + res = filterBasedOnRules(res, names...) + } + log.Infof("resource before name filtering length is %d, after filtering length is %d", n1, len(res)) + return res + +} + func SearchTvSeries(db1 *db.Client, param *SearchParam) ([]torznab.Result, error) { series := db1.GetMediaDetails(param.MediaId) if series == nil { @@ -88,6 +141,8 @@ func SearchTvSeries(db1 *db.Client, param *SearchParam) ([]torznab.Result, error res := searchWithTorznab(db1, prowlarr.TV, names...) + res = filterResourceNames(db1, res, names...) + var filtered []torznab.Result lo: for _, r := range res { @@ -249,6 +304,8 @@ func SearchMovie(db1 *db.Client, param *SearchParam) ([]torznab.Result, error) { names := names2Query(movieDetail.Media) res := searchWithTorznab(db1, prowlarr.Movie, names...) + res = filterResourceNames(db1, res, names...) + if movieDetail.Extras.IsJav() { res1 := searchWithTorznab(db1, prowlarr.Movie, movieDetail.Extras.JavId) res = append(res, res1...) diff --git a/server/server.go b/server/server.go index 2a75bcd..59a610e 100644 --- a/server/server.go +++ b/server/server.go @@ -80,6 +80,8 @@ func (s *Server) Serve() error { setting.POST("/prowlarr", HttpHandler(s.SaveProwlarrSetting)) setting.GET("/limiter", HttpHandler(s.GetSizeLimiter)) setting.POST("/limiter", HttpHandler(s.SetSizeLimiter)) + setting.GET("/ai", HttpHandler(s.GetAIConfig)) + setting.POST("/ai", HttpHandler(s.SetAIConfig)) } activity := api.Group("/activity") { diff --git a/server/setting.go b/server/setting.go index a192104..fda2481 100644 --- a/server/setting.go +++ b/server/setting.go @@ -363,3 +363,22 @@ func (s *Server) SetSizeLimiter(c *gin.Context) (interface{}, error) { } return "success", nil } + +func (s *Server) GetAIConfig(c *gin.Context) (interface{}, error) { + aiConfig, err := s.db.GetAIConfig() + if err != nil { + return nil, errors.Wrap(err, "db") + } + return aiConfig, nil +} + +func (s *Server) SetAIConfig(c *gin.Context) (interface{}, error) { + var in db.AIConfig + if err := c.ShouldBindJSON(&in); err != nil { + return nil, err + } + if err := s.db.SetAIConfig(&in); err != nil { + return nil, errors.Wrap(err, "db") + } + return "success", nil +} \ No newline at end of file diff --git a/ui/lib/providers/APIs.dart b/ui/lib/providers/APIs.dart index b2191d6..d19049d 100644 --- a/ui/lib/providers/APIs.dart +++ b/ui/lib/providers/APIs.dart @@ -46,6 +46,7 @@ class APIs { static final deleteImportlistUrl = "$_baseUrl/api/v1/importlist/delete"; static final getAllImportlists = "$_baseUrl/api/v1/importlist/"; static final prowlarrUrl = "$_baseUrl/api/v1/setting/prowlarr"; + static final aiConfigUrl = "$_baseUrl/api/v1/setting/ai"; static final notifierAllUrl = "$_baseUrl/api/v1/notifier/all"; static final notifierDeleteUrl = "$_baseUrl/api/v1/notifier/id/"; diff --git a/ui/lib/providers/settings.dart b/ui/lib/providers/settings.dart index 7de9224..e3572c2 100644 --- a/ui/lib/providers/settings.dart +++ b/ui/lib/providers/settings.dart @@ -29,6 +29,9 @@ var prowlarrSettingDataProvider = AsyncNotifierProvider.autoDispose( ProwlarrSettingData.new); +var aiConfigDataProvider = + AsyncNotifierProvider.autoDispose(AIConfigData.new); + class EditSettingData extends AutoDisposeAsyncNotifier { @override FutureOr build() async { @@ -548,3 +551,50 @@ class ProwlarrSettingData extends AutoDisposeAsyncNotifier { ref.invalidateSelf(); } } + +class AIConfigData extends AutoDisposeAsyncNotifier { + @override + FutureOr build() async { + final dio = APIs.getDio(); + var resp = await dio.get(APIs.aiConfigUrl); + var sp = ServerResponse.fromJson(resp.data); + if (sp.code != 0) { + throw sp.message; + } + return AIConfig.fromJson(sp.data); + } + + Future save(AIConfig ai) async { + final dio = APIs.getDio(); + var resp = await dio.post(APIs.aiConfigUrl, data: ai.toJson()); + var sp = ServerResponse.fromJson(resp.data); + if (sp.code != 0) { + throw sp.message; + } + ref.invalidateSelf(); + } +} + +class AIConfig { + final bool enabled; + final String geminiApiKey; + final String geminiModelName; + + AIConfig( + {required this.enabled, + required this.geminiApiKey, + required this.geminiModelName}); + + factory AIConfig.fromJson(Map json) { + return AIConfig( + enabled: json["enabled"], + geminiApiKey: json["gemini_api_key"], + geminiModelName: json["gemini_model_name"]); + } + + Map toJson() => { + "enabled": enabled, + "gemini_api_key": geminiApiKey, + "gemini_model_name": geminiModelName + }; +} diff --git a/ui/lib/settings/general.dart b/ui/lib/settings/general.dart index 1823b15..c4921a2 100644 --- a/ui/lib/settings/general.dart +++ b/ui/lib/settings/general.dart @@ -23,152 +23,183 @@ class _GeneralState extends ConsumerState { @override Widget build(BuildContext context) { var settings = ref.watch(settingProvider); - - return settings.when( - data: (v) { - return FormBuilder( - key: _formKey, //设置globalKey,用于后面获取FormState - autovalidateMode: AutovalidateMode.onUserInteraction, - initialValue: { - "tmdb_api": v.tmdbApiKey, - "download_dir": v.downloadDIr, - "log_level": v.logLevel, - "proxy": v.proxy, - "enable_plexmatch": v.enablePlexmatch, - "allow_qiangban": v.allowQiangban, - "enable_nfo": v.enableNfo, - "enable_adult": v.enableAdult, - "tv_naming_format": v.tvNamingFormat, - "movie_naming_format": v.movieNamingFormat, - }, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - FormBuilderTextField( - name: "tmdb_api", - decoration: Commons.requiredTextFieldStyle( - text: "TMDB Api Key", icon: const Icon(Icons.key)), - // - validator: FormBuilderValidators.required(), - ), - FormBuilderTextField( - name: "download_dir", - decoration: Commons.requiredTextFieldStyle( - text: "下载路径", - icon: const Icon(Icons.folder), - helperText: "媒体文件临时下载路径,非最终存储路径"), - // - validator: FormBuilderValidators.required(), - ), - FormBuilderTextField( - name: "proxy", - decoration: const InputDecoration( - labelText: "代理地址", - icon: Icon(Icons.web), - hintText: "http://10.0.0.1:1080", - helperText: "后台联网代理地址,留空表示不启用代理"), - ), - FormBuilderTextField( - decoration: const InputDecoration( - icon: Icon(Icons.folder), - labelText: "电视剧路径命名规则", - helperText: - "go template语法,可用的变量为:.NameCN, .NameEN, .Year, .TmdbID"), - name: "tv_naming_format", - ), - FormBuilderTextField( - decoration: const InputDecoration( - icon: Icon(Icons.folder), - labelText: "电影路径命名规则", - helperText: - "go template语法,可用的变量为:.NameCN, .NameEN, .Year, .TmdbID"), - name: "movie_naming_format", - ), - SizedBox( - width: 300, - child: FormBuilderDropdown( - name: "log_level", - decoration: const InputDecoration( - labelText: "日志级别", - icon: Icon(Icons.file_present_rounded), - ), - items: const [ - DropdownMenuItem(value: "debug", child: Text("DEBUG")), - DropdownMenuItem(value: "info", child: Text("INFO")), - DropdownMenuItem(value: "warn", child: Text("WARN")), - DropdownMenuItem(value: "error", child: Text("ERROR")), - ], - validator: FormBuilderValidators.required(), - ), - ), - SizedBox( - width: 300, - child: FormBuilderSwitch( - decoration: - const InputDecoration(icon: Icon(Icons.back_hand)), - name: "enable_adult", - title: const Text("是否显示成人内容")), - ), - SizedBox( - width: 300, - child: FormBuilderSwitch( - decoration: - const InputDecoration(icon: Icon(Icons.token)), - name: "enable_plexmatch", - title: const Text("Plex 刮削支持")), - ), - SizedBox( - width: 300, - child: FormBuilderSwitch( - decoration: const InputDecoration( - icon: Icon(Icons.library_books), - helperText: "emby/kodi等软件刮削需要"), - name: "enable_nfo", - title: const Text("nfo 文件支持")), - ), - SizedBox( - width: 300, - child: FormBuilderSwitch( - decoration: const InputDecoration( - icon: Icon(Icons.remove_circle)), - name: "allow_qiangban", - title: const Text("是否下载枪版资源")), - ), - Center( - child: Padding( - padding: const EdgeInsets.only(top: 28.0), - child: ElevatedButton( - child: const Padding( - padding: EdgeInsets.all(16.0), - child: Text("保存"), + var aiConfig = ref.watch(aiConfigDataProvider); + return aiConfig.when( + data: (ai) { + return settings.when( + data: (v) { + return FormBuilder( + key: _formKey, //设置globalKey,用于后面获取FormState + autovalidateMode: AutovalidateMode.onUserInteraction, + initialValue: { + "tmdb_api": v.tmdbApiKey, + "download_dir": v.downloadDIr, + "log_level": v.logLevel, + "proxy": v.proxy, + "enable_plexmatch": v.enablePlexmatch, + "allow_qiangban": v.allowQiangban, + "enable_nfo": v.enableNfo, + "enable_adult": v.enableAdult, + "tv_naming_format": v.tvNamingFormat, + "movie_naming_format": v.movieNamingFormat, + "ai_enabled": ai.enabled, + "ai_api_key": ai.geminiApiKey, + "ai_model_name": ai.geminiModelName + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + FormBuilderTextField( + name: "tmdb_api", + decoration: Commons.requiredTextFieldStyle( + text: "TMDB Api Key", icon: const Icon(Icons.key)), + // + validator: FormBuilderValidators.required(), + ), + FormBuilderTextField( + name: "download_dir", + decoration: Commons.requiredTextFieldStyle( + text: "下载路径", + icon: const Icon(Icons.folder), + helperText: "媒体文件临时下载路径,非最终存储路径"), + // + validator: FormBuilderValidators.required(), + ), + FormBuilderTextField( + name: "proxy", + decoration: const InputDecoration( + labelText: "代理地址", + icon: Icon(Icons.web), + hintText: "http://10.0.0.1:1080", + helperText: "后台联网代理地址,留空表示不启用代理"), + ), + FormBuilderTextField( + decoration: const InputDecoration( + icon: Icon(Icons.folder), + labelText: "电视剧路径命名规则", + helperText: + "go template语法,可用的变量为:.NameCN, .NameEN, .Year, .TmdbID"), + name: "tv_naming_format", + ), + FormBuilderTextField( + decoration: const InputDecoration( + icon: Icon(Icons.folder), + labelText: "电影路径命名规则", + helperText: + "go template语法,可用的变量为:.NameCN, .NameEN, .Year, .TmdbID"), + name: "movie_naming_format", + ), + SizedBox( + width: 300, + child: FormBuilderDropdown( + name: "log_level", + decoration: const InputDecoration( + labelText: "日志级别", + icon: Icon(Icons.file_present_rounded), + ), + items: const [ + DropdownMenuItem( + value: "debug", child: Text("DEBUG")), + DropdownMenuItem( + value: "info", child: Text("INFO")), + DropdownMenuItem( + value: "warn", child: Text("WARN")), + DropdownMenuItem( + value: "error", child: Text("ERROR")), + ], + validator: FormBuilderValidators.required(), ), - onPressed: () { - if (_formKey.currentState!.saveAndValidate()) { - var values = _formKey.currentState!.value; - var f = ref - .read(settingProvider.notifier) - .updateSettings(GeneralSetting( - tmdbApiKey: values["tmdb_api"], - downloadDIr: values["download_dir"], - logLevel: values["log_level"], - proxy: values["proxy"], - allowQiangban: values["allow_qiangban"], - enableAdult: values["enable_adult"], - enableNfo: values["enable_nfo"], - tvNamingFormat: values["tv_naming_format"], - movieNamingFormat: - values["movie_naming_format"], - enablePlexmatch: - values["enable_plexmatch"])) - .then((v) => showSnakeBar("更新成功")); - showLoadingWithFuture(f); - } - }), + ), + SizedBox( + width: 300, + child: FormBuilderSwitch( + decoration: const InputDecoration( + icon: Icon(Icons.back_hand)), + name: "enable_adult", + title: const Text("是否显示成人内容")), + ), + SizedBox( + width: 300, + child: FormBuilderSwitch( + decoration: + const InputDecoration(icon: Icon(Icons.token)), + name: "enable_plexmatch", + title: const Text("Plex 刮削支持")), + ), + SizedBox( + width: 300, + child: FormBuilderSwitch( + decoration: const InputDecoration( + icon: Icon(Icons.library_books), + helperText: "emby/kodi等软件刮削需要"), + name: "enable_nfo", + title: const Text("nfo 文件支持")), + ), + SizedBox( + width: 300, + child: FormBuilderSwitch( + decoration: const InputDecoration( + icon: Icon(Icons.remove_circle)), + name: "allow_qiangban", + title: const Text("是否下载枪版资源")), + ), + Divider(), + FormBuilderSwitch( + name: "ai_enabled", title: Text("开启Gemini AI集成")), + FormBuilderTextField( + name: "ai_model_name", + decoration: InputDecoration(labelText: "Gemini 模型名称"), + ), + FormBuilderTextField( + name: "ai_api_key", + decoration: + InputDecoration(labelText: "Gemini Api Key"), + ), + Center( + child: Padding( + padding: const EdgeInsets.only(top: 28.0), + child: ElevatedButton( + child: const Padding( + padding: EdgeInsets.all(16.0), + child: Text("保存"), + ), + onPressed: () { + if (_formKey.currentState!.saveAndValidate()) { + var values = _formKey.currentState!.value; + ref.read(aiConfigDataProvider.notifier).save( + AIConfig( + enabled: values["ai_enabled"], + geminiApiKey: values["ai_api_key"], + geminiModelName: values["ai_model_name"])); + var f = ref + .read(settingProvider.notifier) + .updateSettings(GeneralSetting( + tmdbApiKey: values["tmdb_api"], + downloadDIr: values["download_dir"], + logLevel: values["log_level"], + proxy: values["proxy"], + allowQiangban: + values["allow_qiangban"], + enableAdult: values["enable_adult"], + enableNfo: values["enable_nfo"], + tvNamingFormat: + values["tv_naming_format"], + movieNamingFormat: + values["movie_naming_format"], + enablePlexmatch: + values["enable_plexmatch"])) + .then((v) => showSnakeBar("更新成功")); + showLoadingWithFuture(f); + } + }), + ), + ) + ], ), - ) - ], - ), - ); + ); + }, + error: (err, trace) => PoNetworkError(err: err), + loading: () => const MyProgressIndicator()); }, error: (err, trace) => PoNetworkError(err: err), loading: () => const MyProgressIndicator());