diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4aa60f7..ec8468b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,10 +21,6 @@ jobs: with: go-version: '>=1.20.1' cache: true - - name: Libraries for gio - run: | - sudo apt update - sudo apt install gcc pkg-config libwayland-dev libx11-dev libx11-xcb-dev libxkbcommon-x11-dev libgles2-mesa-dev libegl1-mesa-dev libffi-dev libxcursor-dev libvulkan-dev - uses: sigstore/cosign-installer@main with: cosign-release: 'v2.0.0' # optional @@ -36,53 +32,3 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} COSIGN_PWD: ${{ secrets.COSIGN_PWD }} - release-gui-macos: - needs: goreleaser - runs-on: macos-12 - environment: main - steps: - - id: get-tag - run: | - TAG=${{ github.ref_name }} - echo "VERSION=${TAG#v}" >> $GITHUB_OUTPUT - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - run: git fetch --force --tags - - uses: actions/setup-go@v3 - with: - go-version: '>=1.20.1' - cache: true - - name: Build for macos arm64 - run: CGO_ENABLED=1 GOOS="darwin" GOARCH="arm64" go build -ldflags="-s -w -X main.Version=${VERSION}" -o bulkaigui ./cmd/bulkaigui/main.go - env: - VERSION: ${{ steps.get-tag.outputs.VERSION }} - - name: Zip arm64 - run: | - zip -r bulkaigui_macos_arm64.zip bulkaigui README.md LICENSE - rm bulkaigui - - name: Build for macos - amd64 - run: CGO_ENABLED=1 GOOS="darwin" GOARCH="amd64" go build -ldflags="-s -w -X main.Version=${VERSION}" -o bulkaigui ./cmd/bulkaigui/main.go - env: - VERSION: ${{ steps.get-tag.outputs.VERSION }} - - name: Zip - amd64 - run: | - zip -r bulkaigui_macos_amd64.zip bulkaigui README.md LICENSE - rm bulkaigui - # TODO: add cosign signature - - name: Upload to the release - arm64 - uses: svenstaro/upload-release-action@v2 - with: - repo_token: ${{ secrets.GITHUB_TOKEN }} - file: bulkaigui_macos_arm64.zip - asset_name: "bulkaigui_${{steps.get-tag.outputs.VERSION}}_macos_arm64.zip" - tag: ${{ github.ref }} - overwrite: true - - name: Upload to the release - amd64 - uses: svenstaro/upload-release-action@v2 - with: - repo_token: ${{ secrets.GITHUB_TOKEN }} - file: bulkaigui_macos_amd64.zip - asset_name: "bulkaigui_${{steps.get-tag.outputs.VERSION}}_macos_amd64.zip" - tag: ${{ github.ref }} - overwrite: true \ No newline at end of file diff --git a/.goreleaser.yml b/.goreleaser.yml index f2fec06..ee7d38d 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -6,41 +6,12 @@ builds: - amd64 - arm64 - arm - - id: bulkaigui - binary: bulkaigui - main: ./cmd/bulkaigui - goos: - # Fix building bulkaigui for macos - # - darwin - - linux - goarch: - - amd64 - # Fix building bulkaigui for arm64 - # - arm64 - ldflags: - - -s -w -X main.Version={{.Version}} -X main.Commit={{.Commit}} -X main.Date={{.Date}} - - id: bulkaigui-win - binary: bulkaigui - main: ./cmd/bulkaigui - goos: - - windows - goarch: - - amd64 - - arm64 - ldflags: - - -s -w -X main.Version={{.Version}} -X main.Commit={{.Commit}} -X main.Date={{.Date}} -H=windowsgui archives: - id: bulkai builds: - bulkai format: zip name_template: 'bulkai_{{ .Version }}_{{- if eq .Os "darwin" }}macos{{- else }}{{ .Os }}{{ end }}_{{ .Arch }}' - - id: bulkaigui - builds: - - bulkaigui - - bulkaigui-win - format: zip - name_template: 'bulkaigui_{{ .Version }}_{{- if eq .Os "darwin" }}macos{{- else }}{{ .Os }}{{ end }}_{{ .Arch }}' signs: - cmd: cosign stdin: '{{ .Env.COSIGN_PWD }}' diff --git a/README.md b/README.md index e10af65..580c0ca 100644 --- a/README.md +++ b/README.md @@ -23,25 +23,13 @@ You can use the golang binary to install **bulkai**: -To install the cli: - ```bash go install github.com/igolaizola/bulkai/cmd/bulkai@latest ``` -To install the GUI: - -> The GUI is a work in progress and it may not work properly. - -```bash -go install github.com/igolaizola/bulkai/cmd/bulkaigui@latest -``` - Or you can download the binary from the [releases](https://github.com/igolaizola/bulkai/releases) -## Usage: command-line interface - -The binary you need to use is `bulkai`. +## Usage ### 1. Create session (only for the first time) @@ -99,43 +87,6 @@ You can press `Ctrl+C` to stop the generation. If you want to resume the generation, just press launch the command again using the same settings and album name. Prompt field will be ignored and the prompts will be loaded from the album. -## Usage: graphical user interface - -> The GUI is a work in progress and it may not work properly. - -The binary you need to use is `bulkaigui`. - -### 1. Create session (only for the first time) - -You first need to create a session file with the discord credentials and other information retrieved from your browser. -**bulkai** needs this to: - - - Be able to login to Discord - - Mimic the browser and avoid being detected as a bot - -Go to the `Settings` tab and click on the `Create session` button. - -### 2. Configure settings - -On the `Settings` tab you can configure different settings. -This options will be saved in the configuration file. -See the parameters section to see all available options: [Parameters](#parameters) - -On the `Main` tab you can set the name of the album and the prompts to use. -Each line will be processed as a different prompt. -You can also set the path to a prompt file instead. - -### 3. Launch - -Press the `Start` button to launch the generation. -Images will be downloaded to the album name in the output directory specified in the configuration file. -You will be able to see the progress in the window. -This task can take a long time depending on the number of images to generate. - -You can use the `Stop` button to stop the generation. -If you want to resume the generation, just press the `Start` button again using the same settings and album name. -Prompt field will be ignored and the prompts will be loaded from the album. - ## Parameters Here is a list of all the parameters available to run the image generation. @@ -227,6 +178,5 @@ Thanks for your support! Some of the resources I used to create this project: - - [jkvatne/gio-v](https://github.com/jkvatne/gio-v) to be able to implement the GUI in a quick and easy way. - [bwmarrin/discordgo](https://github.com/bwmarrin/discordgo) to retrieve events from Discord. - [Danny-Dasilva/CycleTLS](https://github.com/Danny-Dasilva/CycleTLS) to mimic the browser and avoid being detected as a bot. diff --git a/cmd/bulkaigui/main.go b/cmd/bulkaigui/main.go deleted file mode 100644 index 18ecc3c..0000000 --- a/cmd/bulkaigui/main.go +++ /dev/null @@ -1,58 +0,0 @@ -package main - -import ( - "context" - "flag" - "log" - "os" - "os/signal" - "runtime/debug" - - "github.com/igolaizola/bulkai/pkg/gui" - "github.com/peterbourgon/ff/v3" - "github.com/peterbourgon/ff/v3/ffcli" -) - -// Build flags -var Version = "" -var Commit = "" -var Date = "" - -func main() { - // Create signal based context - ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) - defer cancel() - - // Launch command - cmd := newCommand() - if err := cmd.ParseAndRun(ctx, os.Args[1:]); err != nil { - log.Fatal(err) - } -} - -func newCommand() *ffcli.Command { - fs := flag.NewFlagSet("bulkaigui", flag.ExitOnError) - - return &ffcli.Command{ - ShortUsage: "bulkaigui [flags] ", - Options: []ff.Option{ - ff.WithConfigFileFlag("config"), - ff.WithConfigFileParser(ff.PlainParser), - ff.WithEnvVarPrefix("BULKAI"), - }, - ShortHelp: "launch gui", - FlagSet: fs, - Exec: func(ctx context.Context, args []string) error { - v := Version - if v == "" { - if buildInfo, ok := debug.ReadBuildInfo(); ok { - v = buildInfo.Main.Version - } - } - if v == "" { - v = "dev" - } - return gui.Run(ctx, v) - }, - } -} diff --git a/go.mod b/go.mod index 4c686c7..bcae4c2 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/igolaizola/bulkai go 1.20 require ( - gioui.org v0.0.0-20230224004350-5f818bc5e7f9 github.com/Danny-Dasilva/fhttp v0.0.0-20220524230104-f801520157d6 github.com/Danny-Dasilva/utls v0.0.0-20220604023528-30cb107b834e github.com/PuerkitoBio/goquery v1.8.0 @@ -13,9 +12,7 @@ require ( github.com/chromedp/cdproto v0.0.0-20230109101555-6b041c6303cc github.com/chromedp/chromedp v0.8.7 github.com/gorilla/websocket v1.5.0 - github.com/igolaizola/giov v0.0.0-20230224222616-657ba5f48ad6 github.com/peterbourgon/ff/v3 v3.3.0 - golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91 golang.org/x/image v0.5.0 golang.org/x/net v0.7.0 gopkg.in/yaml.v2 v2.4.0 @@ -23,20 +20,15 @@ require ( ) require ( - gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2 // indirect - gioui.org/shader v1.0.6 // indirect github.com/andybalholm/cascadia v1.3.1 // indirect - github.com/benoitkugler/textlayout v0.3.0 // indirect github.com/chromedp/sysutil v1.0.0 // indirect github.com/dsnet/compress v0.0.1 // indirect - github.com/go-text/typesetting v0.0.0-20221214153724-0399769901d5 // indirect github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/pool v0.2.1 // indirect github.com/gobwas/ws v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect golang.org/x/crypto v0.5.0 // indirect - golang.org/x/exp v0.0.0-20221031165847-c99f073a8326 // indirect golang.org/x/sys v0.5.0 // indirect golang.org/x/text v0.7.0 // indirect ) diff --git a/go.sum b/go.sum index 99b1676..9072297 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,3 @@ -eliasnaur.com/font v0.0.0-20220124212145-832bb8fc08c3 h1:djFprmHZgrSepsHAIRMp5UJn3PzsoTg9drI+BDmif5Q= -gioui.org v0.0.0-20230224004350-5f818bc5e7f9 h1:8quHBl67dHQh1UvZKcKfW30at01j1wZ7ppM5PvGfZjM= -gioui.org v0.0.0-20230224004350-5f818bc5e7f9/go.mod h1:+W1Kpf96YcfissZocFqIp6O42FDTuphkObbEybp+Ffc= -gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ= -gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2 h1:AGDDxsJE1RpcXTAxPG2B4jrwVUJGFDjINIPi1jtO6pc= -gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ= -gioui.org/shader v1.0.6 h1:cvZmU+eODFR2545X+/8XucgZdTtEjR3QWW6W65b0q5Y= -gioui.org/shader v1.0.6/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM= github.com/Danny-Dasilva/fhttp v0.0.0-20220524230104-f801520157d6 h1:Wzbitazy0HugGNRACX7ZB1En21LT/TiVF6YbxoTTqN8= github.com/Danny-Dasilva/fhttp v0.0.0-20220524230104-f801520157d6/go.mod h1:2IT2IFG+d+zzFuj3+ksGtVytcCBsF402zMNWHsWhD2U= github.com/Danny-Dasilva/utls v0.0.0-20220418175931-f38e470e04f2/go.mod h1:A2g8gPTJWDD3Y4iCTNon2vG3VcjdTBcgWBlZtopfNxU= @@ -17,11 +9,6 @@ github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c= github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= -github.com/benoitkugler/pstokenizer v1.0.0/go.mod h1:l1G2Voirz0q/jj0TQfabNxVsa8HZXh/VMxFSRALWTiE= -github.com/benoitkugler/textlayout v0.3.0 h1:2ehWXEkgb6RUokTjXh1LzdGwG4dRP6X3dqhYYDYhUVk= -github.com/benoitkugler/textlayout v0.3.0/go.mod h1:o+1hFV+JSHBC9qNLIuwVoLedERU7sBPgEFcuSgfvi/w= -github.com/benoitkugler/textlayout-testdata v0.1.1 h1:AvFxBxpfrQd8v55qH59mZOJOQjtD6K2SFe9/HvnIbJk= -github.com/benoitkugler/textlayout-testdata v0.1.1/go.mod h1:i/qZl09BbUOtd7Bu/W1CAubRwTWrEXWq6JwMkw8wYxo= github.com/bwmarrin/discordgo v0.27.0 h1:4ZK9KN+rGIxZ0fdGTmgdCcliQeW8Zhu6MnlFI92nf0Q= github.com/bwmarrin/discordgo v0.27.0/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0= @@ -35,8 +22,6 @@ github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moA github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q= github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo= github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= -github.com/go-text/typesetting v0.0.0-20221214153724-0399769901d5 h1:iOA0HmtpANn48hX2nlDNMu0VVaNza35HJG0WeetBVzQ= -github.com/go-text/typesetting v0.0.0-20221214153724-0399769901d5/go.mod h1:/cmOXaoTiO+lbCwkTZBgCvevJpbFsZ5reXIpEJVh5MI= github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= @@ -46,8 +31,6 @@ github.com/gobwas/ws v1.1.0/go.mod h1:nzvNcVha5eUziGrbxFCo6qFIojQHjJV5cLYIbezhfL github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/igolaizola/giov v0.0.0-20230224222616-657ba5f48ad6 h1:Ee8iZRKloEZgHe6lIzlhtoaeT1vrNfb6lxPYTaVc9qw= -github.com/igolaizola/giov v0.0.0-20230224222616-657ba5f48ad6/go.mod h1:kPuEWxp6iUPf4phGI6QdqRhjsHFnWv6iiw6qVFYKH0c= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= @@ -68,19 +51,12 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= -golang.org/x/exp v0.0.0-20221031165847-c99f073a8326 h1:QfTh0HpN6hlw6D3vu8DAwC8pBIwikq0AI1evdm+FksE= -golang.org/x/exp v0.0.0-20221031165847-c99f073a8326/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= -golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91 h1:ryT6Nf0R83ZgD8WnFFdfI8wCeyqgdXWN4+CkFVNPAT0= -golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91/go.mod h1:VjAR7z0ngyATZTELrBSkxOOHhhlnVUxDye4mcjx5h/8= -golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI= golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190328230028-74de082e2cca/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go deleted file mode 100644 index 7db7483..0000000 --- a/pkg/gui/gui.go +++ /dev/null @@ -1,417 +0,0 @@ -package gui - -import ( - "context" - "errors" - "fmt" - "io" - "log" - "os" - "strconv" - "strings" - "time" - - _ "embed" - - "gioui.org/app" - "gioui.org/font/gofont" - "gioui.org/layout" - "gioui.org/unit" - "github.com/igolaizola/bulkai" - "github.com/igolaizola/bulkai/pkg/session" - "github.com/igolaizola/giov/wid" - "golang.org/x/exp/shiny/materialdesign/icons" - "gopkg.in/yaml.v2" -) - -func Run(ctx context.Context, version string) error { - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - th := wid.NewTheme(gofont.Collection(), 14) - s := newState(ctx, th) - defer s.saveConfig() - - go func() { - defer cancel() - wid.RunWithContext(ctx, - app.NewWindow(app.Title(fmt.Sprintf("BULKAI (%s)", version)), app.Size(unit.Dp(600), unit.Dp(600))), - s.w, - th, - ) - }() - - go app.Main() - go s.log() - <-ctx.Done() - return nil -} - -func (s *state) log() { - r, w := io.Pipe() - defer r.Close() - log.SetOutput(w) - buf := make([]byte, 1024) - t := time.NewTicker(5 * time.Second) - for { - select { - case <-s.ctx.Done(): - return - case <-t.C: - n, err := r.Read(buf) - if err != nil { - s.error(err) - return - } - s.logs = string(buf[:n]) + s.logs - } - } -} - -var ( - configFile = "bulkai.yaml" - sessionFile = "session.yaml" - - bots = []string{"midjourney", "bluewillow"} - - homeIcon, _ = wid.NewIcon(icons.ActionHome) - settingsIcon, _ = wid.NewIcon(icons.ActionSettings) - infoIcon, _ = wid.NewIcon(icons.ActionInfo) - alertIcon, _ = wid.NewIcon(icons.AlertErrorOutline) - stopIcon, _ = wid.NewIcon(icons.NavigationCancel) - newIcon, _ = wid.NewIcon(icons.ContentAdd) - deleteIcon, _ = wid.NewIcon(icons.ContentClear) - - redFg = wid.RGB(0xff0000) - greenFg = wid.RGB(0x2c853b) - grayFg = wid.RGB(0x808080) - blackFg = wid.RGB(0x000000) -) - -type jobStatus int - -var ( - notStarted jobStatus = 0 - running jobStatus = 1 - finished jobStatus = 2 -) - -type state struct { - ctx context.Context - th *wid.Theme - w *layout.Widget - cfg *bulkai.Config - - // UI fields - page string - logs string - err error - job jobStatus - percentage float32 - estimated time.Duration - cancel context.CancelFunc - - // Config fields that need to be parsed - prompts string - bot int - wait string - concurrency string -} - -func newState(ctx context.Context, th *wid.Theme) *state { - var cfg *bulkai.Config - var stateErr error - - // Load config from file - f, err := os.Open(configFile) - if err == nil { - var candidateCfg bulkai.Config - if err := yaml.NewDecoder(f).Decode(&candidateCfg); err != nil { - stateErr = err - } else { - cfg = &candidateCfg - } - f.Close() - } - // Create new config if it doesn't exist - if cfg == nil { - cfg = &bulkai.Config{ - Bot: bots[0], - Output: "output", - Download: true, - Upscale: true, - Thumbnail: true, - Variation: false, - } - } - - // Get bot index - var bot int - for i, b := range bots { - if b == cfg.Bot { - bot = i - break - } - } - - s := &state{ - ctx: ctx, - th: th, - cfg: cfg, - bot: bot, - page: "main", - w: new(layout.Widget), - job: notStarted, - err: stateErr, - } - - s.saveConfig() - s.refresh() - return s -} - -func (s *state) error(err error) { - log.Println(err) - s.err = err - s.refresh() -} - -func (s *state) refresh() { - var widgets []layout.Widget - switch s.page { - default: - switch s.job { - case running: - widgets = []layout.Widget{ - wid.Label(s.th, "Running", wid.Middle(), wid.Heading()), - wid.Label(s.th, fmt.Sprintf("Progress: %.2f%%", s.percentage), wid.Middle()), - wid.Label(s.th, fmt.Sprintf("Estimated time to finish: %s", s.estimated), wid.Middle()), - wid.ProgressBar(s.th, &s.percentage), - wid.Label(s.th, "", wid.Heading()), - wid.Label(s.th, "Press STOP if you want to cancel the current job"), - wid.Space(unit.Dp(2)), - wid.OutlineButton(s.th, "STOP", wid.BtnIcon(stopIcon), wid.Fg(&redFg), wid.Do(func() { - if s.cancel != nil { - s.cancel() - } - })), - } - case finished: - widgets = []layout.Widget{ - wid.Label(s.th, "Finished", wid.Middle(), wid.Heading()), - wid.Label(s.th, fmt.Sprintf("Progress: %.2f%%", s.percentage), wid.Middle()), - wid.Label(s.th, fmt.Sprintf("Estimated time to finish: %s", time.Duration(0)), wid.Middle()), - wid.ProgressBar(s.th, &s.percentage), - wid.Label(s.th, "", wid.Heading()), - wid.Label(s.th, "Press NEW if you want to configure a new process"), - wid.OutlineButton(s.th, "NEW", wid.BtnIcon(newIcon), wid.Fg(&greenFg), wid.Do(func() { - s.job = notStarted - s.refresh() - })), - } - default: - widgets = []layout.Widget{ - wid.Edit(s.th, wid.Lbl("Album name"), wid.Var(&s.cfg.Album)), - wid.Edit(s.th, wid.Lbl("Prefix"), wid.Var(&s.cfg.Prefix)), - wid.Edit(s.th, wid.Lbl("Suffix"), wid.Var(&s.cfg.Suffix)), - wid.Edit(s.th, wid.Lbl("Prompts"), wid.Var(&s.prompts), wid.Area(300, 300)), - wid.Button(s.th, "START", wid.Do(func() { go s.start() })), - } - } - case "settings": - widgets = []layout.Widget{ - wid.DropDown(s.th, &s.bot, bots, wid.Lbl("AI Bot")), - wid.Edit(s.th, wid.Lbl("Output folder"), wid.Var(&s.cfg.Output)), - wid.Checkbox(s.th, "Download", wid.Bool(&s.cfg.Download)), - wid.Checkbox(s.th, "Generate thumbnails", wid.Bool(&s.cfg.Thumbnail)), - wid.Checkbox(s.th, "Upscale", wid.Bool(&s.cfg.Upscale)), - wid.Checkbox(s.th, "Generate variations", wid.Bool(&s.cfg.Variation)), - wid.Edit(s.th, wid.Lbl("Custom channel"), wid.Var(&s.cfg.Channel)), - wid.Edit(s.th, wid.Lbl("Wait time"), wid.Var(&s.wait)), - wid.Edit(s.th, wid.Lbl("Concurrency"), wid.Var(&s.concurrency)), - wid.Edit(s.th, wid.Lbl("Proxy"), wid.Var(&s.cfg.Proxy)), - wid.Row(s.th, nil, []float32{1, 1}, - wid.Label(s.th, "Launch chrome to import session", wid.Right()), - wid.Button(s.th, "Create session", wid.Do(func() { go s.createSession() })), - ), - } - case "logs": - widgets = []layout.Widget{ - wid.Row(s.th, nil, []float32{1, 1}, - wid.OutlineButton(s.th, "Press to clear", wid.BtnIcon(deleteIcon), wid.Do(func() { - s.logs = "" - s.refresh() - })), - wid.Label(s.th, "Log output, use mouse wheel to scroll"), - ), - wid.Edit(s.th, wid.ReadOnly(), wid.Area(500, 500), wid.Var(&s.logs)), - } - } - *s.w = s.grid(widgets...) -} - -func (s *state) grid(body ...layout.Widget) layout.Widget { - widgets := append(s.header(), body...) - return wid.Row(s.th, nil, []float32{.05, .9, .05}, - // Left column - wid.Col(nil, wid.Space(unit.Dp(1))), - // Middle column - wid.Col(nil, widgets...), - // Right column - wid.Col(nil, wid.Space(unit.Dp(1))), - ) -} - -func (s *state) header() []layout.Widget { - mainFg := grayFg - settingsFg := grayFg - aboutFg := grayFg - switch s.page { - case "main": - mainFg = blackFg - case "settings": - settingsFg = blackFg - case "logs": - aboutFg = blackFg - } - - var widgets []layout.Widget - widgets = append(widgets, - wid.Label(s.th, "AI image generator tool", wid.Middle(), wid.Bold(), wid.Role(wid.PrimaryContainer)), - wid.Row(s.th, nil, []float32{1, 1, 1}, - wid.HeaderButton(s.th, "Main", wid.BtnIcon(homeIcon), wid.Fg(&mainFg), wid.Do(func() { - s.page = "main" - s.saveConfig() - s.refresh() - })), - wid.HeaderButton(s.th, "Settings", wid.BtnIcon(settingsIcon), wid.Fg(&settingsFg), wid.Do(func() { - s.page = "settings" - s.saveConfig() - s.refresh() - })), - wid.HeaderButton(s.th, "Logs", wid.BtnIcon(infoIcon), wid.Fg(&aboutFg), wid.Do(func() { - s.page = "logs" - s.saveConfig() - s.refresh() - })), - ), - ) - if s.err != nil { - widgets = append(widgets, - wid.Separator(s.th, unit.Dp(1)), - wid.HeaderButton(s.th, s.err.Error(), wid.BtnIcon(alertIcon), wid.Fg(&redFg), wid.Do(func() { - s.err = nil - s.refresh() - })), - wid.Separator(s.th, unit.Dp(1)), - ) - } - return widgets -} - -func (s *state) start() { - prompts := strings.TrimSpace(s.prompts) - if prompts == "" { - s.error(errors.New("prompts cannot be empty")) - return - } - s.cfg.Prompts = strings.Split(prompts, "\n") - s.cfg.Bot = bots[s.bot] - - // Load session from session.yaml - f, err := os.Open(sessionFile) - if err != nil { - s.error(err) - return - } - defer f.Close() - decoder := yaml.NewDecoder(f) - if err = decoder.Decode(&s.cfg.Session); err != nil { - s.error(err) - return - } - - // Save the config - s.saveConfig() - - s.err = nil - s.job = running - s.percentage = 0 - s.estimated = 0 - s.refresh() - ctx, cancel := context.WithCancel(s.ctx) - defer func() { - cancel() - s.job = finished - s.refresh() - }() - s.cancel = cancel - - // Start the bulk generate command - if err := bulkai.Generate(ctx, s.cfg, bulkai.WithOnUpdate(s.onUpdate)); err != nil { - s.error(err) - return - } -} - -func (s *state) createSession() { - if err := session.Run(s.ctx, false, sessionFile, s.cfg.Proxy); err != nil { - s.error(err) - } -} - -func (s *state) onUpdate(status bulkai.Status) { - s.percentage = status.Percentage / 100.0 - s.estimated = status.Estimated - s.refresh() -} - -func (s *state) saveConfig() { - // Convert UI types to config types - bot := bots[s.bot] - if s.concurrency != "" { - concCandidate, err := strconv.Atoi(s.concurrency) - if err != nil { - s.error(err) - return - } - s.cfg.Concurrency = concCandidate - } - if s.wait != "" { - waitCandidate, err := strconv.Atoi(s.wait) - if err != nil { - s.error(err) - return - } - s.cfg.Wait = time.Duration(waitCandidate) * time.Second - } - - // Create or open the file - f, err := os.Create(configFile) - if err != nil { - s.error(err) - return - } - defer f.Close() - - encoder := yaml.NewEncoder(f) - - // Save all fields except album, prompts, prefix and suffix - cfg := &bulkai.Config{ - Debug: s.cfg.Debug, - Bot: bot, - Proxy: s.cfg.Proxy, - Output: s.cfg.Output, - Variation: s.cfg.Variation, - Upscale: s.cfg.Upscale, - Download: s.cfg.Download, - Thumbnail: s.cfg.Thumbnail, - Channel: s.cfg.Channel, - Concurrency: s.cfg.Concurrency, - Wait: s.cfg.Wait, - } - if err = encoder.Encode(&cfg); err != nil { - s.error(err) - return - } -}