Compare commits

...

6 Commits

Author SHA1 Message Date
CI Bot
075d168dcd chore: update CHANGELOG for v0.7.4 2026-04-27 11:25:44 +00:00
Augustin ROUX
ed4c963576 Merge pull request 'release: v0.7.4 stable — promote develop to main' (#11) from develop into main
All checks were successful
Stable Release / stable (push) Successful in 1m9s
Reviewed-on: #11
2026-04-27 11:24:09 +00:00
Augustin ROUX
1ce5c49622 Merge pull request 'feat: integrate Muyue logo (icon embedded + web favicon) v0.7.4' (#10) from release/v0.7.4 into develop
All checks were successful
Beta Release / beta (push) Successful in 1m12s
PR Check / check (pull_request) Successful in 55s
Reviewed-on: #10
2026-04-27 11:19:46 +00:00
Muyue
830e085c2a feat: integrate Muyue logo (icon embedded in Windows binary + web favicon)
All checks were successful
PR Check / check (pull_request) Successful in 58s
Logo dropped at project root by user. Bake it everywhere it matters:

Assets:
- assets/muyue.ico — multi-res (16/24/32/48/64/128/256) generated via PIL
- assets/muyue-{16,32,64,128,256,512}.png — clean PNG resizes
- LogoMuyue.png kept at root as the source of truth

Windows binary (.exe):
- CI runs `rsrc -ico assets/muyue.ico -arch {amd64,arm64} -o cmd/muyue/rsrc_windows_{amd64,arm64}.syso`
  before `go build` (both ci-main.yml and ci-develop.yml)
- Go automatically links *.syso files matching the target GOOS/GOARCH —
  no code change in the cmd/muyue main package
- .syso files are gitignored: regenerated at every build, never committed
- Existing install-shortcuts subcommand already uses IconLocation =
  "$exe,0" so the embedded icon flows automatically into Desktop +
  Start Menu .lnk files

Web UI:
- web/public/favicon-{16,32}.png + muyue.png + muyue-64.png
- web/index.html: real <link rel="icon"> tags (16/32 PNG + apple-touch),
  replacing the placeholder SVG hexagon
- App.jsx header: 22×22 logo image rendered next to the "MUYUE" wordmark
  (rounded 4px corners for visual consistency with the source logo)

Install snippet (ci-main.yml changelog template):
- Idempotent first line: `New-Item -ItemType Directory -Force -Path $dest`
  to handle the case where the user re-runs after a partial install

Versioning unchanged (still v0.7.3 — these additions stay on the same
release branch / PR #9).
2026-04-27 13:13:56 +02:00
Augustin ROUX
24b09f5700 Merge pull request 'feat: onboarding MiniMax+MiMo + Windows install w/o admin (v0.7.3)' (#9) from release/v0.7.3 into develop
All checks were successful
Beta Release / beta (push) Successful in 1m10s
Reviewed-on: #9
2026-04-27 10:55:48 +00:00
Muyue
1442b4fd8a feat: onboarding 2-keys + Windows install w/o admin (v0.7.3)
All checks were successful
PR Check / check (pull_request) Successful in 56s
Two user-reported pain points:

1. First-run setup proposed only MiniMax (no MiMo step). Onboarding
   now offers both keys side-by-side under a single "apikey" step,
   with per-key Validate buttons. At least one must be valid to
   proceed; the rest of the providers (OpenAI/Anthropic/Z.AI/Ollama)
   are not shown in the wizard — they're configured later via the
   Config tab. Active provider = MiniMax if valid, else MiMo.

2. Windows install instructions failed: Move-Item to C:\Windows
   requires admin. Replaced with a no-admin 4-line snippet that
   installs to %LOCALAPPDATA%\Muyue and calls a new subcommand
   `muyue install-shortcuts` to create Desktop + Start Menu .lnk
   files and add the install dir to the user PATH. Shortcut creation
   uses WScript.Shell COM via PowerShell — keeps Go binary
   dependency-free. Folder paths resolved through
   [Environment]::GetFolderPath so OneDrive/redirected profiles
   work too.

- cmd/muyue/commands/install_shortcuts.go: new file
- web/src/components/OnboardingWizard.jsx: 2-key apikey step
- .gitea/workflows/ci-main.yml: updated install snippet
- internal/version/version.go: 0.7.2 → 0.7.3
- CHANGELOG.md: v0.7.3 entry
2026-04-27 12:12:18 +02:00
21 changed files with 360 additions and 33 deletions

View File

@@ -68,6 +68,13 @@ jobs:
echo "beta_num=${BETA_NUM}" >> $GITHUB_OUTPUT
echo "Building beta release: ${VERSION}"
- name: Generate Windows resource (icon)
run: |
go install github.com/akavel/rsrc@latest
RSRC="$(go env GOPATH)/bin/rsrc"
$RSRC -ico assets/muyue.ico -arch amd64 -o cmd/muyue/rsrc_windows_amd64.syso
$RSRC -ico assets/muyue.ico -arch arm64 -o cmd/muyue/rsrc_windows_arm64.syso
- name: Build (all platforms)
run: |
mkdir -p dist

View File

@@ -64,6 +64,13 @@ jobs:
echo "base=${BASE_VERSION}" >> $GITHUB_OUTPUT
echo "Building stable release: ${VERSION}"
- name: Generate Windows resource (icon)
run: |
go install github.com/akavel/rsrc@latest
RSRC="$(go env GOPATH)/bin/rsrc"
$RSRC -ico assets/muyue.ico -arch amd64 -o cmd/muyue/rsrc_windows_amd64.syso
$RSRC -ico assets/muyue.ico -arch arm64 -o cmd/muyue/rsrc_windows_arm64.syso
- name: Build (all platforms)
run: |
mkdir -p dist
@@ -138,11 +145,12 @@ jobs:
echo "sudo mv muyue-darwin-arm64 /usr/local/bin/muyue"
echo "\`\`\`"
echo ""
echo "**Windows (x86_64)**"
echo "**Windows (x86_64)** — sans privilèges admin, crée les raccourcis Bureau + Menu Démarrer :"
echo "\`\`\`powershell"
echo "Invoke-WebRequest -Uri \"${DL_URL}/muyue-windows-amd64.zip\" -OutFile \"muyue.zip\""
echo "Expand-Archive -Path \"muyue.zip\" -DestinationPath \".\""
echo "Move-Item muyue-windows-amd64.exe C:\\Windows\\muyue.exe"
echo "\$dest = \"\$env:LOCALAPPDATA\\Muyue\"; New-Item -ItemType Directory -Force -Path \$dest | Out-Null"
echo "Invoke-WebRequest -Uri \"${DL_URL}/muyue-windows-amd64.zip\" -OutFile \"\$env:TEMP\\muyue.zip\""
echo "Expand-Archive -Path \"\$env:TEMP\\muyue.zip\" -DestinationPath \$dest -Force"
echo "& \"\$dest\\muyue-windows-amd64.exe\" install-shortcuts"
echo "\`\`\`"
} > /tmp/stable_changelog.md
echo "path=/tmp/stable_changelog.md" >> $GITHUB_OUTPUT

1
.gitignore vendored
View File

@@ -24,6 +24,7 @@ Thumbs.db
*.exe
*.test
*.out
*.syso
vendor/
# Config with secrets

View File

@@ -4,6 +4,89 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## v0.7.4
### Changes since v0.7.2
- feat: integrate Muyue logo (icon embedded in Windows binary + web favicon) (830e085)
- feat: onboarding 2-keys + Windows install w/o admin (v0.7.3) (1442b4f)
### Downloads
| Platform | File |
|----------|------|
| Linux x86_64 | [muyue-linux-amd64.tar.gz](https://gitea.legion-muyue.fr/Muyue/MuyueWorkspace/releases/download/v0.7.4/muyue-linux-amd64.tar.gz) |
| Linux ARM64 | [muyue-linux-arm64.tar.gz](https://gitea.legion-muyue.fr/Muyue/MuyueWorkspace/releases/download/v0.7.4/muyue-linux-arm64.tar.gz) |
| macOS Intel | [muyue-darwin-amd64.tar.gz](https://gitea.legion-muyue.fr/Muyue/MuyueWorkspace/releases/download/v0.7.4/muyue-darwin-amd64.tar.gz) |
| macOS Apple Silicon | [muyue-darwin-arm64.tar.gz](https://gitea.legion-muyue.fr/Muyue/MuyueWorkspace/releases/download/v0.7.4/muyue-darwin-arm64.tar.gz) |
| Windows x86_64 | [muyue-windows-amd64.zip](https://gitea.legion-muyue.fr/Muyue/MuyueWorkspace/releases/download/v0.7.4/muyue-windows-amd64.zip) |
| Windows ARM64 | [muyue-windows-arm64.zip](https://gitea.legion-muyue.fr/Muyue/MuyueWorkspace/releases/download/v0.7.4/muyue-windows-arm64.zip) |
The binary includes both CLI and Desktop modes.
Run `muyue` for TUI, `muyue desktop` for web UI.
### Install
**Linux (x86_64)**
```bash
curl -sL https://gitea.legion-muyue.fr/Muyue/MuyueWorkspace/releases/download/v0.7.4/muyue-linux-amd64.tar.gz | tar xz
chmod +x muyue-linux-amd64
sudo mv muyue-linux-amd64 /usr/local/bin/muyue
```
**macOS (Apple Silicon)**
```bash
curl -sL https://gitea.legion-muyue.fr/Muyue/MuyueWorkspace/releases/download/v0.7.4/muyue-darwin-arm64.tar.gz | tar xz
chmod +x muyue-darwin-arm64
sudo mv muyue-darwin-arm64 /usr/local/bin/muyue
```
**Windows (x86_64)** — sans privilèges admin, crée les raccourcis Bureau + Menu Démarrer :
```powershell
$dest = "$env:LOCALAPPDATA\Muyue"; New-Item -ItemType Directory -Force -Path $dest | Out-Null
Invoke-WebRequest -Uri "https://gitea.legion-muyue.fr/Muyue/MuyueWorkspace/releases/download/v0.7.4/muyue-windows-amd64.zip" -OutFile "$env:TEMP\muyue.zip"
Expand-Archive -Path "$env:TEMP\muyue.zip" -DestinationPath $dest -Force
& "$dest\muyue-windows-amd64.exe" install-shortcuts
```
## v0.7.4
### Logo Muyue intégré
- `LogoMuyue.png` ajouté à la racine + déclinaisons générées dans `assets/` (16/32/64/128/256/512 px) et `assets/muyue.ico` (multi-résolution 16-256 px).
- **Binaire Windows** : icône embarquée comme ressource Windows via `github.com/akavel/rsrc` au build CI (génération de `cmd/muyue/rsrc_windows_{amd64,arm64}.syso`). Conséquences :
- Explorateur Windows affiche l'icône Muyue sur le `.exe`
- Les raccourcis créés par `install-shortcuts` héritent de l'icône (via `IconLocation = "$exe,0"`)
- Aucune dépendance Go à runtime ; les `.syso` sont gitignorés et regénérés à chaque build
- **UI web** : favicon réel (16/32 px), apple-touch-icon (256 px) et logo affiché dans le header à côté de "MUYUE".
- Snippet d'install Windows : 1ʳᵉ ligne idempotente (`New-Item -ItemType Directory -Force`) pour gérer le cas d'une ré-exécution après install partielle.
- Préservation du logo source en pleine résolution (912×950 RGBA) — pas de perte d'information.
## v0.7.3
### Onboarding — focus MiniMax + MiMo
- L'étape `apikey` du wizard de premier lancement propose désormais **les deux clés** (MiniMax + MiMo) côte à côte ; au moins une doit être validée pour continuer.
- Les autres fournisseurs (OpenAI, Anthropic, Z.AI, Ollama) ne sont plus proposés dans le wizard — l'utilisateur les configure ensuite via l'onglet **Configuration** s'il le souhaite. Justification : pour les nouveaux utilisateurs, deux choix simples > six choix qui ralentissent le démarrage.
- Si MiniMax est validé, il devient le provider actif. Sinon, c'est MiMo. Si les deux sont validés, MiniMax reste actif (peut être basculé via `/model change` plus tard).
### Install Windows — pas d'admin + raccourcis automatiques
- **Avant** : la 3ᵉ ligne du snippet d'install (`Move-Item ... C:\Windows\muyue.exe`) échouait avec `UnauthorizedAccessException` sur PowerShell sans élévation.
- **Maintenant** : 4 lignes, toutes exécutables sans admin :
```powershell
$dest = "$env:LOCALAPPDATA\Muyue"
Invoke-WebRequest -Uri ".../muyue-windows-amd64.zip" -OutFile "$env:TEMP\muyue.zip"
Expand-Archive -Path "$env:TEMP\muyue.zip" -DestinationPath $dest -Force
& "$dest\muyue-windows-amd64.exe" install-shortcuts
```
- Nouvelle commande `muyue install-shortcuts` (Windows uniquement) :
- crée `Muyue.lnk` sur le Bureau et dans le Menu Démarrer (résolus via `[Environment]::GetFolderPath`, robuste OneDrive / profils non-standards) ;
- utilise WScript.Shell COM via PowerShell pour générer les `.lnk` (pas de dépendance Go ajoutée) ;
- ajoute le dossier d'install au `PATH` utilisateur (scope User, pas de modif système).
- Une icône custom pourra être branchée plus tard en remplaçant la ressource embed du `.exe` ; pour l'instant, l'icône Windows par défaut du binaire est utilisée.
## v0.7.2
### Changes since v0.7.0

BIN
LogoMuyue.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
assets/muyue-128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
assets/muyue-16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 750 B

BIN
assets/muyue-256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

BIN
assets/muyue-32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
assets/muyue-512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 KiB

BIN
assets/muyue-64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

BIN
assets/muyue.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

View File

@@ -0,0 +1,151 @@
package commands
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"github.com/spf13/cobra"
)
// installShortcutsCmd creates desktop + Start Menu shortcuts on Windows so
// non-technical users can launch Muyue without opening a terminal. It also
// adds the install directory to the user's PATH (per-user, no admin).
//
// Implementation note: shortcut (.lnk) creation on Windows is most reliable
// via WScript.Shell COM. We invoke it via PowerShell — keeps the Go binary
// dependency-free and works on any Windows 10+ host.
var installShortcutsCmd = &cobra.Command{
Use: "install-shortcuts",
Short: "Create Desktop + Start Menu shortcuts (Windows only) and add Muyue to PATH",
RunE: func(cmd *cobra.Command, args []string) error {
if runtime.GOOS != "windows" {
fmt.Println("install-shortcuts is a Windows-only command (no-op on this platform)")
return nil
}
exe, err := os.Executable()
if err != nil {
return fmt.Errorf("locate executable: %w", err)
}
exe, _ = filepath.Abs(exe)
installDir := filepath.Dir(exe)
fmt.Println("Installing Muyue shortcuts...")
fmt.Printf(" Executable : %s\n", exe)
desktop, err := userShellFolder("Desktop")
if err != nil {
return fmt.Errorf("locate Desktop folder: %w", err)
}
startMenu, err := userShellFolder("Programs")
if err != nil {
return fmt.Errorf("locate Start Menu Programs folder: %w", err)
}
desktopLnk := filepath.Join(desktop, "Muyue.lnk")
startLnk := filepath.Join(startMenu, "Muyue.lnk")
if err := createWindowsShortcut(desktopLnk, exe, installDir, "Muyue — AI-powered dev environment"); err != nil {
return fmt.Errorf("create desktop shortcut: %w", err)
}
fmt.Printf(" Desktop : %s\n", desktopLnk)
if err := createWindowsShortcut(startLnk, exe, installDir, "Muyue — AI-powered dev environment"); err != nil {
return fmt.Errorf("create Start Menu shortcut: %w", err)
}
fmt.Printf(" Start Menu : %s\n", startLnk)
if err := addUserPATH(installDir); err != nil {
fmt.Fprintf(os.Stderr, " PATH : warning — could not add %s to user PATH: %v\n", installDir, err)
} else {
fmt.Printf(" PATH : added %s (open a new terminal to pick it up)\n", installDir)
}
fmt.Println("\nDone — double-click the Muyue icon on your Desktop to launch.")
return nil
},
}
func init() {
rootCmd.AddCommand(installShortcutsCmd)
}
// userShellFolder asks Windows for a user shell folder via PowerShell —
// resilient to OneDrive redirection and non-default profile locations.
// `which` is one of: Desktop, Programs (Start Menu Programs), StartMenu.
func userShellFolder(which string) (string, error) {
ps := fmt.Sprintf(`[Environment]::GetFolderPath('%s')`, which)
out, err := exec.Command("powershell", "-NoLogo", "-NoProfile", "-Command", ps).Output()
if err != nil {
return "", err
}
path := stripTrailingWhitespace(string(out))
if path == "" {
return "", fmt.Errorf("empty path for %s", which)
}
return path, nil
}
func stripTrailingWhitespace(s string) string {
for len(s) > 0 && (s[len(s)-1] == '\n' || s[len(s)-1] == '\r' || s[len(s)-1] == ' ' || s[len(s)-1] == '\t') {
s = s[:len(s)-1]
}
return s
}
// createWindowsShortcut generates a .lnk via WScript.Shell COM. The arguments
// are passed through PowerShell variables (not interpolated into the script
// body) to avoid quoting issues with paths containing spaces or special chars.
func createWindowsShortcut(lnkPath, target, workingDir, description string) error {
script := `
$lnk = $env:MUYUE_LNK
$target = $env:MUYUE_TARGET
$workdir = $env:MUYUE_WORKDIR
$desc = $env:MUYUE_DESC
$wsh = New-Object -ComObject WScript.Shell
$sc = $wsh.CreateShortcut($lnk)
$sc.TargetPath = $target
$sc.WorkingDirectory = $workdir
$sc.Description = $desc
$sc.IconLocation = "$target,0"
$sc.Save()
`
cmd := exec.Command("powershell", "-NoLogo", "-NoProfile", "-Command", script)
cmd.Env = append(os.Environ(),
"MUYUE_LNK="+lnkPath,
"MUYUE_TARGET="+target,
"MUYUE_WORKDIR="+workingDir,
"MUYUE_DESC="+description,
)
out, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("powershell: %v: %s", err, string(out))
}
return nil
}
// addUserPATH appends installDir to the user's PATH if not already present.
// Uses PowerShell to read/write the User-scope environment via .NET API,
// which broadcasts WM_SETTINGCHANGE so new processes pick it up.
func addUserPATH(installDir string) error {
script := `
$dir = $env:MUYUE_INSTALL_DIR
$current = [Environment]::GetEnvironmentVariable('Path', 'User')
if ($current -eq $null) { $current = '' }
$parts = $current -split ';' | Where-Object { $_ -ne '' }
if ($parts -notcontains $dir) {
$new = if ($current -eq '') { $dir } else { "$current;$dir" }
[Environment]::SetEnvironmentVariable('Path', $new, 'User')
}
`
cmd := exec.Command("powershell", "-NoLogo", "-NoProfile", "-Command", script)
cmd.Env = append(os.Environ(), "MUYUE_INSTALL_DIR="+installDir)
out, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("powershell: %v: %s", err, string(out))
}
return nil
}

View File

@@ -7,7 +7,7 @@ import (
const (
Name = "muyue"
Version = "0.7.2"
Version = "0.7.4"
Author = "La Légion de Muyue"
)

View File

@@ -4,8 +4,11 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#0A0A0C" />
<title>muyue</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⬡</text></svg>" />
<title>Muyue</title>
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/muyue.png" />
<link rel="shortcut icon" href="/muyue.png" />
</head>
<body>
<div id="root"></div>

BIN
web/public/favicon-16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 750 B

BIN
web/public/favicon-32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
web/public/muyue-64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

BIN
web/public/muyue.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

View File

@@ -103,6 +103,7 @@ export default function App() {
<div className="app-layout">
<header className="header">
<div className="header-brand">
<img src="/muyue-64.png" alt="Muyue" className="header-logo-img" width="22" height="22" style={{ borderRadius: 4, verticalAlign: 'middle' }} />
<span className="header-logo">MUYUE</span>
<span className="header-version">v{info.version || '...'}</span>
</div>

View File

@@ -23,8 +23,12 @@ export default function OnboardingWizard({ api, onComplete }) {
language: 'fr',
keyboard: 'azerty',
apikey: '',
apikey_mimo: '',
editor: '',
})
const [keyValidMimo, setKeyValidMimo] = useState(false)
const [errorMimo, setErrorMimo] = useState(null)
const [validatingMimo, setValidatingMimo] = useState(false)
const [editorList, setEditorList] = useState(BASE_EDITORS)
const [saving, setSaving] = useState(false)
const [error, setError] = useState(null)
@@ -52,7 +56,7 @@ export default function OnboardingWizard({ api, onComplete }) {
case 'name': return answers.name.trim().length > 0
case 'language': return !!answers.language
case 'keyboard': return !!answers.keyboard
case 'apikey': return keyValid && !scanning
case 'apikey': return (keyValid || keyValidMimo) && !scanning
case 'editor': return true
case 'done': return true
default: return true
@@ -173,6 +177,33 @@ export default function OnboardingWizard({ api, onComplete }) {
setValidating(false)
}
const handleValidateKeyMimo = async () => {
if (!answers.apikey_mimo.trim()) return
setValidatingMimo(true)
setErrorMimo(null)
try {
await api.validateProvider({
name: 'mimo',
api_key: answers.apikey_mimo,
model: 'mimo-v2.5-pro',
base_url: 'https://token-plan-ams.xiaomimimo.com/v1',
})
setKeyValidMimo(true)
// Save MiMo. If MiniMax wasn't validated yet, MiMo becomes the active provider.
await api.saveProvider({
name: 'mimo',
api_key: answers.apikey_mimo,
model: 'mimo-v2.5-pro',
base_url: 'https://token-plan-ams.xiaomimimo.com/v1',
active: !keyValid,
})
} catch (err) {
setErrorMimo(err.message || 'Clé invalide')
setKeyValidMimo(false)
}
setValidatingMimo(false)
}
const handleSave = async () => {
@@ -201,6 +232,15 @@ export default function OnboardingWizard({ api, onComplete }) {
active: true,
})
}
if (answers.apikey_mimo.trim()) {
await api.saveProvider({
name: 'mimo',
api_key: answers.apikey_mimo,
model: 'mimo-v2.5-pro',
base_url: 'https://token-plan-ams.xiaomimimo.com/v1',
active: !answers.apikey.trim(),
})
}
onComplete()
} catch (err) {
setError(err.message || 'Erreur lors de la sauvegarde')
@@ -283,38 +323,71 @@ export default function OnboardingWizard({ api, onComplete }) {
{current.key === 'apikey' && (
<div className="onboarding-step">
<div className="onboarding-title">Clé API MiniMax</div>
<div className="onboarding-title">Clés API</div>
<div className="onboarding-desc">
Entrez votre clé API MiniMax pour activer l'assistant IA. La clé est obligatoire pour continuer.
Renseignez au moins l'une des deux clés pour activer l'assistant. Les autres fournisseurs (OpenAI, Anthropic, Ollama, Z.AI) se configurent plus tard depuis l'onglet Configuration.
</div>
<input
className="onboarding-input"
placeholder="sk-xxxxxxxxxxxxxxxx"
type="password"
value={answers.apikey}
onChange={e => { setAnswers(a => ({ ...a, apikey: e.target.value })); setKeyValid(false); setError(null) }}
autoFocus
/>
{error && !keyValid && <div className="onboarding-required">{error}</div>}
{keyValid && !scanning && <div className="onboarding-valid">Clé valide ✓ — Appuyez sur Entrée pour continuer</div>}
<div style={{ display: 'flex', flexDirection: 'column', gap: 6, marginTop: 4 }}>
<label style={{ fontSize: 12, color: 'var(--text-tertiary)', fontWeight: 600 }}>MiniMax</label>
<input
className="onboarding-input"
placeholder="sk-xxxxxxxxxxxxxxxx (MiniMax)"
type="password"
value={answers.apikey}
onChange={e => { setAnswers(a => ({ ...a, apikey: e.target.value })); setKeyValid(false); setError(null) }}
autoFocus
/>
<div style={{ display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' }}>
<button
className="sm primary"
onClick={handleValidateKey}
disabled={validating || !answers.apikey.trim()}
>
{validating ? 'Validation...' : 'Valider MiniMax'}
</button>
{keyValid && <span className="onboarding-valid">✓ MiniMax OK</span>}
{error && !keyValid && <span className="onboarding-required">{error}</span>}
</div>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 6, marginTop: 12 }}>
<label style={{ fontSize: 12, color: 'var(--text-tertiary)', fontWeight: 600 }}>MiMo (Xiaomi)</label>
<input
className="onboarding-input"
placeholder="sk-xxxxxxxxxxxxxxxx (MiMo)"
type="password"
value={answers.apikey_mimo}
onChange={e => { setAnswers(a => ({ ...a, apikey_mimo: e.target.value })); setKeyValidMimo(false); setErrorMimo(null) }}
/>
<div style={{ display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' }}>
<button
className="sm primary"
onClick={handleValidateKeyMimo}
disabled={validatingMimo || !answers.apikey_mimo.trim()}
>
{validatingMimo ? 'Validation...' : 'Valider MiMo'}
</button>
{keyValidMimo && <span className="onboarding-valid">✓ MiMo OK</span>}
{errorMimo && !keyValidMimo && <span className="onboarding-required">{errorMimo}</span>}
</div>
</div>
{scanning && (
<div className="onboarding-scanning">
<div className="onboarding-scanning" style={{ marginTop: 8 }}>
<Loader size={14} className="spin-icon" />
<span>{scanMessage}</span>
</div>
)}
{requiredError && <div className="onboarding-required">Veuillez valider votre clé API pour continuer</div>}
<div style={{ display: 'flex', gap: 8, marginTop: 4 }}>
<button
className="sm primary"
onClick={handleValidateKey}
disabled={validating || !answers.apikey.trim()}
>
{validating ? 'Validation...' : 'Valider la clé'}
</button>
</div>
{!keyValid && !error && answers.apikey.trim() && (
<div className="onboarding-hint">Entrez votre clé puis cliquez "Valider la clé"</div>
{requiredError && (
<div className="onboarding-required" style={{ marginTop: 8 }}>
Veuillez valider au moins une clé (MiniMax ou MiMo) pour continuer.
</div>
)}
{(keyValid || keyValidMimo) && !scanning && (
<div className="onboarding-valid" style={{ marginTop: 8 }}>
Au moins une clé est valide — appuyez sur Suivant pour continuer.
</div>
)}
</div>
)}