├── .github └── workflows │ └── relese.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── api └── v1 │ ├── api.go │ └── card_api.go ├── cmd └── anki-card │ └── main.go ├── configs └── config.yaml ├── docker-compose.yml ├── go.mod ├── go.sum ├── internal ├── config │ └── config.go ├── handler │ └── crad_handler.go ├── model │ ├── request │ │ ├── create_card.go │ │ └── openai.go │ └── response │ │ ├── card.go │ │ └── response.go └── service │ └── card_service.go └── web ├── embed.go ├── favicon.ico ├── index.html └── js ├── app.61d8a641.js └── chunk-vendors.d087c419.js /.github/workflows/relese.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*.*.*' # 触发条件:推送的标签匹配版本号模式,例如 v1.0.0 7 | workflow_dispatch: 8 | 9 | jobs: 10 | create-release: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Create Release 15 | id: create_release 16 | uses: actions/create-release@v1 17 | env: 18 | GITHUB_TOKEN: ${{ secrets.TOKEN_GITHUB }} 19 | with: 20 | tag_name: ${{ github.ref }} 21 | release_name: Release ${{ github.ref }} 22 | draft: true 23 | prerelease: false 24 | outputs: 25 | upload_url: ${{ steps.create_release.outputs.upload_url }} 26 | 27 | 28 | build: 29 | needs: create-release 30 | runs-on: ubuntu-latest 31 | 32 | strategy: 33 | matrix: 34 | goos: [ linux, darwin, windows ] 35 | goarch: [ amd64, arm64 ] 36 | 37 | steps: 38 | - name: Checkout code 39 | uses: actions/checkout@v2 40 | 41 | - name: Set up Go 42 | uses: actions/setup-go@v3 43 | with: 44 | go-version: '1.21.4' # 替换为你的Go版本 45 | 46 | - name: Build the project 47 | run: | 48 | GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} go build -ldflags="-s -w" -o anki-card-${{ matrix.goos }}-${{ matrix.goarch }} ./cmd/anki-card/main.go 49 | 50 | - name: Archive the build output 51 | run: | 52 | zip anki-card-${{ matrix.goos }}-${{ matrix.goarch }}.zip anki-card-${{ matrix.goos }}-${{ matrix.goarch }} 53 | 54 | - name: Upload Release Asset 55 | uses: actions/upload-release-asset@v1 56 | env: 57 | GITHUB_TOKEN: ${{ secrets.TOKEN_GITHUB }} 58 | with: 59 | upload_url: ${{ needs.create-release.outputs.upload_url }} 60 | asset_path: ./anki-card-${{ matrix.goos }}-${{ matrix.goarch }}.zip 61 | asset_name: anki-card-${{ matrix.goos }}-${{ matrix.goarch }}.zip 62 | asset_content_type: application/zip 63 | 64 | 65 | 66 | 67 | docker: 68 | needs: build 69 | runs-on: ubuntu-latest 70 | 71 | steps: 72 | - uses: actions/checkout@v4 73 | name: git pull 74 | 75 | - uses: docker/login-action@v3.2.0 76 | name: Docker Login 77 | with: 78 | # Server address of Docker registry. If not set then will default to Docker Hub 79 | # registry: ${{ vars.REGISTRY }} 80 | # Username used to log against the Docker registry 81 | username: ${{ secrets.DOCKER_HUB_ACCOUNT }} 82 | # Password or personal access token used to log against the Docker registry 83 | password: ${{ secrets.DOCKER_HUB_PWD }} 84 | 85 | - name: Build and push the Docker image 86 | run: | 87 | tag=${GITHUB_REF#refs/tags/} 88 | docker build -t ${{ vars.SPACE_HUB_NAME }}/anki-card:${tag} . 89 | docker push ${{ vars.SPACE_HUB_NAME }}/anki-card:${tag} 90 | 91 | 92 | - name: Tag Docker image as latest 93 | if: startsWith(github.ref, 'refs/tags/') 94 | run: | 95 | tag=${GITHUB_REF#refs/tags/} 96 | docker tag ${{ vars.SPACE_HUB_NAME }}/anki-card:${tag} ${{ vars.SPACE_HUB_NAME }}/anki-card:latest 97 | docker push ${{ vars.SPACE_HUB_NAME }}/anki-card:latest 98 | 99 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | go.work.sum 23 | 24 | # golang 25 | .idea/ 26 | *.iws 27 | *.iml 28 | *.ipr 29 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Use the official Golang image as the base image 2 | FROM golang:1.21.4-alpine AS builder 3 | 4 | # Set the current working directory inside the container 5 | WORKDIR /app 6 | 7 | # Copy the entire project 8 | COPY . . 9 | 10 | # Download all dependencies. Dependencies will be cached if the go.mod and go.sum files are not changed 11 | RUN go mod download 12 | 13 | # Install upx and binutils 14 | RUN apk add --no-cache upx binutils 15 | 16 | # Build the Go app 17 | RUN go build -ldflags="-s -w" -o /app/bin/anki-card ./cmd/anki-card/main.go 18 | 19 | RUN strip /app/bin/anki-card 20 | 21 | # Compress the binary 22 | RUN upx -9 /app/bin/anki-card 23 | 24 | # Use a smaller base image for the final build 25 | FROM alpine:latest 26 | 27 | # Copy the pre-built binary and config files from the builder stage 28 | COPY --from=builder /app/bin/anki-card /app/ 29 | COPY --from=builder /app/configs /app/configs 30 | 31 | 32 | # Command to run the executable 33 | CMD ["/app/anki-card"] 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 zhangwt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # anki-card 2 | **anki-card** 是基于`openai`接口,将知识点资料生成 **Anki** 记忆卡的工具🔧,目前仅支持基础模板格式 3 | ## 使用介绍 4 | ![anki-card](https://github.com/zhangwt-cn/anki-card/assets/52098594/978dd416-c2a4-473e-a3d8-28e69762f991) 5 | 6 | 7 | ### 生成配置 8 | - api 地址: 需要填入符合`openai`接口的规范的 api 地址 9 | - api key: 接口授权key 10 | 11 | ### 记忆卡导入 Anki 12 | 1. 在生成结果页面下载记忆卡文件 13 | 2. Anki 中选择对应的牌组 14 | 3. 导入文件 15 | 16 | 17 | ## 关于 api 18 | 如果没有 `openai` 的 api ,就无法使用这个工具🔧,所以在这里整理下 api 的获取方式 19 | ### 付费 20 | 1. openai 官方充值使用,缺点是需要国外卡和国外网络 21 | 2. 中转站,但有可能买到掺假的 api 注意辨别, 推荐 [oaipro](https://api.oaipro.com/), 22 | 23 | ### 公益 24 | 在 [LinuxDO](https://linux.do/) 每天有许多大佬分享一些使用额度,动手能力强的可以自己学习 c 25 | 26 | 27 | ### 使用 chat2api 28 | 如果有chatgpt plus,推荐使用 [oaifree](https://api.oaifree.com), 在`https://chatgpt.com/api/auth/session`获取 accessToken 作为 api key, api 地址域名替换成 `api.oaifree.com` 29 | 30 | ## 部署 31 | 在 configs 目录下创建 config.yaml 文件 32 | ```bash copy 33 | server: 34 | port: ":9527" 35 | ``` 36 | 目前就只有运行端口设置,如果不需要修改运行端口就不需要创建 37 | 38 | 39 | ### docker 方式 40 | 41 | 1. 拉取镜像 42 | ```bash copy 43 | docker pull zhangwt647/anki-card:latest 44 | ``` 45 | 46 | 2. 运行 47 | ```bash copy 48 | sudo docker run -d \ 49 | --name anki-card \ 50 | -p 9527:9527 \ 51 | -v $(pwd)/configs:/app/configs \ 52 | zhangwt647/anki-card:latest 53 | ``` 54 | 55 | ### docker-compose 方式 56 | 57 | docker-compose.yml 58 | ```bash copy 59 | version: '3.8' 60 | 61 | services: 62 | anki-card: 63 | image: zhangwt647/anki-card:latest 64 | ports: 65 | - "9527:9527" 66 | volumes: 67 | - ./configs:/app/configs 68 | ``` 69 | 70 | 在 docker-compose.yml 目录 71 | ```bash copy 72 | docker-compose pull && docker-compose up -d 73 | ``` 74 | 75 | ### 运行二进制文件 76 | [releases](https://github.com/zhangwt-cn/anki-card/releases) 下载对应平台二进制文件运行 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /api/v1/api.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | ) 6 | 7 | func RegisterRoutes(router *gin.Engine) { 8 | v1 := router.Group("/api/v1") 9 | { 10 | cardRoutes(v1) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /api/v1/card_api.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "anki-card/internal/handler" 5 | "github.com/gin-gonic/gin" 6 | ) 7 | 8 | func cardRoutes(router *gin.RouterGroup) { 9 | card := router.Group("/card") 10 | { 11 | card.POST("/generate", handler.GenerateCard) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /cmd/anki-card/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | v1 "anki-card/api/v1" 5 | "anki-card/internal/config" 6 | "anki-card/web" 7 | "github.com/gin-gonic/gin" 8 | "log" 9 | "net/http" 10 | ) 11 | 12 | // @title Anki card API 13 | // @version 1.0 14 | // @description Anki card. 15 | // @BasePath /api/v1 16 | func main() { 17 | cfg := config.LoadConfig() 18 | 19 | router := gin.Default() 20 | v1.RegisterRoutes(router) 21 | 22 | // Static file route 23 | router.StaticFS("/", http.FS(web.Web)) 24 | 25 | err := router.Run(cfg.ServerPort) 26 | if err != nil { 27 | log.Fatalf("Failed to run server: %v", err) 28 | return 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /configs/config.yaml: -------------------------------------------------------------------------------- 1 | server: 2 | port: ":9527" 3 | 4 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | anki-card: 5 | image: zhangwt647/anki-card:latest 6 | ports: 7 | - "9527:9527" 8 | volumes: 9 | - ./configs:/app/configs -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module anki-card 2 | 3 | go 1.21.4 4 | 5 | require ( 6 | github.com/gin-gonic/gin v1.10.0 7 | github.com/spf13/viper v1.19.0 8 | ) 9 | 10 | require ( 11 | github.com/bytedance/sonic v1.11.9 // indirect 12 | github.com/bytedance/sonic/loader v0.1.1 // indirect 13 | github.com/cloudwego/base64x v0.1.4 // indirect 14 | github.com/cloudwego/iasm v0.2.0 // indirect 15 | github.com/fsnotify/fsnotify v1.7.0 // indirect 16 | github.com/gabriel-vasile/mimetype v1.4.4 // indirect 17 | github.com/gin-contrib/sse v0.1.0 // indirect 18 | github.com/go-playground/locales v0.14.1 // indirect 19 | github.com/go-playground/universal-translator v0.18.1 // indirect 20 | github.com/go-playground/validator/v10 v10.22.0 // indirect 21 | github.com/goccy/go-json v0.10.3 // indirect 22 | github.com/google/go-cmp v0.6.0 // indirect 23 | github.com/hashicorp/hcl v1.0.0 // indirect 24 | github.com/json-iterator/go v1.1.12 // indirect 25 | github.com/klauspost/cpuid/v2 v2.2.8 // indirect 26 | github.com/leodido/go-urn v1.4.0 // indirect 27 | github.com/magiconair/properties v1.8.7 // indirect 28 | github.com/mattn/go-isatty v0.0.20 // indirect 29 | github.com/mitchellh/mapstructure v1.5.0 // indirect 30 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 31 | github.com/modern-go/reflect2 v1.0.2 // indirect 32 | github.com/pelletier/go-toml/v2 v2.2.2 // indirect 33 | github.com/rogpeppe/go-internal v1.11.0 // indirect 34 | github.com/sagikazarmark/locafero v0.4.0 // indirect 35 | github.com/sagikazarmark/slog-shim v0.1.0 // indirect 36 | github.com/sourcegraph/conc v0.3.0 // indirect 37 | github.com/spf13/afero v1.11.0 // indirect 38 | github.com/spf13/cast v1.6.0 // indirect 39 | github.com/spf13/pflag v1.0.5 // indirect 40 | github.com/subosito/gotenv v1.6.0 // indirect 41 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 42 | github.com/ugorji/go/codec v1.2.12 // indirect 43 | go.uber.org/atomic v1.9.0 // indirect 44 | go.uber.org/multierr v1.9.0 // indirect 45 | golang.org/x/arch v0.8.0 // indirect 46 | golang.org/x/crypto v0.24.0 // indirect 47 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect 48 | golang.org/x/net v0.26.0 // indirect 49 | golang.org/x/sys v0.21.0 // indirect 50 | golang.org/x/text v0.16.0 // indirect 51 | google.golang.org/protobuf v1.34.2 // indirect 52 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 53 | gopkg.in/ini.v1 v1.67.0 // indirect 54 | gopkg.in/yaml.v3 v3.0.1 // indirect 55 | ) 56 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/bytedance/sonic v1.11.9 h1:LFHENlIY/SLzDWverzdOvgMztTxcfcF+cqNsz9pK5zg= 2 | github.com/bytedance/sonic v1.11.9/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= 3 | github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= 4 | github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= 5 | github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= 6 | github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= 7 | github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= 8 | github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= 9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 12 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 13 | github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= 14 | github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= 15 | github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= 16 | github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= 17 | github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I= 18 | github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s= 19 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 20 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 21 | github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= 22 | github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= 23 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 24 | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 25 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 26 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 27 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 28 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 29 | github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao= 30 | github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= 31 | github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= 32 | github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= 33 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 34 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 35 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 36 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 37 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 38 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 39 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 40 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 41 | github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= 42 | github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= 43 | github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= 44 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 45 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 46 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 47 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 48 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 49 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 50 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 51 | github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= 52 | github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= 53 | github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= 54 | github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= 55 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 56 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 57 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= 58 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 59 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 60 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 61 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 62 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 63 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 64 | github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= 65 | github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= 66 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 67 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 68 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 69 | github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= 70 | github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= 71 | github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= 72 | github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= 73 | github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= 74 | github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= 75 | github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= 76 | github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= 77 | github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= 78 | github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= 79 | github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= 80 | github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= 81 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 82 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 83 | github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= 84 | github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= 85 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 86 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 87 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 88 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 89 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 90 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 91 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 92 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 93 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 94 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 95 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 96 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 97 | github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= 98 | github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= 99 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 100 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 101 | github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= 102 | github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 103 | go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= 104 | go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 105 | go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= 106 | go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= 107 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 108 | golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= 109 | golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= 110 | golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= 111 | golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= 112 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= 113 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= 114 | golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= 115 | golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 116 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 117 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 118 | golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= 119 | golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 120 | golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= 121 | golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= 122 | google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= 123 | google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= 124 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 125 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 126 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 127 | gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= 128 | gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 129 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 130 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 131 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 132 | nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= 133 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 134 | -------------------------------------------------------------------------------- /internal/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/spf13/viper" 5 | "log" 6 | ) 7 | 8 | type Config struct { 9 | ServerPort string 10 | SwaggerEnable bool 11 | } 12 | 13 | func LoadConfig() Config { 14 | viper.SetConfigFile("configs/config.yaml") 15 | err := viper.ReadInConfig() 16 | if err != nil { 17 | log.Printf("Error reading config file: %v\n", err) 18 | return Config{ 19 | ServerPort: ":9527", 20 | } 21 | } 22 | 23 | return Config{ 24 | ServerPort: viper.GetString("server.port"), 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /internal/handler/crad_handler.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "anki-card/internal/model/request" 5 | "anki-card/internal/model/response" 6 | "anki-card/internal/service" 7 | "github.com/gin-gonic/gin" 8 | "net/http" 9 | ) 10 | 11 | // GenerateCard CreateCard 12 | // @Summary Create card 13 | // @Description Create card 14 | // @Tags card 15 | // @Accept json 16 | // @Produce json 17 | // @Param req body request.CreateCard true "Create card" 18 | // @Success 200 {object} response.Response 19 | // @Router /card/generate [post] 20 | func GenerateCard(c *gin.Context) { 21 | var req request.CreateCard 22 | if err := c.ShouldBindJSON(&req); err != nil { 23 | c.JSON(http.StatusOK, response.BadRequest(err.Error())) 24 | return 25 | } 26 | content, err := service.GenerateCard(req) 27 | if err != nil { 28 | c.JSON(http.StatusOK, response.InternalServerError(err.Error())) 29 | return 30 | } 31 | 32 | c.JSON(http.StatusOK, response.Success(content)) 33 | } 34 | -------------------------------------------------------------------------------- /internal/model/request/create_card.go: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | type CreateCard struct { 4 | Material string `json:"material" binding:"required"` 5 | Model string `json:"model" binding:"required"` 6 | ApiUrl string `json:"apiUrl" binding:"required"` 7 | ApiKey string `json:"apiKey" binding:"required"` 8 | } 9 | -------------------------------------------------------------------------------- /internal/model/request/openai.go: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | type ChatCompletionRequest struct { 4 | Model string `json:"model"` 5 | Messages []Message `json:"messages"` 6 | } 7 | 8 | type Message struct { 9 | Role string `json:"role"` 10 | Content string `json:"content"` 11 | } 12 | 13 | type ChatCompletionResponse struct { 14 | ID string `json:"id"` 15 | Object string `json:"object"` 16 | Created int `json:"created"` 17 | Choices []Choice `json:"choices"` 18 | Usage UsageInfo `json:"usage"` 19 | } 20 | 21 | type Choice struct { 22 | Index int `json:"index"` 23 | Message Message `json:"message"` 24 | FinishReason string `json:"finish_reason"` 25 | } 26 | 27 | type UsageInfo struct { 28 | PromptTokens int `json:"prompt_tokens"` 29 | CompletionTokens int `json:"completion_tokens"` 30 | TotalTokens int `json:"total_tokens"` 31 | } 32 | -------------------------------------------------------------------------------- /internal/model/response/card.go: -------------------------------------------------------------------------------- 1 | package response 2 | 3 | type BasicCard struct { 4 | Question string `json:"question"` 5 | Answer string `json:"answer"` 6 | } 7 | -------------------------------------------------------------------------------- /internal/model/response/response.go: -------------------------------------------------------------------------------- 1 | package response 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | type Response struct { 8 | Code int `json:"code"` 9 | Message string `json:"message"` 10 | Data interface{} `json:"data,omitempty"` 11 | } 12 | 13 | // Success response 14 | func Success(data interface{}) Response { 15 | return Response{ 16 | Code: http.StatusOK, 17 | Message: "Success", 18 | Data: data, 19 | } 20 | } 21 | 22 | // Error response 23 | func Error(code int, message string) Response { 24 | return Response{ 25 | Code: code, 26 | Message: message, 27 | } 28 | } 29 | 30 | // BadRequest response 31 | func BadRequest(message string) Response { 32 | return Error(http.StatusBadRequest, message) 33 | } 34 | 35 | // InternalServerError response 36 | func InternalServerError(message string) Response { 37 | return Error(http.StatusInternalServerError, message) 38 | } 39 | -------------------------------------------------------------------------------- /internal/service/card_service.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "anki-card/internal/model/request" 5 | "anki-card/internal/model/response" 6 | "bytes" 7 | "encoding/json" 8 | "fmt" 9 | "io" 10 | "net/http" 11 | "strings" 12 | ) 13 | 14 | // GenerateCard generate card 15 | func GenerateCard(req request.CreateCard) ([]response.BasicCard, error) { 16 | cards, err := basic(req) 17 | if err != nil { 18 | return nil, fmt.Errorf("failed to generate card: %w", err) 19 | } 20 | return cards, nil 21 | } 22 | 23 | func basic(req request.CreateCard) ([]response.BasicCard, error) { 24 | resp, err := makeOpenAIRequest(req) 25 | if err != nil { 26 | return nil, fmt.Errorf("failed to make OpenAI request: %w", err) 27 | } 28 | choice := resp.Choices[0] 29 | cardJson := choice.Message.Content 30 | 31 | // replace ```json and ``` with empty string 32 | cardJson = strings.ReplaceAll(cardJson, "```json", "") 33 | cardJson = strings.ReplaceAll(cardJson, "```", "") 34 | 35 | var cards []response.BasicCard 36 | // json -> cards 37 | err = json.Unmarshal([]byte(cardJson), &cards) 38 | if err != nil { 39 | return nil, fmt.Errorf("failed to unmarshal card json: %w", err) 40 | } 41 | 42 | return cards, nil 43 | } 44 | 45 | func makeOpenAIRequest(c request.CreateCard) (*request.ChatCompletionResponse, error) { 46 | client := &http.Client{} 47 | 48 | reqBody := request.ChatCompletionRequest{ 49 | Model: c.Model, 50 | Messages: []request.Message{ 51 | { 52 | Role: "system", 53 | Content: "提取以下材料中的所有知识点并将其转换为Anki卡片格式。每个知识点的提取要必须严格遵循以下规则:\n1. 对于每个知识点,生成一个问题和答案对。\n2. 如果材料中包含MarkDown数学公式,将其保持原样提取出来并在公式的前后加上空格。\n3. 最终输出为JSON格式,每个知识点对应一个JSON对象,格式为: [{\"question\":\"\",\"answer\":\"\"}]\n4. 确保生成的JSON可以直接被代码解析,不包含多余的符号或字符,如```json等代码块标记。\n", 54 | }, 55 | { 56 | Role: "user", 57 | Content: c.Material, 58 | }, 59 | }, 60 | } 61 | 62 | requestBodyBytes, err := json.Marshal(reqBody) 63 | if err != nil { 64 | return nil, fmt.Errorf("failed to marshal request body: %w", err) 65 | } 66 | 67 | req, err := http.NewRequest("POST", c.ApiUrl, bytes.NewBuffer(requestBodyBytes)) 68 | if err != nil { 69 | return nil, fmt.Errorf("failed to create request: %w", err) 70 | } 71 | 72 | req.Header.Set("Authorization", "Bearer "+c.ApiKey) 73 | req.Header.Set("Content-Type", "application/json") 74 | 75 | resp, err := client.Do(req) 76 | if err != nil { 77 | return nil, fmt.Errorf("failed to make request: %w", err) 78 | } 79 | defer func(Body io.ReadCloser) { 80 | err := Body.Close() 81 | if err != nil { 82 | fmt.Println("Failed to close response body") 83 | } 84 | }(resp.Body) 85 | 86 | if resp.StatusCode != http.StatusOK { 87 | bodyBytes, _ := io.ReadAll(resp.Body) 88 | return nil, fmt.Errorf("error response from OpenAI: %s", bodyBytes) 89 | } 90 | 91 | var responseBody request.ChatCompletionResponse 92 | err = json.NewDecoder(resp.Body).Decode(&responseBody) 93 | if err != nil { 94 | return nil, fmt.Errorf("failed to decode response body: %w", err) 95 | } 96 | 97 | return &responseBody, nil 98 | } 99 | -------------------------------------------------------------------------------- /web/embed.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import "embed" 4 | 5 | //go:embed * 6 | var Web embed.FS 7 | -------------------------------------------------------------------------------- /web/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangwt-cn/anki-card/2643b0d3bbd18ffb879856819414017958a7266a/web/favicon.ico -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | anki-card-web
-------------------------------------------------------------------------------- /web/js/app.61d8a641.js: -------------------------------------------------------------------------------- 1 | (function(){"use strict";var e={1963:function(e,t,a){var n=a(5130),o=a(6768);function l(e,t,a,n,l,r){const i=(0,o.g2)("router-view"),u=(0,o.g2)("n-message-provider");return(0,o.uX)(),(0,o.Wv)(u,null,{default:(0,o.k6)((()=>[(0,o.bF)(i)])),_:1})}var r={name:"App"},i=a(1241);const u=(0,i.A)(r,[["render",l]]);var s=u,d=a(1387);const p={style:{display:"flex","justify-content":"flex-end"}},c={key:0},g=(0,o.Lk)("br",null,null,-1);function f(e,t,a,n,l,r){const i=(0,o.g2)("n-icon"),u=(0,o.g2)("n-button"),s=(0,o.g2)("n-input"),d=(0,o.g2)("n-form-item"),f=(0,o.g2)("n-select"),b=(0,o.g2)("n-form"),v=(0,o.g2)("n-tab-pane"),m=(0,o.g2)("n-upload"),h=(0,o.g2)("n-gi"),y=(0,o.g2)("n-grid"),k=(0,o.g2)("AnkiCard"),A=(0,o.g2)("n-result"),w=(0,o.g2)("n-tabs"),_=(0,o.g2)("n-card"),F=(0,o.g2)("n-spin");return(0,o.uX)(),(0,o.Wv)(F,{show:e.show,delay:1e3},{default:(0,o.k6)((()=>[(0,o.bF)(_,{title:"Anki 卡片生成",style:{"margin-bottom":"16px"}},{default:(0,o.k6)((()=>[(0,o.bF)(u,{text:"",style:{"font-size":"24px"},tag:"a",href:"https://github.com/zhangwt-cn/anki-card"},{icon:(0,o.k6)((()=>[(0,o.bF)(i,{component:e.LogoGithub},null,8,["component"])])),_:1}),(0,o.bF)(w,{type:"line",value:e.selectedTab,"onUpdate:value":t[6]||(t[6]=t=>e.selectedTab=t),animated:""},{default:(0,o.k6)((()=>[(0,o.bF)(v,{name:"config",tab:"生成配置"},{default:(0,o.k6)((()=>[(0,o.bF)(b,{ref:"formRef",model:e.req,rules:e.rules,"label-placement":"left","label-width":"auto","require-mark-placement":"right-hanging",size:e.size,style:{maxWidth:"640px"}},{default:(0,o.k6)((()=>[(0,o.bF)(d,{label:"api 地址",path:"apiUrl"},{default:(0,o.k6)((()=>[(0,o.bF)(s,{value:e.req.apiUrl,"onUpdate:value":t[0]||(t[0]=t=>e.req.apiUrl=t),placeholder:"opeai 接口地址"},null,8,["value"])])),_:1}),(0,o.bF)(d,{label:"api key",path:"apiKey"},{default:(0,o.k6)((()=>[(0,o.bF)(s,{value:e.req.apiKey,"onUpdate:value":t[1]||(t[1]=t=>e.req.apiKey=t),placeholder:"接口 key"},null,8,["value"])])),_:1}),(0,o.bF)(d,{label:"模型",path:"model"},{default:(0,o.k6)((()=>[(0,o.bF)(f,{value:e.req.model,"onUpdate:value":t[2]||(t[2]=t=>e.req.model=t),placeholder:"mode",options:e.generalOptions},null,8,["value","options"])])),_:1}),(0,o.bF)(d,{label:"知识点资料",path:"material"},{default:(0,o.k6)((()=>[(0,o.bF)(s,{value:e.req.material,"onUpdate:value":t[3]||(t[3]=t=>e.req.material=t),placeholder:"知识点资料",type:"textarea",autosize:{minRows:3,maxRows:100}},null,8,["value"])])),_:1}),(0,o.Lk)("div",p,[(0,o.bF)(u,{round:"",type:"primary",onClick:t[4]||(t[4]=t=>e.handleValidateButtonClick(e.model))},{default:(0,o.k6)((()=>[(0,o.eW)(" 生成 ")])),_:1})])])),_:1},8,["model","rules","size"])])),_:1}),(0,o.bF)(v,{name:"result",tab:"生成结果"},{default:(0,o.k6)((()=>[e.ankiCardList.length>0?((0,o.uX)(),(0,o.CE)("div",c,[(0,o.bF)(y,{"x-gap":"12"},{default:(0,o.k6)((()=>[(0,o.bF)(h,{span:6},{default:(0,o.k6)((()=>[(0,o.bF)(m,{"default-file-list":e.fileList,"list-type":"text","show-download-button":"",onDownload:e.handleDownload},null,8,["default-file-list","onDownload"])])),_:1})])),_:1}),g,(0,o.bF)(k,{cards:e.ankiCardList},null,8,["cards"])])):(0,o.Q3)("",!0),0==e.ankiCardList.length?((0,o.uX)(),(0,o.Wv)(A,{key:1,status:"info",title:"卡片未生成",description:"别着急,生成之后再看"},{footer:(0,o.k6)((()=>[(0,o.bF)(u,{onClick:t[5]||(t[5]=t=>e.changeTab("config"))},{default:(0,o.k6)((()=>[(0,o.eW)("去生成卡片")])),_:1})])),_:1})):(0,o.Q3)("",!0)])),_:1})])),_:1},8,["value"])])),_:1})])),_:1},8,["show"])}a(4603),a(7566),a(8721);var b=a(144),v=a(4232);const m=(0,o.Lk)("br",null,null,-1);function h(e,t,a,n,l,r){const i=(0,o.g2)("n-ellipsis"),u=(0,o.g2)("n-card"),s=(0,o.g2)("n-grid-item"),d=(0,o.g2)("n-grid"),p=(0,o.g2)("n-pagination"),c=(0,o.g2)("n-space");return(0,o.uX)(),(0,o.CE)(o.FK,null,[(0,o.bF)(d,{"x-gap":12,"y-gap":8,cols:5},{default:(0,o.k6)((()=>[((0,o.uX)(!0),(0,o.CE)(o.FK,null,(0,o.pI)(e.paginatedData,(e=>((0,o.uX)(),(0,o.Wv)(s,{key:e.id},{default:(0,o.k6)((()=>[(0,o.bF)(u,{hoverable:"",size:"huge",segmented:{content:!0,footer:"soft"}},{footer:(0,o.k6)((()=>[(0,o.bF)(i,{"line-clamp":1},{default:(0,o.k6)((()=>[(0,o.eW)((0,v.v_)(e.answer),1)])),_:2},1024)])),default:(0,o.k6)((()=>[(0,o.bF)(i,{"line-clamp":1},{default:(0,o.k6)((()=>[(0,o.eW)((0,v.v_)(e.question),1)])),_:2},1024)])),_:2},1024)])),_:2},1024)))),128))])),_:1}),m,(0,o.bF)(c,{justify:"center"},{default:(0,o.k6)((()=>[e.pageCount>0?((0,o.uX)(),(0,o.Wv)(p,{key:0,page:e.page,"page-size":e.pageSize,"page-count":e.pageCount,"onUpdate:page":e.handlePageChange},null,8,["page","page-size","page-count","onUpdate:page"])):(0,o.Q3)("",!0)])),_:1})],64)}var y=a(4620),k=a(7033),A=a(2169),w=a(2701),_=(0,o.pM)({name:"AnkiCard",props:{cards:{type:Array,required:!0,default:()=>[]}},components:{NCard:y.Ay,NPagination:k.A,NSpace:A.A,NEllipsis:w.Ay},setup(e){const t=(0,b.KR)(1),a=(0,b.KR)(15),n=(0,o.EW)((()=>{const n=(t.value-1)*a.value;return e.cards.slice(n,n+a.value)})),l=(0,o.EW)((()=>0===e.cards.length?0:Math.ceil(e.cards.length/a.value))),r=e=>{t.value=e};return{page:t,pageSize:a,paginatedData:n,pageCount:l,handlePageChange:r}}});const F=(0,i.A)(_,[["render",h]]);var C=F,x=a(9648),q=a(496),R=a(8911),K=a(2384),N=a(4390),O=a(8572),U=a(4880),L=a(3980),j=a(4367),z=a(5829),T=a(4373);const W=window.location.protocol,E=window.location.hostname,S=window.location.port?`:${window.location.port}`:"",P=`${W}//${E}${S}`,X=T.A.create({baseURL:P,timeout:12e5,headers:{"Content-Type":"application/json"}});var M=X,$=a(7387),D=a(7966),B=a(4083),I=(0,o.pM)({components:{NCard:y.Ay,NTabs:x.A,NTabPane:q.A,NForm:R.A,NFormItem:K.Ay,NInput:N.A,NSelect:O.A,NButton:U.Ay,NSpin:L.A,NUpload:j.A,NResult:z.A,NIcon:D._,AnkiCard:C},setup(){const e=(0,b.KR)(null),t=(0,b.KR)({apiUrl:"https://api.openai.com/v1/chat/completions",apiKey:"",model:"gpt-4o",material:""}),a=(0,b.KR)([]),n=(0,b.KR)("config"),o=(0,$.J)(),l=(0,b.KR)(!1),r=(0,b.KR)([]),i=e=>{n.value=e};return{ankiCardList:a,fileList:r,formRef:e,selectedTab:n,show:l,size:(0,b.KR)("medium"),req:t,LogoGithub:B.A,generalOptions:["gpt-4o","gpt-3.5-turbo-0125","gpt-4-turbo"].map((e=>({label:e,value:e}))),rules:{apiUrl:{required:!0,trigger:["blur","input"],message:"请输入接口地址"},apiKey:{required:!0,trigger:["blur","input"],message:"请输入接口key"},material:{required:!0,trigger:["blur","input"],message:"请输入需要处理的资料"},model:{required:!0,trigger:["blur","change"],message:"请选择模型"}},async handleValidateButtonClick(){e.value?.validate((async e=>{if(!e){l.value=!0;try{const e=await M.post("/api/v1/card/generate",t.value);if(console.log(e.data),200!==e.data?.code)return void o.error(e.data?.message||"生成失败,请检查配置");a.value=e.data.data,n.value="result";const l=a.value.map((e=>`${e.question}\t${e.answer}`)).join("\n"),i=new Blob([l],{type:"text/plain"}),u=URL.createObjectURL(i);r.value=[{id:"a",name:"anki_cards.txt",status:"finished",url:u}]}catch(i){console.error("Error posting data:",i),o.error("生成失败,请检查配置")}finally{l.value=!1}}}))},changeTab:i}}});const Q=(0,i.A)(I,[["render",f]]);var G=Q;const V=[{path:"/",component:G}],J=(0,d.aE)({history:(0,d.LA)(),routes:V});var H=J,Y=a(7275),Z=a(9531),ee=a(71),te=a(732),ae=a(6233),ne=a(8122);const oe=(0,Y.A)({components:[U.Ay,N.A,Z.A,ee.A,te.Ay,K.Ay,R.A,ae.A,ne.A,O.A]}),le=(0,n.Ef)(s);le.use(oe),le.use(H),le.mount("#app")}},t={};function a(n){var o=t[n];if(void 0!==o)return o.exports;var l=t[n]={exports:{}};return e[n].call(l.exports,l,l.exports,a),l.exports}a.m=e,function(){var e=[];a.O=function(t,n,o,l){if(!n){var r=1/0;for(d=0;d=l)&&Object.keys(a.O).every((function(e){return a.O[e](n[u])}))?n.splice(u--,1):(i=!1,l0&&e[d-1][2]>l;d--)e[d]=e[d-1];e[d]=[n,o,l]}}(),function(){a.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return a.d(t,{a:t}),t}}(),function(){a.d=function(e,t){for(var n in t)a.o(t,n)&&!a.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})}}(),function(){a.g=function(){if("object"===typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"===typeof window)return window}}()}(),function(){a.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)}}(),function(){a.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})}}(),function(){var e={524:0};a.O.j=function(t){return 0===e[t]};var t=function(t,n){var o,l,r=n[0],i=n[1],u=n[2],s=0;if(r.some((function(t){return 0!==e[t]}))){for(o in i)a.o(i,o)&&(a.m[o]=i[o]);if(u)var d=u(a)}for(t&&t(n);s