mouaif 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +140 -0
- package/bin/mouaif.js +281 -0
- package/frontend/dist/assets/AgentFilePicker-CcKLJorU.js +1 -0
- package/frontend/dist/assets/CliModal-Hs5phmNZ.js +7 -0
- package/frontend/dist/assets/DictationPage-BI23lp42.js +2 -0
- package/frontend/dist/assets/FileEditor-DDl31c6d.js +2 -0
- package/frontend/dist/assets/GitModal-3EC_gpJ5.js +2 -0
- package/frontend/dist/assets/Inspector-Ba3R1w04.js +73 -0
- package/frontend/dist/assets/SettingsAbout-bvZGDEDw.js +1 -0
- package/frontend/dist/assets/SettingsActions-Dk6WX9jv.js +1 -0
- package/frontend/dist/assets/SettingsAgents-BNV0MgDB.js +1 -0
- package/frontend/dist/assets/SettingsDefaults-DbMmQbzc.js +1 -0
- package/frontend/dist/assets/SettingsHiddenContent-BZ2sloH1.js +1 -0
- package/frontend/dist/assets/SettingsMcp-DOrfbQd1.js +1 -0
- package/frontend/dist/assets/SettingsMcpEdit-BGMQ2CWC.js +3 -0
- package/frontend/dist/assets/SettingsMcpRegistry-BywXee_A.js +1 -0
- package/frontend/dist/assets/SettingsNotifications-B0LEs11a.js +1 -0
- package/frontend/dist/assets/SettingsPricing-BAg33iVF.js +1 -0
- package/frontend/dist/assets/SettingsProject-DNrKhCcZ.js +14 -0
- package/frontend/dist/assets/SettingsProjects-IqkBfDcm.js +1 -0
- package/frontend/dist/assets/SettingsPrompts-BgeiASuk.js +1 -0
- package/frontend/dist/assets/SettingsProviders-k0xJN0IK.js +1 -0
- package/frontend/dist/assets/SettingsTags-B5kjFdQi.js +1 -0
- package/frontend/dist/assets/agentNavigation-BiiCpFz5.js +1 -0
- package/frontend/dist/assets/codemirror-Bp6CUUFk.js +30 -0
- package/frontend/dist/assets/index-BGvI4n0T.js +61 -0
- package/frontend/dist/assets/index-Bgg1gnDf.css +1 -0
- package/frontend/dist/assets/index-C1sQFIC-.css +1 -0
- package/frontend/dist/assets/index-CANPYzQg.css +1 -0
- package/frontend/dist/assets/index-Crn1LdzK.css +1 -0
- package/frontend/dist/assets/index-FbCWDPiB.css +1 -0
- package/frontend/dist/assets/projectQS-D1cSZ7Gr.js +1 -0
- package/frontend/dist/assets/virtual-list-6H9b4K51.js +1 -0
- package/frontend/dist/icons/favicon-32.png +0 -0
- package/frontend/dist/icons/icon-180-apple.png +0 -0
- package/frontend/dist/icons/icon-192.png +0 -0
- package/frontend/dist/icons/icon-512.png +0 -0
- package/frontend/dist/icons/icon-maskable-512.png +0 -0
- package/frontend/dist/index.html +83 -0
- package/frontend/dist/manifest.webmanifest +33 -0
- package/frontend/dist/sw.js +482 -0
- package/package.json +98 -0
- package/scripts/patch-zimmerframe.js +58 -0
- package/src/access-auth.js +515 -0
- package/src/agentFeatures.js +294 -0
- package/src/agentFiles.js +164 -0
- package/src/agentSkills.js +147 -0
- package/src/agents.js +230 -0
- package/src/ai-chat.js +21 -0
- package/src/ai-endpoints.js +1880 -0
- package/src/ai-stream.js +2048 -0
- package/src/ai.js +68 -0
- package/src/auth.js +391 -0
- package/src/chatdb.js +816 -0
- package/src/chats.js +275 -0
- package/src/custom-actions.js +65 -0
- package/src/files.js +431 -0
- package/src/hideFileContent.js +327 -0
- package/src/http-server.js +535 -0
- package/src/index.js +15 -0
- package/src/inspector.js +731 -0
- package/src/inspectorProfiles.js +503 -0
- package/src/live-chat.js +107 -0
- package/src/mcp.js +1517 -0
- package/src/messages.js +238 -0
- package/src/modelList.js +137 -0
- package/src/notifications.js +52 -0
- package/src/oauth-anthropic.js +280 -0
- package/src/oauth-github-copilot.js +417 -0
- package/src/oauth-mcp.js +216 -0
- package/src/oauth-openrouter.js +285 -0
- package/src/package-version.js +20 -0
- package/src/projects.js +285 -0
- package/src/promptProfiles.js +256 -0
- package/src/prompts.js +384 -0
- package/src/providerShapes.js +44 -0
- package/src/providers/base.js +41 -0
- package/src/providers/index.js +25 -0
- package/src/push.js +315 -0
- package/src/qr.js +192 -0
- package/src/restart.js +47 -0
- package/src/server-handlers-access.js +306 -0
- package/src/server-handlers-actions.js +100 -0
- package/src/server-handlers-ai.js +248 -0
- package/src/server-handlers-auth.js +273 -0
- package/src/server-handlers-chats.js +1436 -0
- package/src/server-handlers-git.js +467 -0
- package/src/server-handlers-mcp-oauth.js +56 -0
- package/src/server-handlers-misc.js +783 -0
- package/src/server-handlers-projects.js +289 -0
- package/src/server-handlers-prompts.js +259 -0
- package/src/server-handlers-push.js +102 -0
- package/src/server-handlers-settings.js +406 -0
- package/src/server-handlers-tools.js +654 -0
- package/src/server-handlers-transcribe.js +399 -0
- package/src/server-shared.js +780 -0
- package/src/server-web-static.js +191 -0
- package/src/settings.js +898 -0
- package/src/statusBar.js +541 -0
- package/src/tags.js +414 -0
- package/src/toolFeedback.js +225 -0
- package/src/tools/ask.js +154 -0
- package/src/tools/authorization.js +932 -0
- package/src/tools/files.js +1150 -0
- package/src/tools/progress.js +71 -0
- package/src/tools/restart.js +32 -0
- package/src/tools/searchEngine.js +957 -0
- package/src/tools/shell.js +341 -0
- package/src/tools/subagent.js +47 -0
- package/src/tools/task.js +234 -0
- package/src/tools/webpreview.js +448 -0
- package/src/trace.js +103 -0
- package/src/transcribe.js +683 -0
- package/src/usage.js +389 -0
- package/src/util.js +151 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Antoine Gjeloshaj
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# mouaif 🚀
|
|
2
|
+
|
|
3
|
+
mouaif is a mobile-first AI coding assistant for local projects. Connect your preferred AI providers, chat about a project, and choose which coding tools the assistant may use.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- Node.js 18 or newer
|
|
8
|
+
- A supported AI provider account, or a local Ollama installation
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
The npm package name is [`mouaif`](https://www.npmjs.com/package/mouaif). Start it directly with `npx`, without a global install, and require a login:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npx mouaif serve --auth
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
On the first authenticated start, the terminal prints a setup link, QR code, and short code for creating your username and password. Later starts reuse those access settings and show the login screen.
|
|
19
|
+
|
|
20
|
+
Install the `mouaif` command globally:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install -g mouaif
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Or install from a checkout of this repository:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
git clone <repo-url>
|
|
30
|
+
cd mouaif
|
|
31
|
+
npm install
|
|
32
|
+
npm link
|
|
33
|
+
```
|
|
34
|
+
`npm install` builds the web UI once when its sources are newer than `frontend/dist/` (the Vite toolchain is present in a source checkout), so there is no separate build step. `npm link` makes the `mouaif` command available in your terminal.
|
|
35
|
+
|
|
36
|
+
Every install ships the pre-built web UI in `frontend/dist/`, so `mouaif serve` never builds the frontend. The package depends on `better-sqlite3` and `@napi-rs/keyring`, which ship prebuilt binaries for common platforms; on an unusual platform Node compiles them, so the first install can take a few minutes.
|
|
37
|
+
|
|
38
|
+
## Run
|
|
39
|
+
|
|
40
|
+
With `npx`:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npx mouaif serve --auth
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Or, after a global install:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
mouaif serve --auth
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Open `http://127.0.0.1:5732/` in a browser and create or enter your access credentials. Keep the terminal open while using mouaif and press `Ctrl+C` to stop it. Omit `--auth` only when you intentionally want the app to be accessible without a login.
|
|
53
|
+
|
|
54
|
+
Useful commands:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
mouaif serve --port 9000 # use another port
|
|
58
|
+
mouaif serve --host 0.0.0.0 # listen on your local network
|
|
59
|
+
mouaif info # show version and default port
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## First setup
|
|
63
|
+
|
|
64
|
+
1. Open the **Chats** tab and tap **Add project**.
|
|
65
|
+
2. Choose an existing folder or create one.
|
|
66
|
+
3. Open **Settings → Providers** and connect an AI provider.
|
|
67
|
+
4. Open the project settings and add or select a model.
|
|
68
|
+
5. Create a chat and send your first message.
|
|
69
|
+
|
|
70
|
+
## Authentication
|
|
71
|
+
|
|
72
|
+
### Connect an AI provider
|
|
73
|
+
|
|
74
|
+
Open **Settings → Providers**, select a provider, then enter its API key or use **Sign in** when offered. Supported connections include OpenAI-compatible services, Anthropic, Google Gemini, Ollama, OpenRouter, GitHub Copilot, Azure OpenAI, Mistral, Groq, and DeepSeek.
|
|
75
|
+
|
|
76
|
+
### Protect access to mouaif
|
|
77
|
+
|
|
78
|
+
The recommended command requires login and creates a setup invitation when no user exists yet:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
npx mouaif serve --auth
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
To replace the access user or explicitly generate a fresh expiring setup link, QR code, and short code:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
npx mouaif serve --auth-setup
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
You can also set credentials while keeping the password out of shell history:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
MOUAIF_PASSWORD='a-long-password' \
|
|
94
|
+
npx mouaif serve --auth --user alice
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
PowerShell:
|
|
98
|
+
|
|
99
|
+
```powershell
|
|
100
|
+
$env:MOUAIF_PASSWORD = 'a-long-password'
|
|
101
|
+
npx mouaif serve --auth --user alice
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
After setup, require login on future starts with:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
npx mouaif serve --auth
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
If you installed `mouaif` globally, the shorter equivalent is `mouaif serve --auth`; the auth options are identical. Use HTTPS and `--public-origin` before making mouaif available outside the computer running it.
|
|
111
|
+
|
|
112
|
+
## App abilities
|
|
113
|
+
|
|
114
|
+
- Organize chats by local project and select models per chat.
|
|
115
|
+
- Attach files and images, use custom prompts, and control reasoning options.
|
|
116
|
+
- Use **Draft Craft** to send selected code or an annotated Inspector image to any chat draft.
|
|
117
|
+
- Let the assistant read and edit project files.
|
|
118
|
+
- Run approved non-interactive shell commands and view live output.
|
|
119
|
+
- Track tasks, answer structured questions, and delegate work to project agents.
|
|
120
|
+
- Connect additional tools through MCP.
|
|
121
|
+
- Preview pages and inspect console and network activity in the Inspector.
|
|
122
|
+
- Gate tools per project with **Off**, **Ask**, or **Allow**.
|
|
123
|
+
- Export chat traces and receive browser notifications for long-running work.
|
|
124
|
+
|
|
125
|
+
## Documentation
|
|
126
|
+
|
|
127
|
+
- [Getting started](docs/features/getting-started.md) — install, run, update, and first setup.
|
|
128
|
+
- [Authentication](docs/features/authentication.md) — connect AI providers and protect app access.
|
|
129
|
+
- [App abilities](docs/features/app-abilities.md) — projects, chats, coding tools, agents, MCP, and Inspector.
|
|
130
|
+
- [Draft Craft](docs/features/draft-craft.md) — add selected code or annotated Inspector images to a chat draft.
|
|
131
|
+
|
|
132
|
+
Build the static documentation site with:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
npm run docs:build
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## License
|
|
139
|
+
|
|
140
|
+
MIT — see [LICENSE](LICENSE).
|
package/bin/mouaif.js
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const { spawn } = require('child_process');
|
|
7
|
+
const { program } = require('commander');
|
|
8
|
+
const { createServer, destroyOpenSockets, DEFAULT_PORT } = require('../src/index.js');
|
|
9
|
+
const accessAuth = require('../src/access-auth.js');
|
|
10
|
+
const qr = require('../src/qr.js');
|
|
11
|
+
|
|
12
|
+
const { name, version, description } = require('../package.json');
|
|
13
|
+
|
|
14
|
+
const SERVE_CHILD_ENV = 'MOUAIF_SERVE_CHILD';
|
|
15
|
+
const WATCH_EXTS = new Set(['.js', '.jsx', '.json', '.css', '.html']);
|
|
16
|
+
|
|
17
|
+
function closeServer(server) {
|
|
18
|
+
return new Promise((resolve) => {
|
|
19
|
+
if (!server || !server.listening) return resolve();
|
|
20
|
+
// Force-close keep-alive / SSE sockets so server.close() can resolve
|
|
21
|
+
// promptly instead of hanging on connections that never end.
|
|
22
|
+
server.close(() => {
|
|
23
|
+
destroyOpenSockets();
|
|
24
|
+
resolve();
|
|
25
|
+
});
|
|
26
|
+
// Destroy immediately for sockets that are idle but still counted;
|
|
27
|
+
// server.close() only fires once all connections are gone.
|
|
28
|
+
destroyOpenSockets();
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function displayOrigin(options, port) {
|
|
33
|
+
if (options.publicOrigin) return options.publicOrigin;
|
|
34
|
+
const host = String(options.host || '127.0.0.1');
|
|
35
|
+
if (host !== '0.0.0.0' && host !== '::') return `http://${host.includes(':') ? '[' + host + ']' : host}:${port}`;
|
|
36
|
+
const interfaces = os.networkInterfaces();
|
|
37
|
+
for (const entries of Object.values(interfaces)) {
|
|
38
|
+
for (const entry of entries || []) {
|
|
39
|
+
if (entry && entry.family === 'IPv4' && !entry.internal) return `http://${entry.address}:${port}`;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return `http://127.0.0.1:${port}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function collectWatchFiles(dir, out = []) {
|
|
46
|
+
let entries = [];
|
|
47
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }); }
|
|
48
|
+
catch { return out; }
|
|
49
|
+
for (const entry of entries) {
|
|
50
|
+
if (entry.name === 'node_modules' || entry.name === '.git' || entry.name === 'dist' || entry.name === 'build') continue;
|
|
51
|
+
const full = path.join(dir, entry.name);
|
|
52
|
+
if (entry.isDirectory()) collectWatchFiles(full, out);
|
|
53
|
+
else if (WATCH_EXTS.has(path.extname(entry.name))) out.push(full);
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function runSupervisor(options) {
|
|
59
|
+
const watch = !!options.watch;
|
|
60
|
+
const port = String(parseInt(options.port, 10));
|
|
61
|
+
const host = String(options.host || '127.0.0.1');
|
|
62
|
+
const publicOrigin = String(options.publicOrigin || process.env.MOUAIF_PUBLIC_ORIGIN || '');
|
|
63
|
+
const binPath = path.join(__dirname, 'mouaif.js');
|
|
64
|
+
const watchRoots = [path.join(__dirname), path.join(__dirname, '..', 'src')];
|
|
65
|
+
const watched = new Set();
|
|
66
|
+
let child = null;
|
|
67
|
+
let stopping = false;
|
|
68
|
+
let pendingRestart = false;
|
|
69
|
+
let debounce = null;
|
|
70
|
+
|
|
71
|
+
function startChild() {
|
|
72
|
+
const args = [binPath, 'serve', '--port', port, '--host', host];
|
|
73
|
+
if (publicOrigin) args.push('--public-origin', publicOrigin);
|
|
74
|
+
if (options.auth || options.user || options.authSetup) args.push('--auth');
|
|
75
|
+
if (options.user) args.push('--user', options.user);
|
|
76
|
+
if (options.authSetup) args.push('--auth-setup');
|
|
77
|
+
child = spawn(process.execPath, args, {
|
|
78
|
+
stdio: 'inherit',
|
|
79
|
+
env: { ...process.env, [SERVE_CHILD_ENV]: '1', ...(options.password ? { MOUAIF_PASSWORD: options.password } : {}) }
|
|
80
|
+
});
|
|
81
|
+
child.on('exit', (code, signal) => {
|
|
82
|
+
child = null;
|
|
83
|
+
if (stopping) return;
|
|
84
|
+
if (pendingRestart || code === 0) {
|
|
85
|
+
pendingRestart = false;
|
|
86
|
+
startChild();
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const hint = watch ? 'waiting for changes...' : 'not restarting (use --watch or a manual restart to try again).';
|
|
90
|
+
console.error(`[mouaif] server stopped (${signal || code}); ${hint}`);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function restartChild(file) {
|
|
95
|
+
if (debounce) clearTimeout(debounce);
|
|
96
|
+
debounce = setTimeout(() => {
|
|
97
|
+
console.log(`[mouaif] change detected: ${path.relative(process.cwd(), file)}; restarting...`);
|
|
98
|
+
pendingRestart = true;
|
|
99
|
+
if (child) child.kill('SIGTERM');
|
|
100
|
+
else startChild();
|
|
101
|
+
}, 100);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function refreshWatchFiles() {
|
|
105
|
+
for (const root of watchRoots) {
|
|
106
|
+
for (const file of collectWatchFiles(root)) {
|
|
107
|
+
if (watched.has(file)) continue;
|
|
108
|
+
watched.add(file);
|
|
109
|
+
fs.watchFile(file, { interval: 500 }, (cur, prev) => {
|
|
110
|
+
if (cur.mtimeMs !== prev.mtimeMs || cur.size !== prev.size) restartChild(file);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function stop() {
|
|
117
|
+
stopping = true;
|
|
118
|
+
for (const file of watched) fs.unwatchFile(file);
|
|
119
|
+
if (child) child.kill('SIGTERM');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (watch) {
|
|
123
|
+
console.log('[mouaif] watch mode enabled');
|
|
124
|
+
refreshWatchFiles();
|
|
125
|
+
setInterval(refreshWatchFiles, 2000);
|
|
126
|
+
}
|
|
127
|
+
startChild();
|
|
128
|
+
process.on('SIGINT', () => { stop(); process.exit(0); });
|
|
129
|
+
process.on('SIGTERM', () => { stop(); process.exit(0); });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Worker process — the actual HTTP server. Runs as a child of the supervisor
|
|
133
|
+
// so POST /api/restart (or a --watch source change) can drop it and let the
|
|
134
|
+
// supervisor spawn a brand-new process: every module is re-read from disk and
|
|
135
|
+
// runtime state (MCP children, DB handles, OAuth flows) is re-initialized.
|
|
136
|
+
function runWorker(options) {
|
|
137
|
+
const port = parseInt(options.port, 10);
|
|
138
|
+
const authEnabled = !!(options.auth || options.user || options.authSetup);
|
|
139
|
+
let server;
|
|
140
|
+
const lifecycle = {
|
|
141
|
+
restarting: false,
|
|
142
|
+
restart: async () => {
|
|
143
|
+
// Hand control back to the supervisor: close the listening socket,
|
|
144
|
+
// then exit with code 0 so the supervisor respawns a fresh worker.
|
|
145
|
+
await closeServer(server);
|
|
146
|
+
process.exit(0);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
function start() {
|
|
151
|
+
server = createServer(port, { lifecycle, publicOrigin: options.publicOrigin, authEnabled });
|
|
152
|
+
server.listen(port, options.host, () => {
|
|
153
|
+
const servedOrigin = displayOrigin(options, port);
|
|
154
|
+
console.log(`🚀 mouaif server running at ${servedOrigin}`);
|
|
155
|
+
console.log(` Web: / — mobile UI`);
|
|
156
|
+
console.log(` REST: GET /data — get data`);
|
|
157
|
+
console.log(` REST: POST /data — update data`);
|
|
158
|
+
console.log(` SSE: GET /events — subscribe to events`);
|
|
159
|
+
console.log(` CDP: /api/inspector/ + WS /api/inspector/proxy`);
|
|
160
|
+
if (authEnabled && accessAuth.disabled()) {
|
|
161
|
+
console.log('');
|
|
162
|
+
console.log('🔓 App access is disabled from the app (Settings → Access & passkeys).');
|
|
163
|
+
console.log(' Re-enable it in Settings, or restart with --auth-setup to re-assert protection.');
|
|
164
|
+
} else if (authEnabled && (options.authSetup || !accessAuth.configured())) {
|
|
165
|
+
const setup = accessAuth.createSetupCode();
|
|
166
|
+
const setupUrl = servedOrigin + '/#/setup?code=' + encodeURIComponent(setup.code);
|
|
167
|
+
console.log('');
|
|
168
|
+
console.log('🔐 Set up app access (expires in 15 minutes)');
|
|
169
|
+
console.log(` Link: ${setupUrl}`);
|
|
170
|
+
console.log(` Code: ${setup.code}`);
|
|
171
|
+
try { console.log('\n' + qr.terminal(setupUrl)); } catch (_) { /* narrow terminals can use the link */ }
|
|
172
|
+
const securePasskeys = /^https:\/\//i.test(servedOrigin) || /^http:\/\/(localhost|127(?:\.\d+){3}|\[::1\])(?::|\/|$)/i.test(servedOrigin);
|
|
173
|
+
console.log(securePasskeys
|
|
174
|
+
? ' The setup page can create a password and register a passkey.'
|
|
175
|
+
: ' Password setup is available. Passkeys require HTTPS for remote devices.');
|
|
176
|
+
}
|
|
177
|
+
console.log(' Press Ctrl+C to stop');
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
start();
|
|
182
|
+
|
|
183
|
+
// Last-resort breadcrumbs. The HTTP dispatcher (src/http-server.js
|
|
184
|
+
// handleRequest) already converts anything a request handler throws or
|
|
185
|
+
// rejects into a logged 500, so reaching these handlers means the
|
|
186
|
+
// failure came from outside a request (a timer, an SSE write, a
|
|
187
|
+
// background MCP child). Log them loudly and keep serving rather than
|
|
188
|
+
// letting Node's default `--unhandled-rejections=throw` exit the
|
|
189
|
+
// process and drop every connected client.
|
|
190
|
+
process.on('unhandledRejection', (reason) => {
|
|
191
|
+
console.error('[mouaif] unhandled rejection:', (reason && reason.stack) || reason);
|
|
192
|
+
});
|
|
193
|
+
process.on('uncaughtException', (error) => {
|
|
194
|
+
console.error('[mouaif] uncaught exception:', (error && error.stack) || error);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
function shutdown() {
|
|
198
|
+
console.log('\n⏹ Shutting down...');
|
|
199
|
+
closeServer(server).then(() => process.exit(0));
|
|
200
|
+
}
|
|
201
|
+
process.on('SIGINT', shutdown);
|
|
202
|
+
process.on('SIGTERM', shutdown);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
program
|
|
206
|
+
.name(name)
|
|
207
|
+
.version(version)
|
|
208
|
+
.description(description);
|
|
209
|
+
|
|
210
|
+
program
|
|
211
|
+
.command('serve')
|
|
212
|
+
.description('Start the HTTP server')
|
|
213
|
+
.option('-p, --port <port>', 'Port to listen on', DEFAULT_PORT)
|
|
214
|
+
.option('-h, --host <host>', 'Host to bind to', '127.0.0.1')
|
|
215
|
+
.option('--public-origin <origin>', 'Public HTTP(S) origin when served through a proxy', process.env.MOUAIF_PUBLIC_ORIGIN)
|
|
216
|
+
.option('--auth', 'Require app access authentication (disabled unless explicitly enabled)')
|
|
217
|
+
.option('--user <user>', 'Set the app access user before serving')
|
|
218
|
+
.option('--password <password>', 'Set the app access password before serving (prefer MOUAIF_PASSWORD to avoid shell history)')
|
|
219
|
+
.option('--auth-setup', 'Print a one-time setup link, QR code, and short code')
|
|
220
|
+
.option('-w, --watch', 'Restart the server when local source files change')
|
|
221
|
+
.action((options) => {
|
|
222
|
+
// `mouaif serve` always runs as a supervisor + worker pair. The
|
|
223
|
+
// supervisor (this process) stays alive and respawns a fresh worker
|
|
224
|
+
// process on POST /api/restart (and on source changes with --watch),
|
|
225
|
+
// so a restart always loads the latest code from disk.
|
|
226
|
+
if (process.env[SERVE_CHILD_ENV] === '1') {
|
|
227
|
+
return runWorker(options);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const suppliedPassword = options.password || process.env.MOUAIF_PASSWORD || '';
|
|
231
|
+
if ((options.user && !suppliedPassword) || (!options.user && suppliedPassword)) {
|
|
232
|
+
console.error('❌ --user and --password (or MOUAIF_PASSWORD) must be supplied together');
|
|
233
|
+
process.exitCode = 1;
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (options.user) {
|
|
237
|
+
try {
|
|
238
|
+
accessAuth.setPassword(options.user, suppliedPassword);
|
|
239
|
+
console.log(`🔐 App access user set to ${accessAuth.user().username}`);
|
|
240
|
+
} catch (error) {
|
|
241
|
+
console.error('❌ Could not set app access:', error.message);
|
|
242
|
+
process.exitCode = 1;
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return runSupervisor(options);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
program
|
|
250
|
+
.command('info')
|
|
251
|
+
.description('Show server info')
|
|
252
|
+
.action(() => {
|
|
253
|
+
console.log(`📦 mouaif v${version}`);
|
|
254
|
+
console.log(` ${description}`);
|
|
255
|
+
console.log(` Default port: ${DEFAULT_PORT}`);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
program
|
|
259
|
+
.command('import-chats')
|
|
260
|
+
.description('Import chat transcripts from JSON files into the SQLite store')
|
|
261
|
+
.argument('<projectDir>', 'Absolute path to the project directory')
|
|
262
|
+
.option('--skip-existing', 'Skip chats already imported (only import missing messages)')
|
|
263
|
+
.action((projectDir, options) => {
|
|
264
|
+
const abs = path.resolve(projectDir);
|
|
265
|
+
if (!fs.existsSync(abs)) {
|
|
266
|
+
console.error('❌ Project directory does not exist:', abs);
|
|
267
|
+
process.exit(1);
|
|
268
|
+
}
|
|
269
|
+
const chatdb = require('../src/chatdb.js');
|
|
270
|
+
console.log(`📦 Importing chats from ${abs}...`);
|
|
271
|
+
const result = chatdb.importFromJson(abs, { skipExisting: !!options.skipExisting });
|
|
272
|
+
console.log(` ✅ ${result.chats} chats, ${result.messages} messages imported`);
|
|
273
|
+
if (result.errors.length) {
|
|
274
|
+
for (const e of result.errors) console.error(' ⚠️ ' + e);
|
|
275
|
+
}
|
|
276
|
+
if (result.chats === 0 && result.messages === 0) {
|
|
277
|
+
console.log(' Nothing to import.');
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
program.parse(process.argv);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{d as f,h as M,u as B,k as a,f as T}from"./index-BGvI4n0T.js";async function D(e,o){const s=new URLSearchParams;e&&s.set("projectDir",e),o&&s.set("dir",o);const i=await T("/api/files?"+s.toString());return i.status!==200?{error:i.body&&i.body.error||"HTTP "+i.status}:{body:i.body}}function H(e){const o=e.projectDir||"",s=e.onPick||(()=>{}),i=e.onClose||(()=>{}),[d,m]=f(o||""),[u,C]=f(""),[b,h]=f([]),[k,p]=f(!1),[_,y]=f("");async function l(t){const n=typeof t=="string"&&t?t:o||"";p(!0),y("");const r=await D(o,n);if(r.error){y(r.error),h([]),p(!1);return}m(r.body.dir),C(r.body.browseTop||""),h(Array.isArray(r.body.entries)?r.body.entries:[]),p(!1)}M(()=>{l(o)},[o]);const x=B({onClose:()=>{i&&i()}});function w(){const t=d||"";if(!t)return;const n=u||"";if(n&&t===n)return;const r=t.replace(/[\\/]+$/,""),c=Math.max(r.lastIndexOf("\\"),r.lastIndexOf("/"));if(c<=0){l(n);return}const v=r.slice(0,c);if(n&&v===n){l(n);return}l(v)}function g(t){if(t.type==="dir"){l(t.path);return}t.binary||s(t.relPath)}function P(){const t=d||"";if(!t)return null;const n=u||"";if(n&&t===n)return null;const r=t.replace(/[\\/]+$/,""),c=Math.max(r.lastIndexOf("\\"),r.lastIndexOf("/"));return c>0?r.slice(0,c):n||null}return a("div",{class:"afp__overlay",role:"dialog","aria-modal":"true","aria-label":e.label||"Pick an agent file",onClick:t=>{t.target===t.currentTarget&&i()}},a("div",{class:"afp__sheet",ref:x},a("div",{class:"afp__head"},a("div",{class:"afp__title"},"Pick a file"),a("div",{class:"afp__sub"},e.description||"Tapping a file adds its project-relative path to the list."),a("div",{class:"afp__path-row"},a("button",{class:"icon-btn afp__iconbtn",type:"button",onClick:w,"aria-label":"Up one folder",title:"Up"},a("svg",{viewBox:"0 0 24 24",width:16,height:16,"aria-hidden":"true"},a("path",{d:"M12 4 4 12l8 8 1.4-1.4L8.8 14H20v-2H8.8l4.6-4.6L12 4Z",fill:"currentColor",transform:"rotate(-90 12 12)"}))),a("div",{class:"afp__path"},d||"project"),a("button",{class:"icon-btn icon-btn--close afp__iconbtn",type:"button",onClick:i,"aria-label":"Close",title:"Close"},a("svg",{viewBox:"0 0 24 24",width:16,height:16,"aria-hidden":"true"},a("path",{d:"M18.3 5.71 12 12l6.3 6.29-1.41 1.42L10.59 13.4 4.3 19.71 2.88 18.3 9.17 12 2.88 5.71 4.3 4.3l6.29 6.29 6.3-6.29 1.41 1.41Z",fill:"currentColor"}))))),a("div",{class:"afp__body"},_?a("p",{class:"afp__err"},_):a("ul",{class:"afp__list","aria-label":"Files and folders"},b.length===0?a("li",{class:"afp__empty"},k?"loading…":"no files here"):b.map(t=>a("li",{key:t.path,class:"afp__row"+(t.binary?" afp__row--binary":""),role:"button",tabindex:t.binary?-1:0,"aria-disabled":t.binary?"true":"false",title:t.binary?"binary file — cannot be picked here":t.relPath||t.path,onClick:()=>g(t),onKeydown:n=>{t.binary||(n.key==="Enter"||n.key===" ")&&(n.preventDefault(),g(t))}},a("span",{class:"afp__row-icon","aria-hidden":"true"},t.type==="dir"?a("svg",{viewBox:"0 0 24 24",width:16,height:16},a("path",{d:"M3 6a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V6Z",fill:"currentColor"})):a("svg",{viewBox:"0 0 24 24",width:16,height:16},a("path",{d:"M6 2h8l4 4v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2Zm7 1.5V7h3.5L13 3.5Z",fill:"currentColor"}))),a("span",{class:"afp__row-name"},t.name),t.type==="dir"?a("span",{class:"afp__row-meta"},t.hasChildren?"…":"·"):a("span",{class:"afp__row-meta"},t.binary?"binary":t.size<1024?t.size+" B":Math.round(t.size/1024)+" KB")))),a("div",{class:"afp__foot"},P()?a("button",{class:"btn btn--ghost afp__up",type:"button",onClick:w},"↑ Up"):null,a("button",{class:"btn",type:"button",onClick:i},"Cancel")))))}export{H as A};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import{d as s,A as a,am as D,a3 as C,h as T,f as S,u as A,k as t}from"./index-BGvI4n0T.js";function U(E){const{projectDir:i,onClose:p}=E,[I,_]=s(!0),[w,L]=s(""),[H,B]=s(""),[x,J]=s(i||""),[N,R]=s(!1),[O,P]=s(!1),l=a(null),u=a(null),h=a(null),K=a(null),[f,j]=s(""),c=a(null);c.current||(c.current=new D);const y=a(!0),m=C((e,r)=>{if(c.current||(c.current=new D),r==="exit"){c.current.write(`\r
|
|
2
|
+
└─ process exited with code `+e+`
|
|
3
|
+
`),j(c.current.render());return}c.current.write(String(e||"")),j(c.current.render())},[]);T(()=>{const e=l.current;if(!e)return;const r=e.scrollHeight-e.scrollTop-e.clientHeight<48;if(c.current?c.current.isFullScreen:!1){const n=e.scrollTop,g=e.scrollHeight;e.textContent=f,y.current||r?e.scrollTop=e.scrollHeight:e.scrollTop=n+(e.scrollHeight-g)}else if(y.current||r)e.textContent=f,e.scrollTop=e.scrollHeight;else{const n=e.scrollTop;e.textContent=f,e.scrollTop=n}},[f]);const M=C(e=>{if(l.current&&l.current._onScroll&&l.current.removeEventListener("scroll",l.current._onScroll),l.current=e,e){const r=()=>{y.current=e.scrollHeight-e.scrollTop-e.clientHeight<48};e._onScroll=r,e.addEventListener("scroll",r,{passive:!0})}},[]);T(()=>()=>{l.current&&l.current._onScroll&&l.current.removeEventListener("scroll",l.current._onScroll)},[]),T(()=>{let e=!1,r=null;async function o(){try{const n=await S("/api/tools/cli/session?projectDir="+encodeURIComponent(i||""));if(e)return;if(n.status!==200){L(n.body&&n.body.error||"HTTP "+n.status),_(!1);return}h.current=n.body.id,B(n.body.shell||""),P(!!n.body.interactive),n.body.projectDir&&J(n.body.projectDir),r=new EventSource("/events"),K.current=r,r.addEventListener("cli_output",g=>{let d;try{d=JSON.parse(g.data)}catch{return}!d||d.id!==h.current||m(d.data,d.stream)}),_(!1),u.current&&u.current.focus()}catch(n){e||(L(String(n)),_(!1))}}return o(),()=>{e=!0,r&&r.close(),h.current&&S("/api/tools/cli/close",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({projectDir:i})}).catch(()=>{})}},[i]);const q=A({onClose:()=>{p&&p()}}),[v,b]=s(""),k=C(async(e,r)=>{R(!0);try{const o=await S("/api/tools/cli/command",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({projectDir:i,cmd:e,raw:!!r})});o.status!==200&&m(`
|
|
4
|
+
`+(o.body&&o.body.error||"HTTP "+o.status)+`
|
|
5
|
+
`,"stderr")}catch(o){m(`
|
|
6
|
+
error: `+String(o)+`
|
|
7
|
+
`,"stderr")}finally{R(!1),u.current&&u.current.focus()}},[i,m]);async function z(){const e=v;!e.trim()&&!O||(b(""),await k(e,!1))}return t("div",{class:"cli__overlay",role:"dialog","aria-modal":"true","aria-label":"Command prompt"},t("div",{class:"cli__sheet",ref:q},t("div",{class:"cli__head"},t("div",{class:"cli__title-stack"},t("span",{class:"cli__title"},H?"CLI — "+H:"CLI"),t("span",{class:"cli__dir",title:x},x),O?t("span",{class:"cli__badge",title:"Interactive terminal — prompting programs can read your answer"},"interactive"):null),t("button",{class:"icon-btn icon-btn--close cli__iconbtn",type:"button",onClick:p,"aria-label":"Close",title:"Close"},t("svg",{viewBox:"0 0 24 24",width:16,height:16,"aria-hidden":"true"},t("path",{d:"M18.3 5.71 12 12l6.3 6.29-1.41 1.42L10.59 13.4 4.3 19.71 2.88 18.3 9.17 12 2.88 5.71 4.3 4.3l6.29 6.29 6.3-6.29 1.41 1.41Z",fill:"currentColor"})))),t("div",{class:"cli__body"},I?t("div",{class:"cli__empty"},"Starting command prompt…"):w?t("div",{class:"cli__error"},t("p",null,w),t("button",{class:"btn",type:"button",onClick:p},"Close")):t("div",{class:"cli__terminal"},t("pre",{ref:M,class:"cli__out","aria-label":"Command output",tabindex:"-1"}),t("div",{class:"cli__prompt-row"},t("span",{class:"cli__prompt-mark","aria-hidden":"true"},"❯"),t("input",{ref:u,class:"input cli__prompt",type:"text",value:v,onInput:e=>b(e.currentTarget.value),placeholder:"Type a command — runs in the project folder","aria-label":"Command line",autocomplete:"off",autocapitalize:"off",spellcheck:"false",disabled:N,onKeyDown:e=>{if(e.key==="Enter")if(e.preventDefault(),e.ctrlKey||e.metaKey){const r=v;b(""),k(r,!0)}else z()}}))))))}export{U as CliModal};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{d as o,b as R,h as W,f as T,X as Bt,A as f,Y as he,Z as $,a0 as Et,a1 as Nt,a2 as me,a3 as tt,a4 as ye,a5 as be,a6 as ve,a7 as ge,k as e,a8 as _e,a9 as et,aa as Ot,R as we,ab as ke,ac as Te,ad as Re,ae as Ce,af as Me,ag as Ie,ah as Pe,ai as Ae,aj as Se,ak as je,al as De,p as Le,s as xe}from"./index-BGvI4n0T.js";const rt="dictation";function Ue(){const[l,p]=o([]),[m,Ft]=o([]),[y,nt]=o(""),[A,at]=o(""),[ot,Ut]=o(""),[st,Ht]=o(""),[Kt,it]=o(!0),[w,ct]=o(!0),[lt,dt]=o(""),[ut,zt]=o(()=>({dir:R.value&&R.value.dir||"",name:R.value&&R.value.name||""})),u=ut.dir;W(()=>{if(u)return;let t=!1;return(async()=>{try{const r=await T("/api/projects/registered"),a=r.status===200&&Array.isArray(r.body&&r.body.projects)?r.body.projects[0]:null;!t&&a&&zt({dir:a.path||"",name:a.name||a.path||""})}catch{}})(),()=>{t=!0}},[u]);const[pt]=o(()=>Bt()),[_,S]=o(!1),[ft,ht]=o(0),[mt,yt]=o(0),[g,bt]=o(null),[vt,gt]=o(""),[C,Y]=o(""),[M,j]=o(!1),[_t,D]=o(""),[Wt,i]=o(""),[Yt,c]=o(""),[q,qt]=o(null),wt=f(""),L=f(null),x=f(null),I=f([]),B=f(0),E=f(null),N=f(null),O=f(null),F=f(null),J=f(null),V=f(null),[k,U]=o(!1),[kt,Jt]=o([]),[Tt,Vt]=o([]);function X(t,r,a){const s=!!(a&&a.onlyIfEmpty),n=t.models;p(n),Ft(t.kinds),Jt(Array.isArray(t.liveFailures)?t.liveFailures:[]),Vt(Array.isArray(t.providers)?t.providers:[]);const d=Re(n,r);d&&(!s||!wt.current)&&(nt(d.modelId),at(d.providerId))}W(()=>{let t=!1;return(async()=>{ct(!0),dt("");let r={};try{const n=await T("/api/settings");n.status===200&&n.body&&n.body.app&&(r=n.body.app[rt]||{})}catch{}t||it(he(r));let a={models:[],kinds:[],total:0};try{a=await $(u,{live:!1})}catch(n){t||dt(n&&n.message?n.message:"Could not load models")}if(t)return;X(a,r);const s=Array.isArray(a.providers)&&a.providers.length>0;if(ct(!1),!(!u||!s)){U(!0);try{const n=await $(u);t||X(n,r,{onlyIfEmpty:!0})}catch{}finally{t||U(!1)}}})(),()=>{t=!0}},[u]);const Rt=f(null),[Xt,Ct]=o(()=>new Set),[Gt,Mt]=o([]);function P(){const t=Rt.current||{props:{projectDir:""},recentModels:[]};return Rt.current=t,t.props.projectDir=u,t}W(()=>{let t=!1;return Ct(Et(P())),Mt(Nt(P())),me(P()).then(r=>{!t&&r&&Mt(Nt(P()))}),()=>{t=!0}},[u]);function Qt(t){const r=P();je(r,t.provider||"",t.id),Ct(Et(r))}const H=tt(()=>{E.current&&(clearInterval(E.current),E.current=null),N.current&&(clearInterval(N.current),N.current=null),O.current&&(clearTimeout(O.current),O.current=null)},[]),G=tt(()=>{const t=x.current;if(x.current=null,t&&typeof t.getTracks=="function")for(const r of t.getTracks())try{r.stop()}catch{}if(F.current){try{F.current.close()}catch{}F.current=null}J.current=null,V.current=null,yt(0)},[]),It=tt(()=>{H();const t=L.current;if(t&&t.state!=="inactive")try{t.stop()}catch{}S(!1)},[H]);W(()=>()=>{H();const t=L.current;if(t&&t.state!=="inactive")try{t.stop()}catch{}const r=x.current;if(r&&typeof r.getTracks=="function")for(const a of r.getTracks())try{a.stop()}catch{}},[H]);function Zt(t,r){try{const a=r.AudioContext||r.webkitAudioContext;if(!a)return;const s=new a,n=s.createMediaStreamSource(t),d=s.createAnalyser();d.fftSize=1024,n.connect(d),F.current=s,J.current=d,V.current=new Uint8Array(d.fftSize),N.current=setInterval(()=>{const z=J.current,h=V.current;if(!z||!h)return;z.getByteTimeDomainData(h);let Lt=0;for(let Z=0;Z<h.length;Z++){const xt=(h[Z]-128)/128;Lt+=xt*xt}yt(Math.min(1,Math.sqrt(Lt/h.length)*Math.SQRT2))},250)}catch{}}async function $t(){i(""),c("");const t=typeof window<"u"?window:null;if(!t||!Bt(t)){i("This browser cannot record audio."),c("error");return}let r;try{r=await t.navigator.mediaDevices.getUserMedia({audio:!0})}catch(n){i(n&&n.name==="NotAllowedError"?"Microphone permission was refused. Allow it in the browser, then tap again.":"Could not open the microphone: "+(n&&n.message||n)),c("error");return}x.current=r;const a=Me(t.MediaRecorder,t);let s;try{s=a?new t.MediaRecorder(r,{mimeType:a}):new t.MediaRecorder(r)}catch{try{s=new t.MediaRecorder(r)}catch(n){G(),i("Could not start the recorder: "+(n&&n.message||n)),c("error");return}}I.current=[],s.ondataavailable=n=>{n&&n.data&&n.data.size&&I.current.push(n.data)},s.onstop=()=>{const n=s.mimeType||a||"audio/webm",d=I.current.length?new Blob(I.current,{type:n}):null;I.current=[],L.current=null,G(),S(!1),d&&d.size?(bt(d),gt(n),i("Recorded "+et(Date.now()-B.current)+". Ready to transcribe."),c("success")):(i("Nothing was captured — check that the microphone is not muted."),c("error"))},s.onerror=()=>{G(),S(!1),i("The recorder stopped unexpectedly."),c("error")},L.current=s,B.current=Date.now(),bt(null),gt(""),ht(0),S(!0),s.start(),E.current=setInterval(()=>ht(Date.now()-B.current),200),O.current=setTimeout(()=>It(),Ot),Zt(r,t)}async function te(){if(!y){i("Pick a dictation model first."),c("error");return}if(!g){i("Record something first."),c("error");return}j(!0),D("transcribe"),i("Transcribing with "+y+"…"),c("busy");try{const t=await Ie(g),r=await Pe({projectDir:u,modelId:y,providerId:A,kind:v&&v.kind||"",audioBase64:t,mimeType:vt||g.type||"audio/webm",filename:Ae(vt||g.type,B.current),language:ot,prompt:st});Y(r.text||""),qt({model:r.model,kind:r.kind,bytes:r.bytes,durationMs:r.durationMs,usage:r.usage||null,costLabel:Se(r).label}),i("Transcribed with "+(r.model&&r.model.id||y)+"."),c("success")}catch(t){i(t&&t.message||"Transcription failed"),c("error")}finally{j(!1),D("")}}async function ee(){try{await navigator.clipboard.writeText(C),i("Transcript copied."),c("success")}catch{i("Could not copy — long-press the text instead."),c("error")}}async function re(t){const r=u;if(!r){i("Open a project first, then this can hand the text to a chat."),c("error");return}j(!0),D("handoff");try{const a=await T("/api/chats?projectDir="+encodeURIComponent(r)+"&limit=1");if(a.status!==200)throw new Error(a.body&&a.body.error||"HTTP "+a.status);const s=a.body&&a.body.chats&&a.body.chats[0]||null;if(!s)throw new Error("This project has no chat yet — start one in the Chats tab.");const n=await T("/api/chats/"+encodeURIComponent(s.id)+"?projectDir="+encodeURIComponent(r));if(n.status!==200||!n.body||!n.body.chat)throw new Error(n.body&&n.body.error||"HTTP "+n.status);const d=typeof n.body.chat.draft=="string"?n.body.chat.draft:"",z=d.trim()?d.replace(/\s+$/,"")+`
|
|
2
|
+
`+C:C,h=await T("/api/chats/"+encodeURIComponent(s.id),{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({projectDir:r,draft:z})});if(h.status!==200)throw new Error(h.body&&h.body.error||"HTTP "+h.status);i(t?'Draft filled in "'+(s.title||s.id)+'" — send it from the chat.':'Draft filled in "'+(s.title||s.id)+'".'),c("success")}catch(a){i(a&&a.message||"Could not reach the chat."),c("error")}finally{j(!1),D("")}}const ne=ye(l),ae=be(l).map(t=>({id:t.id,provider:t.provider||""})),oe=y?{providerId:A,modelId:y}:null,se=ve({text:C,chatId:u,hasRecording:!!g}),ie=l.filter(t=>t.source!=="live").length,ce=l.some(t=>t.source==="live"),Pt=Array.isArray(Tt)?Tt:[],b=Ce({catalogBusy:w,liveBusy:k,transcribing:M&&_t==="transcribe",handoff:M&&_t==="handoff"}),Q=De({catalogBusy:w,liveBusy:k,projectCount:ie,hasLive:ce});async function le(){if(!(k||w)){U(!0),i(""),c("");try{const t=await $(u,{refresh:!0});X(t,{modelId:y,providerId:A},{onlyIfEmpty:!1})}catch(t){i(t&&t.message||"Could not refresh the model list."),c("error")}finally{U(!1)}}}function de(t){const r=t?t.modelId:"",a=t?t.providerId:"";wt.current=r,nt(r),at(a),i(""),c(""),At({modelId:r,providerId:a})}const ue=f({record:null,chain:Promise.resolve()});async function At(t){const r=ue.current;return r.record=Object.assign({},r.record,t),r.chain=r.chain.then(async()=>{try{const a=await T("/api/settings"),s=a.status===200&&a.body&&a.body.app&&a.body.app[rt]||{},n=Object.assign({},s,r.record);r.record=n,await xe({[rt]:n})}catch{}}),r.chain}function pe(t){it(t),At({live:t})}const v=l.find(t=>t.id===y&&(t.provider||"")===A)||null,St=v&&v.source==="live"?ge(v):"",K=v&&v.kind||"",jt=Ee(ut,u),Dt=_?"Stop":ft?"Record again":"Record",fe=!!g&&!!y&&!M&&!_;return e("section",{class:"dictation"},e("div",{class:"view-head"},e("a",{href:"#/settings",class:"view-back","aria-label":"Back to settings"},"←"),e("h2",{class:"view-title"},"Dictation")),e("div",{class:"group"},e("div",{class:"group__title"},"Dictation model",jt?e("span",{class:"group__title-note"},jt):null),e("div",{class:"dictation__fields"},e("div",{class:"dictation__field"},e(we,{models:ne,value:oe,onChange:de,onOpen:()=>{},placeholder:w?"Loading models…":Be(l)||"Pick a model",ariaLabel:"Pick dictation model",recommended:ae,pinned:Xt,onTogglePin:Qt,recent:Gt,variant:"sheet",refreshEmpty:"No dictation models"})),v&&K?e("div",{class:"dictation__field"},e("span",{class:"label"},"Sends as"),e("span",{class:"dictation__kind-readout",title:ke(m,K)||K},Te(K))):null),e("ul",{class:"dictation__options"},e("li",null,e("label",{class:"dictation__option-row",for:"dictation-live"},e("span",{class:"dictation__option-body"},e("span",{class:"dictation__option-title"},"Live transcription"),e("span",{class:"dictation__option-note"},"In a chat, the composer fills in as you speak instead of waiting for you to stop. This page still records and transcribes once.")),e("span",{class:"switch"},e("input",{id:"dictation-live",type:"checkbox",checked:Kt,onChange:t=>pe(!!(t.target&&t.target.checked))}),e("span",{class:"switch__track","aria-hidden":"true"},e("span",{class:"switch__thumb"}))))),e("li",null,e("div",{class:"dictation__option-row"},e("span",{class:"dictation__option-body"},e("label",{class:"dictation__option-title",for:"dictation-language"},"Language"),e("span",{class:"dictation__option-note"},"Optional. An ISO-639-1 or BCP-47 code (en, fr, de) that biases decoding instead of leaving it to guess.")),e("input",{class:"input dictation__option-input",id:"dictation-language",type:"text",placeholder:"en, fr, de…",value:ot,onInput:t=>Ut(t.target.value.slice(0,20))}))),e("li",null,e("div",{class:"dictation__option-row"},e("span",{class:"dictation__option-body"},e("label",{class:"dictation__option-title",for:"dictation-prompt"},"Vocabulary hint"),e("span",{class:"dictation__option-note"},"Optional. Names and jargon the provider should expect — the OpenAI-style prompt field.")),e("input",{class:"input dictation__option-input",id:"dictation-prompt",type:"text",placeholder:"mouaif, MediaRecorder, SSE…",value:st,onInput:t=>Ht(t.target.value.slice(0,400))})))),e("div",{class:"dictation__catalog-note"},e("span",{class:"hint hint--compact dictation__catalog-line","aria-busy":Q.loading?"true":void 0},Q.loading?e("span",{class:"dictation__spinner","aria-hidden":"true"}):null,Q.text),u&&Pt.length?e("button",{class:"dictation__refresh",type:"button",disabled:k||w,onClick:le,"aria-label":"Refresh the model list from the provider"},k?"Refreshing…":"Refresh"):null),St?e("p",{class:"hint hint--compact dictation__selected-badge"},v.id+" — "+St):null,lt?e("p",{class:"hint hint--compact dictation__error"},"Could not read the model list: "+lt):null,kt.length?e("div",{class:"dictation__failures"},kt.map(t=>e("p",{key:t.provider,class:"hint hint--compact dictation__error"},"No models from "+Ne(t.provider)+" — "+t.error))):null,!w&&!k&&!l.length?e("p",{class:"hint hint--compact"},Pt.length?"Your providers returned no usable models. Check the connection in Settings → Providers, then tap Refresh.":"No models yet, and no provider connection to list them from. Connect a provider in Settings → Providers (or add a model to this project in .mouaif.json), then come back."):null),e("div",{class:"group"},e("div",{class:"group__title"},"Test"),e("div",{class:"dictation__card dictation__card--recorder"},e("div",{class:"dictation__clock"},e("span",{class:"dictation__time","aria-live":"polite"},et(ft)),e("span",{class:"dictation__limit"},"max "+et(Ot))),e("div",{class:"dictation__meter",role:"meter","aria-label":"Input level","aria-valuenow":Math.round(mt*100),"aria-valuemin":0,"aria-valuemax":100},e("span",{class:"dictation__meter-fill",style:{width:Math.round((_?mt:0)*100)+"%"}})),e("button",{class:"dictation__record"+(_?" is-recording":g?" is-secondary":""),type:"button",disabled:!pt||M,onClick:_?It:$t,"aria-busy":b==="handoff"?"true":void 0,"aria-label":_?"Stop recording":b==="handoff"?"Working…":Dt},b==="handoff"?e("span",{class:"dictation__spinner","aria-hidden":"true"}):e("span",{class:"dictation__record-dot","aria-hidden":"true"}),e("span",{class:"dictation__record-label"},b==="handoff"?"Working…":Dt)),g&&!_?e("div",{class:"dictation__recorder-actions"},e("span",{class:"dictation__ready"},"Recording ready"),e("button",{class:"dictation__transcribe",type:"button",disabled:!fe,onClick:te,"aria-busy":b==="transcribe"?"true":void 0,"aria-label":"Transcribe the recording"},b==="transcribe"?e("span",{class:"dictation__spinner dictation__spinner--on-accent","aria-hidden":"true"}):null,b==="transcribe"?"Transcribing…":"Transcribe")):null,pt?null:e("p",{class:"hint hint--compact"},"This browser cannot record audio. Dictation needs a browser with MediaRecorder and microphone access."))),e("div",{class:"group"},e("div",{class:"group__title"},"Transcript"),e("div",{class:"dictation__transcript","aria-busy":b==="transcribe"||b==="handoff"?"true":void 0},e("textarea",{class:"input dictation__textarea",rows:6,placeholder:"The transcript appears here and stays editable.",value:C,"aria-label":"Transcript",onInput:t=>Y(t.target.value)}),e("div",{class:"dictation__actions"},se.map(t=>e("button",{key:t.id,class:"btn"+(t.id==="send"?" btn--primary":"")+(t.id==="clear"?" btn--ghost":""),type:"button",disabled:t.disabled||M,onClick:()=>{if(t.id==="copy")return ee();if(t.id==="clear"){Y(""),i(""),c("");return}return re(t.id==="send")}},t.label)))),q?e("p",{class:"hint hint--compact dictation__last-run",title:Oe(q)},_e(q)):null),e("p",{class:"status dictation__status","aria-live":"polite","data-state":Yt||void 0},Wt))}function Be(l){return l&&l.length?"":"No models in this project"}function Ee(l,p){if(!p)return"no project";const m=R.value&&R.value.dir||"";return m&&m===p?"":l&&l.name||""}function Ne(l){const p=Le(l);return p&&p.label||l}function Oe(l){const p=l&&l.usage;if(!p)return"The provider reported no token counts for this run";const m=[];return p.promptTokens&&m.push("input "+p.promptTokens+" tok"),p.completionTokens&&m.push("output "+p.completionTokens+" tok"),m.length?"Provider usage: "+m.join(", "):"The provider reported no token counts for this run"}export{Ue as DictationView};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{A as S,d,a3 as y,h as D,_ as bt,k as e,S as M,D as pt,f as L}from"./index-BGvI4n0T.js";import{b as F,o as yt,l as mt,h as _t,H as vt,c as gt,d as wt,i as Ct,e as xt,f as kt,I as Pt,s as St,k as Dt,m as Mt,r as Lt,q as Ht,J as Tt,a as jt,z as Bt,A as zt,B as At,C as Et,F as Rt,v as Y,g as Zt}from"./codemirror-Bp6CUUFk.js";function Ft(l){if(!l)return null;const n=l.toLowerCase(),o=n.split("/").pop()||n;if(o==="dockerfile")return null;const a=(o.match(/\.[a-z0-9]+$/)||[""])[0];switch(a){case".js":case".jsx":case".mjs":case".cjs":return Y({jsx:a===".jsx"});case".ts":case".tsx":return Y({jsx:a===".tsx",typescript:!0});case".html":case".htm":case".svg":case".xml":case".mdx":return Rt();case".css":case".scss":case".sass":case".less":return Et();case".json":return At();case".md":case".markdown":return zt();case".py":return Bt();default:return null}}async function Ot(l,n){const o=new URLSearchParams;l&&o.set("projectDir",l),n&&o.set("dir",n);const a=await L("/api/files?"+o.toString());return a.status!==200?{error:a.body&&a.body.error||"HTTP "+a.status,code:a.body&&a.body.code}:{body:a.body}}async function Ut(l,n){const o=new URLSearchParams;l&&o.set("projectDir",l),o.set("path",n);const a=await L("/api/file?"+o.toString());return a.status!==200?{error:a.body&&a.body.error||"HTTP "+a.status,code:a.body&&a.body.code}:{body:a.body}}async function Vt(l,n,o){const a=await L("/api/file",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({projectDir:l,path:n,content:o})});return a.status!==200?{error:a.body&&a.body.error||"HTTP "+a.status,code:a.body&&a.body.code}:{body:a.body}}async function It(l,n){const o=new URLSearchParams;l&&o.set("projectDir",l),o.set("path",n);const a=await L("/api/file-media?"+o.toString());return a.status!==200?{error:a.body&&a.body.error||"HTTP "+a.status,code:a.body&&a.body.code}:{body:a.body}}function qt(l){const n=l.projectDir||"",o=l.onClose||(()=>{}),a=S(n),[H,O]=d(n||""),[v,tt]=d(""),[U,V]=d([]),[et,T]=d(""),[rt,j]=d(!1),[c,I]=d(null),[u,x]=d(null),[Kt,K]=d(!1),[at,g]=d(""),[m,w]=d(!1),[k,G]=d(!1),[B,h]=d(""),[st,z]=d(!1),[nt,it]=d(null),A=S(null),i=S(null),[P,W]=d(!1),E=S(null),b=y(async t=>{const r=typeof t=="string"&&t?t:a.current||n;j(!0),T("loading…");const s=await Ot(n,r);if(s.error){T(s.error),V([]),j(!1);return}a.current=s.body.dir,O(s.body.dir),tt(s.body.browseTop||""),V(Array.isArray(s.body.entries)?s.body.entries:[]),T((s.body.entries?s.body.entries.length:0)+" items"),j(!1)},[n]);D(()=>{b(n)},[b,n]);const _=y(()=>m?window.confirm("Discard unsaved changes?"):!0,[m]),R=y(async t=>{if(!_())return;x(null),g(""),h("loading…");const r=await Ut(n,t);if(r.error){h(r.error);return}const s={relPath:r.body.relPath,absPath:r.body.path,content:r.body.content,size:r.body.size,ext:r.body.ext};I(s),w(!1),h(s.size+" bytes")},[n,_]),q=y(async(t,r)=>{if(r&&r.forceText)return R(t);K(!0),g("loading…");const s=await It(n,t);if(K(!1),s.error){g(s.error),x(null);return}I(null),w(!1),h(""),x({relPath:s.body.relPath,absPath:s.body.path,dataUrl:s.body.dataUrl,mime:s.body.mime,size:s.body.size,ext:s.body.ext}),g(s.body.size+" bytes · "+s.body.mime)},[n,R]);bt(()=>{c&&ot(c)},[c]);function ot(t){if(!A.current)return;i.current&&(i.current.destroy(),i.current=null);const r=Ft(t.relPath),s=[mt(),_t(),vt(),gt(),wt(),Ct(),xt(),kt(),Pt(),St(Zt),Dt.of([{key:"Mod-s",preventDefault:!0,run:()=>(E.current&&E.current(),!0)},Mt,...Lt,...Ht,...Tt]),F.lineWrapping,yt,F.updateListener.of(p=>{p.docChanged&&(m||w(!0),B&&B.indexOf("saved")!==0&&h("edited"))})];r&&s.push(r);const f=jt.create({doc:t.content,extensions:s});i.current=new F({state:f,parent:A.current}),requestAnimationFrame(()=>{i.current&&i.current.requestMeasure()})}D(()=>()=>{i.current&&(i.current.destroy(),i.current=null)},[]),D(()=>{u&&i.current&&(i.current.destroy(),i.current=null)},[u]);const Z=y(async()=>{if(!c||!i.current)return;G(!0),h("saving…");const t=i.current.state.doc.toString(),r=await Vt(n,c.relPath,t);if(G(!1),r.error){h("save failed: "+r.error);return}w(!1),h("saved · "+r.body.size+" bytes")},[c,n]);D(()=>{E.current=Z},[Z]);const lt=y(()=>{!c||!i.current||_()&&(i.current.dispatch({changes:{from:0,to:i.current.state.doc.length,insert:c.content}}),w(!1),h("reverted"))},[c,_]),J=y(()=>{_()&&o()},[_,o]);function $(){const t=a.current||"";if(!t)return;const r=v||"";if(r&&t===r)return;const s=t.replace(/[\\/]+$/,""),f=Math.max(s.lastIndexOf("\\"),s.lastIndexOf("/"));if(f<=0){b(r);return}const p=s.slice(0,f);if(r&&p===r){b(r);return}b(p)}function ct(t){if(t.key==="Enter"){t.preventDefault();const r=(H||"").trim();b(r||v||n)}}function dt(){const t=(H||"").trim();b(t||v||n)}function N(t){if(t.type==="dir"){b(t.path);return}if(t.image){q(t.relPath);return}t.binary||R(t.relPath)}function ut(){if(!c||!i.current)return;const t=i.current.state.selection.main;if(t.empty){h("Select code before using Draft Craft."),i.current.focus();return}const r=i.current.state.sliceDoc(t.from,t.to),s=i.current.state.doc.lineAt(t.from).number,f=i.current.state.doc.lineAt(t.to).number;it({projectDir:n,text:c.relPath+":"+s+(f!==s?"-"+f:"")+`
|
|
2
|
+
`+r}),z(!0)}function ft(){const t=a.current||"";if(!t)return null;const r=v||"";if(r&&t===r)return null;const s=t.replace(/[\\/]+$/,""),f=Math.max(s.lastIndexOf("\\"),s.lastIndexOf("/"));return f>0?s.slice(0,f):r||null}function ht(){const t=v||n||"",r=a.current||t;if(!t||r===t)return e(M,null,e("span",{class:"fe__crumb fe__crumb--root"},t||"project"));const s=r.startsWith(t)?r.slice(t.length).replace(/^[\\/]+/,""):r,f=s?s.split(/[\\/]+/):[],p=[];p.push(e("button",{key:"root",type:"button",class:"fe__crumb fe__crumb--root",onClick:()=>b(t)},t.split(/[\\/]+/).pop()||t));let C=t;return f.forEach((Q,X)=>{C=C+(C.endsWith("\\")||C.endsWith("/")?"":"/")+Q,p.push(e("span",{key:"sep-"+X,class:"fe__crumb-sep","aria-hidden":"true"},"/")),p.push(e("button",{key:"seg-"+X,type:"button",class:"fe__crumb",onClick:()=>b(C)},Q))}),e(M,null,p)}return e("div",{class:"fe__overlay",role:"dialog","aria-modal":"true","aria-label":"File editor",onClick:t=>{}},e("div",{class:"fe__sheet"+(P?" fe__sheet--full":"")},e("div",{class:"fe__head"},P?e("div",{class:"fe__path-row"},e("button",{class:"icon-btn fe__iconbtn is-active",type:"button",onClick:()=>W(!1),"aria-label":"Show file list","aria-pressed":"true",title:"Show file list"},e("svg",{viewBox:"0 0 24 24",width:18,height:18,"aria-hidden":"true"},e("path",{d:"M4 4h7v16H4zM13 4h7v16h-7z",fill:"currentColor"}))),e("button",{class:"icon-btn icon-btn--close fe__iconbtn",type:"button",onClick:J,"aria-label":"Close",title:"Close"},e("svg",{viewBox:"0 0 24 24",width:16,height:16,"aria-hidden":"true"},e("path",{d:"M18.3 5.71 12 12l6.3 6.29-1.41 1.42L10.59 13.4 4.3 19.71 2.88 18.3 9.17 12 2.88 5.71 4.3 4.3l6.29 6.29 6.3-6.29 1.41 1.41Z",fill:"currentColor"})))):e("div",{class:"fe__path-row"},e("button",{class:"icon-btn fe__iconbtn",type:"button",onClick:$,"aria-label":"Up one folder",title:"Up"},e("svg",{viewBox:"0 0 24 24",width:16,height:16,"aria-hidden":"true"},e("path",{d:"M12 4 4 12l8 8 1.4-1.4L8.8 14H20v-2H8.8l4.6-4.6L12 4Z",fill:"currentColor",transform:"rotate(-90 12 12)"}))),e("div",{class:"fe__path-input-wrap"},e("input",{class:"input fe__path-input",type:"text",value:H,onInput:t=>O(t.currentTarget.value),onKeydown:ct,placeholder:"Folder path (absolute, or under this project)","aria-label":"Current folder",spellcheck:"false",autocomplete:"off"})),e("button",{class:"icon-btn fe__iconbtn",type:"button",onClick:dt,"aria-label":"Go to folder",title:"Go"},"Go"),e("button",{class:"icon-btn fe__iconbtn",type:"button",onClick:()=>b(a.current),"aria-label":"Refresh",title:"Refresh"},e("svg",{viewBox:"0 0 24 24",width:16,height:16,"aria-hidden":"true"},e("path",{d:"M12 4V1L7 6l5 5V7c3.31 0 6 2.69 6 6 0 1-.25 1.97-.7 2.8l1.46 1.46A7.93 7.93 0 0 0 20 13c0-4.42-3.58-8-8-8Zm-5.3 7.7A7.93 7.93 0 0 0 4 13c0 4.42 3.58 8 8 8v3l5-5-5-5v3c-3.31 0-6-2.69-6-6 0-1 .25-1.97.7-2.8L5.24 10.24Z",fill:"currentColor"}))),e("button",{class:"icon-btn fe__iconbtn",type:"button",onClick:()=>W(!0),"aria-label":"Hide file list","aria-pressed":"false",title:"Hide file list"},e("svg",{viewBox:"0 0 24 24",width:18,height:18,"aria-hidden":"true"},e("path",{d:"M3 5h18v14H3z",fill:"currentColor"}))),e("button",{class:"icon-btn icon-btn--close fe__iconbtn",type:"button",onClick:J,"aria-label":"Close",title:"Close"},e("svg",{viewBox:"0 0 24 24",width:16,height:16,"aria-hidden":"true"},e("path",{d:"M18.3 5.71 12 12l6.3 6.29-1.41 1.42L10.59 13.4 4.3 19.71 2.88 18.3 9.17 12 2.88 5.71 4.3 4.3l6.29 6.29 6.3-6.29 1.41 1.41Z",fill:"currentColor"})))),!P&&e("div",{class:"fe__crumbs","aria-label":"Breadcrumb"},ht()),!P&&e("div",{class:"fe__list-status"},e("span",{class:"status"},et||" "),ft()?e("button",{class:"btn btn--ghost fe__list-up",type:"button",onClick:$},"↑ Up"):null)),e("div",{class:"fe__body"},e("div",{class:"fe__list-wrap"},e("ul",{class:"fe__list","aria-label":"Files and folders"},U.length===0?e("li",{class:"fe__empty"},rt?"loading…":"no files here"):U.map(t=>e("li",{key:t.path,class:"fe__row"+(t.binary?" fe__row--binary":"")+(t.image?" fe__row--image":"")+(c&&c.relPath===t.relPath||u&&u.relPath===t.relPath?" is-open":""),role:"button",tabindex:t.binary?-1:0,"aria-disabled":t.binary?"true":"false",title:t.binary&&!t.image?"binary file — cannot be opened here":t.path,onClick:()=>N(t),onKeydown:r=>{t.binary&&!t.image||(r.key==="Enter"||r.key===" ")&&(r.preventDefault(),N(t))}},e("span",{class:"fe__row-icon","aria-hidden":"true"},t.type==="dir"?e("svg",{viewBox:"0 0 24 24",width:16,height:16},e("path",{d:"M3 6a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V6Z",fill:"currentColor"})):t.image?e("svg",{viewBox:"0 0 24 24",width:16,height:16},e("path",{d:"M21 5H3a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h18a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2Zm-9 11-3-4-2.5 3L4 13.5V7h16v9l-4-3-4 3Zm-4-7a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3Z",fill:"currentColor"})):e("svg",{viewBox:"0 0 24 24",width:16,height:16},e("path",{d:"M6 2h8l4 4v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2Zm7 1.5V7h3.5L13 3.5Z",fill:"currentColor"}))),e("span",{class:"fe__row-name"},t.name),t.type==="dir"?e("span",{class:"fe__row-meta"},t.hasChildren?"…":"·"):e("span",{class:"fe__row-meta"},t.image?"image":t.binary?"binary":t.size<1024?t.size+" B":Math.round(t.size/1024)+" KB"))))),e("div",{class:"fe__editor-wrap"},u?e(M,null,e("div",{class:"fe__editor-head"},e("div",{class:"fe__editor-path",title:u.absPath},u.relPath),e("div",{class:"fe__editor-actions"},u.ext===".svg"||u.mime==="image/svg+xml"?e("button",{class:"btn",type:"button",onClick:()=>q(u.relPath,{forceText:!0}),title:"Open this file in the code editor"},"Edit"):null,e("button",{class:"btn",type:"button",onClick:()=>{x(null),g("")},title:"Close preview"},"Close"))),e("div",{class:"fe__media-host"},e("img",{class:"fe__media-img",src:u.dataUrl,alt:u.relPath,draggable:"false"})),e("div",{class:"fe__editor-status"},e("span",{class:"status"},at||" "))):c?e(M,null,e("div",{class:"fe__editor-head"},e("div",{class:"fe__editor-path",title:c.absPath},c.relPath+(m?" •":"")),e("div",{class:"fe__editor-actions"},e("button",{class:"btn fe__draft-craft",type:"button",onClick:ut,disabled:k,title:"Add the selected code to any chat draft"},e("svg",{viewBox:"0 0 24 24",width:18,height:18,"aria-hidden":"true"},e("path",{d:"M4 4h16v13a3 3 0 0 1-3 3H7a3 3 0 0 1-3-3V4Zm2 2v9h4a2 2 0 0 0 4 0h4V6H6Z",fill:"currentColor"})),e("span",null,"Draft Craft")),e("button",{class:"btn",type:"button",onClick:lt,disabled:!m||k,title:"Revert to the last saved version"},"Revert"),e("button",{class:"btn btn--primary",type:"button",onClick:Z,disabled:!m||k,title:"Save (Ctrl/Cmd+S is wired to the editor save keymap via the browser default)","aria-label":"Save file"},k?"Saving…":"Save"))),e("div",{ref:A,class:"fe__editor-host"}),e("div",{class:"fe__editor-status"},e("span",{class:"status"},B||" "))):e("div",{class:"fe__editor-empty"},e("p",null,"Pick a file from the list to start editing, or tap an image to preview it."),e("p",{class:"fe__editor-hint"},"Tip: type a path above or use the breadcrumb to jump to a folder.")))),e(pt,{open:st,payload:nt,placement:"top",onClose:()=>z(!1),onAdded:t=>{z(!1),h("Selected code added with Draft Craft."),l.onDraftCraftAdded&&l.onDraftCraftAdded(t)}})))}export{qt as FileEditorView};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{d as m,a3 as H,h as gt,u as K,k as t,S as z,f as O,A as ht,c as pt}from"./index-BGvI4n0T.js";async function Q(s,a,n,o){const c=await O("/api/git",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({projectDir:s,action:a,args:n||"",message:o||""})});return c.status!==200?{ok:!1,stderr:c.body&&c.body.error||"HTTP "+c.status}:c.body||{ok:!1,stderr:"no response"}}async function X(s,a,n){const o=await O("/api/git",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({projectDir:s,action:a,files:n})});return o.status!==200?{ok:!1,stderr:o.body&&o.body.error||"HTTP "+o.status}:o.body||{ok:!1,stderr:"no response"}}function Y(s){return String(s||"").split(" -> ")[0]}function _t({diff:s}){if(!s)return null;const a=s.split(`
|
|
2
|
+
`);return t("div",{class:"gm__diff"},t("pre",{class:"gm__diff-pre","aria-label":"Diff"},a.map((n,o)=>{let c="";return n.startsWith("@@")?c=" gm__diff-hunk":n.startsWith("+")&&!n.startsWith("+++")?c=" gm__diff-add":n.startsWith("-")&&!n.startsWith("---")?c=" gm__diff-del":n.startsWith("diff --git")&&(c=" gm__diff-file"),t("span",{key:o,class:"gm__diff-line"+c},n||" ")})))}function et({file:s,defaultOpen:a,action:n,onAction:o,busy:c}){const[h,r]=m(!!a),p=!!(s.diff&&s.diff.trim()),f=n==="unstage"?"Unstage":"Stage";return t("div",{class:"gm__file"},t("div",{class:"gm__file-head"},t("button",{class:"gm__file-toggle",type:"button",onClick:()=>r(!h),"aria-expanded":String(h),"aria-label":(p?"Toggle diff for ":"")+s.path},t("span",{class:"gm__file-status gm__file-status--"+s.status},s.statusText||s.status),t("span",{class:"gm__file-path",title:s.path},s.path),p?t("span",{class:"gm__file-caret","aria-hidden":"true"},h?"▾":"▸"):null),o?t("button",{class:"gm__file-action"+(n==="unstage"?" gm__file-action--unstage":""),type:"button",disabled:c,onClick:()=>o(s),"aria-label":f+" "+s.path,title:f+" "+s.path},f):null),h&&p?t(_t,{diff:s.diff}):null)}function ft({projectDir:s,commit:a,busy:n,onAction:o}){const[c,h]=m(!1),[r,p]=m(null),[f,l]=m(""),[g,w]=m(!1),[d,T]=m(""),y=H(async()=>{if(!(g||d===a.hash)){w(!0),l("");try{const _=new URLSearchParams;_.set("projectDir",s),_.set("hash",a.hash);const b=await O("/api/git/commit-files?"+_.toString());b.status===200&&b.body&&b.body.ok?(p(b.body.files||[]),T(a.hash)):l(b.body&&b.body.error||"HTTP "+b.status)}catch(_){l(String(_))}w(!1)}},[s,a.hash,g,d]);function C(){const _=!c;h(_),_&&d!==a.hash&&y()}return t("div",{class:"gm__commit"},t("div",{class:"gm__commit-row"},t("button",{class:"gm__commit-head"+(c?" is-open":""),type:"button",onClick:C,"aria-expanded":String(c),"aria-label":"Toggle commit "+a.short},t("span",{class:"gm__commit-caret","aria-hidden":"true"},c?"▾":"▸"),t("span",{class:"gm__commit-hash"},a.short),t("span",{class:"gm__commit-subject",title:a.subject},a.subject),t("span",{class:"gm__commit-meta"},a.author?t("span",{class:"gm__commit-author"},a.author):null,a.date?t("span",{class:"gm__commit-date"},yt(a.date)):null)),t(bt,{commit:a,busy:n,onAction:o})),c&&t("div",{class:"gm__commit-body"},g?t("div",{class:"gm__empty"},"Loading files…"):f?t("div",{class:"gm__error"},t("p",null,f),t("button",{class:"btn",type:"button",onClick:y},"Retry")):r===null?t("div",{class:"gm__empty"},"Loading files…"):r.length===0?t("div",{class:"gm__empty"},"No file changes in this commit"):r.map((_,b)=>t(et,{key:_.path+"-"+b,file:_}))))}async function $(s){try{return await navigator.clipboard.writeText(s||""),!0}catch{try{const n=document.createElement("textarea");n.value=s||"",n.style.position="fixed",n.style.opacity="0",document.body.appendChild(n),n.select();const o=document.execCommand("copy");return document.body.removeChild(n),o}catch{return!1}}}function bt({commit:s,busy:a,onAction:n}){const[o,c]=m(!1),h=ht(null);return pt(h,()=>c(!1),o),t("div",{ref:h,class:"gm__commit-menu"},t("button",{class:"gm__commit-menu-btn",type:"button","aria-haspopup":"true","aria-expanded":String(o),"aria-label":"Commit options for "+s.short,title:"Commit options",disabled:!!a,onClick:r=>{r.stopPropagation(),c(!o)}},"⋯"),t("div",{class:"gm__commit-menu-pop",hidden:!o,role:"menu",onClick:r=>r.stopPropagation()},t("button",{type:"button",onClick:()=>{c(!1),n(s,"copy-hash")}},"Copy hash"),t("button",{type:"button",onClick:()=>{c(!1),n(s,"copy-message")}},"Copy message"),t("div",{class:"gm__commit-menu-sep",role:"separator"}),t("button",{type:"button",disabled:!!a,onClick:()=>{c(!1),n(s,"checkout")}},"Checkout"),t("button",{type:"button",disabled:!!a,onClick:()=>{c(!1),n(s,"cherry-pick")}},"Cherry-pick"),t("button",{type:"button",disabled:!!a,onClick:()=>{c(!1),n(s,"revert")}},"Revert")))}function yt(s){if(!s)return"";const a=new Date(s);if(isNaN(a.getTime()))return s;const n={day:"numeric",month:"short",year:"numeric",hour:"2-digit",minute:"2-digit"};try{return a.toLocaleString(void 0,n)}catch{return s}}function Ct(s){const a=/stash@\{(\d+)\}/.exec(s||"");return a?a[1]:s}function St(s){const{projectDir:a,onClose:n}=s,[o,c]=m(null),[h,r]=m(""),[p,f]=m(!0),[l,g]=m(""),[w,d]=m(""),[T,y]=m(0),[C,_]=m([]),[b,G]=m(0),[M,W]=m(!1),[j,st]=m(!1),[A,at]=m(!1),[nt,Z]=m(!1),[E,V]=m(""),[P,L]=m(null),v=H(async()=>{if(!a){f(!1),r("No project selected");return}if(!nt){Z(!0),f(!0),r(""),d(""),_([]),y(0),G(0);try{const e=new URLSearchParams;e.set("projectDir",a);const i=await O("/api/git/info?"+e.toString());i.status!==200?r(i.body&&i.body.error||"HTTP "+i.status):i.body&&i.body.ok===!1?r(i.body&&i.body.error||"Not a git repository"):(c(i.body),_(i.body.commits||[]),y(i.body.commits&&i.body.commits.length||0))}catch(e){r(String(e))}f(!1),Z(!1)}},[a]);gt(()=>{v()},[v]);const F=H(()=>{L(e=>e?null:(n&&n(),e))},[n]),ot=K({onClose:F}),it=K({onClose:F,active:!!P});async function k(e,i,u){if(l)return;g(e),d("");const q=await Q(a,e,i,u);q.ok?(d("git "+e+" ok"),await v()):d((q.stderr||"git "+e+" failed").trim()||"git "+e+" failed"),g("")}async function U(e,i){if(l)return;g(e),d("");const u=await X(a,e,[Y(i.path)]);u.ok?(d(""),await v()):d((u.stderr||"git "+e+" failed").trim()||"git "+e+" failed"),g("")}async function ct(){if(l)return;g("add"),d("");const e=R.map(u=>Y(u.path));if(!e.length){g("");return}const i=await X(a,"add",e);i.ok?(d(""),await v()):d((i.stderr||"git add failed").trim()||"git add failed"),g("")}async function J(){if(l)return;const e=E.trim();if(!e){d("Enter a commit message");return}g("commit"),d("");const i=await Q(a,"commit","",e);i.ok?(V(""),d("Committed…"),await v()):d((i.stderr||"git commit failed").trim()||"git commit failed"),g("")}function lt(e){l||e===(o&&o.branch)||k("checkout",e)}async function rt(e,i){if(!l){if(i==="copy-hash"){const u=await $(e.hash);d(u?"Copied hash "+e.short:"Copy failed");return}if(i==="copy-message"){const u=await $(e.subject);d(u?"Copied message":"Copy failed");return}if(i==="checkout"){L({commit:e,action:"checkout"});return}if(i==="cherry-pick"){L({commit:e,action:"cherry-pick"});return}if(i==="revert"){L({commit:e,action:"revert"});return}}}function dt(){const e=P;L(null),e&&(e.action==="checkout"?k("checkout",e.commit.hash):e.action==="cherry-pick"?k("cherry-pick",e.commit.hash):e.action==="revert"&&k("revert",e.commit.hash))}async function mt(){if(!M){W(!0);try{const e=new URLSearchParams;e.set("projectDir",a),e.set("offset",String(T)),e.set("count","20");const i=await O("/api/git/commits?"+e.toString());i.status===200&&i.body&&i.body.ok&&(_(u=>u.concat(i.body.commits||[])),G(i.body.total||0),y(u=>u+(i.body.commits?i.body.commits.length:0)))}catch{}W(!1)}}const D=o&&o.staged||[],R=o&&o.unstaged||[],N=o&&o.stashes||[],B=o&&o.branch||"",I=o&&o.branches||[],S=o&&o.ahead||0,x=o&&o.behind||0,ut=b>0?C.length<b:C.length>=20;return t("div",{class:"gm__overlay",role:"dialog","aria-modal":"true","aria-label":"Git"},t("div",{class:"gm__sheet",ref:ot},t("div",{class:"gm__head"},t("label",{class:"gm__branch-wrap"},t("span",{class:"gm__branch-label"},"Branch"),t("select",{class:"gm__branch-select",value:B,disabled:l==="checkout",onChange:e=>lt(e.currentTarget.value),"aria-label":"Branch"},I.length===0?t("option",{value:B},B||"(none)"):I.map(e=>t("option",{key:e,value:e},e)))),t("button",{class:"icon-btn gm__iconbtn"+(x>0?" gm__iconbtn--has-count":""),type:"button",onClick:()=>k("pull"),disabled:!!l,"aria-label":"Pull from remote"+(x>0?" ("+x+" behind)":""),title:"Pull"+(x>0?" ("+x+" behind)":"")},x>0?t("span",{class:"gm__iconbtn-badge"},x):null,t("svg",{viewBox:"0 0 24 24",width:18,height:18,"aria-hidden":"true"},t("path",{d:"M11 3v9.6L8.4 10 7 11.4l5 5 5-5L15.6 10 13 12.6V3h-2Zm-7 15h16v2H4v-2Z",fill:"currentColor"}))),t("button",{class:"icon-btn gm__iconbtn"+(S>0?" gm__iconbtn--has-count":""),type:"button",onClick:()=>k("push"),disabled:!!l,"aria-label":"Push to remote"+(S>0?" ("+S+" ahead)":""),title:"Push"+(S>0?" ("+S+" ahead)":"")},S>0?t("span",{class:"gm__iconbtn-badge"},S):null,t("svg",{viewBox:"0 0 24 24",width:18,height:18,"aria-hidden":"true"},t("path",{d:"M12 3a1 1 0 0 1 .7.3l5 5-1.4 1.4L13 7.4V20h-2V7.4L7.7 9.7 6.3 8.3l5-5A1 1 0 0 1 12 3Z",fill:"currentColor"}))),t("button",{class:"icon-btn gm__iconbtn",type:"button",onClick:v,disabled:p||!!l,"aria-label":"Refresh git status",title:"Refresh"},t("svg",{viewBox:"0 0 24 24",width:16,height:16,"aria-hidden":"true"},t("path",{d:"M12 4V1L7 6l5 5V7c3.31 0 6 2.69 6 6 0 1-.25 1.97-.7 2.8l1.46 1.46A7.93 7.93 0 0 0 20 13c0-4.42-3.58-8-8-8Zm-5.3 7.7A7.93 7.93 0 0 0 4 13c0 4.42 3.58 8 8 8v3l5-5-5-5v3c-3.31 0-6-2.69-6-6 0-1 .25-1.97.7-2.8L5.24 10.24Z",fill:"currentColor"}))),t("button",{class:"icon-btn icon-btn--close gm__iconbtn",type:"button",onClick:n,"aria-label":"Close",title:"Close"},t("svg",{viewBox:"0 0 24 24",width:16,height:16,"aria-hidden":"true"},t("path",{d:"M18.3 5.71 12 12l6.3 6.29-1.41 1.42L10.59 13.4 4.3 19.71 2.88 18.3 9.17 12 2.88 5.71 4.3 4.3l6.29 6.29 6.3-6.29 1.41 1.41Z",fill:"currentColor"})))),w?t("div",{class:"gm__notice"},w):null,t("div",{class:"gm__body"},p?t("div",{class:"gm__empty"},"Loading git status…"):h?t("div",{class:"gm__error"},t("p",null,h),t("button",{class:"btn",type:"button",onClick:v},"Retry")):t(z,null,t("div",{class:"gm__section"},t("button",{class:"gm__section-head",type:"button",onClick:()=>at(!A),"aria-expanded":String(A),"aria-controls":"gm-section-stash"},t("span",{class:"gm__section-caret","aria-hidden":"true"},A?"▾":"▸"),t("span",{class:"gm__section-title"},"Stash"),t("span",{class:"gm__section-count"},N.length||"")),A&&t("div",{id:"gm-section-stash",class:"gm__section-body"},t("div",{class:"gm__stash-toolbar"},t("button",{class:"gm__stash-btn gm__stash-btn--stashup",type:"button",disabled:!!l,onClick:()=>k("stash"),"aria-label":"Stash working changes",title:"Stash up"},t("svg",{viewBox:"0 0 24 24",width:14,height:14,"aria-hidden":"true"},t("path",{d:"M12 4v9.6L9.4 11 8 12.4l5 5 5-5-1.4-1.4L14 13.6V4h-2ZM5 20h14v2H5v-2Z",fill:"currentColor"}))," Stash up")),N.length===0?t("div",{class:"gm__empty"},"No stashed changes"):N.map((e,i)=>t(vt,{key:i,stash:e,busy:l,onApply:u=>k("stash-apply",u),onPop:u=>k("stash-pop",u),onDrop:u=>k("stash-drop",u)})))),t(tt,{id:"staged",title:"Staged changes",files:D,defaultOpen:D.length>0,emptyText:"Nothing staged",action:"unstage",onAction:e=>U("unstage",e),busy:!!l,header:t("div",{class:"gm__commit-bar"},t("input",{class:"input gm__commit-input",type:"text",value:E,onInput:e=>V(e.currentTarget.value),placeholder:"Commit message","aria-label":"Commit message",disabled:!!l,onKeyDown:e=>{e.key==="Enter"&&(e.preventDefault(),J())}}),t("button",{class:"gm__commit-btn",type:"button",disabled:!!l||D.length===0,onClick:J,"aria-label":"Commit staged changes",title:"Commit staged changes"},l==="commit"?"Committing…":"Commit"))}),t(tt,{id:"unstaged",title:"Unstaged changes",files:R,defaultOpen:R.length>0,emptyText:"Working tree clean",action:"add",onAction:e=>U("add",e),busy:!!l}),D.length>0&&R.length>0?t("button",{class:"gm__stage-all",type:"button",disabled:!!l,onClick:ct,"aria-label":"Stage all unstaged changes",title:"Stage all unstaged changes"},"Stage all ("+R.length+")"):null,t("div",{class:"gm__section"},t("button",{class:"gm__section-head",type:"button",onClick:()=>st(!j),"aria-expanded":String(j),"aria-controls":"gm-section-commits"},t("span",{class:"gm__section-caret","aria-hidden":"true"},j?"▾":"▸"),t("span",{class:"gm__section-title"},"Recent commits"),t("span",{class:"gm__section-count"},b||C.length||"")),j&&t("div",{id:"gm-section-commits",class:"gm__section-body"},C.length===0?t("div",{class:"gm__empty"},"No commits yet"):t(z,null,C.map((e,i)=>t(ft,{key:e.hash||i,projectDir:a,commit:e,busy:!!l,onAction:rt})),ut?t("button",{class:"gm__load-more",type:"button",disabled:M,onClick:mt},M?"Loading…":"Load more"):null)))))),P?t(kt,{commit:P.commit,action:P.action,busy:!!l,sheetRef:it,onCancel:()=>L(null),onConfirm:dt}):null)}function kt({commit:s,action:a,busy:n,onCancel:o,onConfirm:c,sheetRef:h}){let r="Confirm git action",p="";a==="checkout"?(r="Checkout commit?",p="Check out "+s.short+" as a detached HEAD. Any current changes must be committed or stashed first."):a==="cherry-pick"?(r="Cherry-pick commit?",p="Apply "+s.short+(' "'+s.subject+'"')+" onto the current branch as a new commit."):a==="revert"&&(r="Revert commit?",p="Create a new commit that undoes "+s.short+(' "'+s.subject+'"')+".");const f=a==="checkout"?"Checkout":a==="cherry-pick"?"Cherry-pick":"Revert";return t("div",{class:"gm__overlay gm__confirm",role:"presentation",onClick:n?void 0:o},t("div",{class:"gm__sheet gm__confirm-sheet",role:"alertdialog","aria-modal":"true","aria-label":r,ref:h,onClick:l=>l.stopPropagation()},t("div",{class:"gm__confirm-body"},t("strong",{class:"gm__confirm-title"},r),t("p",{class:"gm__confirm-msg"},p),t("div",{class:"gm__confirm-actions"},t("button",{class:"btn gm__confirm-cancel",type:"button",disabled:n,onClick:o},"Cancel"),t("button",{class:"btn gm__confirm-go",type:"button","data-danger":"1",disabled:n,onClick:c},n?"Working…":f)))))}function tt({id:s,title:a,files:n,defaultOpen:o,emptyText:c,renderFile:h,action:r,onAction:p,busy:f,header:l}){const[g,w]=m(o),d=n?n.length:0,T=h||((y,C)=>t(et,{key:y.path+"-"+C,file:y,action:r,onAction:p,busy:f}));return t("div",{class:"gm__section"},t("button",{class:"gm__section-head"+(g?" is-open":""),type:"button",onClick:()=>w(!g),"aria-expanded":String(g),"aria-controls":"gm-section-"+s},t("span",{class:"gm__section-caret","aria-hidden":"true"},g?"▾":"▸"),t("span",{class:"gm__section-title"},a),t("span",{class:"gm__section-count"},d||"")),g&&t("div",{id:"gm-section-"+s,class:"gm__section-body"},l||null,d===0?t("div",{class:"gm__empty"},c||"Nothing here"):n.map((y,C)=>T(y,C))))}function vt({stash:s,busy:a,onApply:n,onPop:o,onDrop:c}){return t("div",{class:"gm__stash-row"},t("span",{class:"gm__stash-index"},Ct(s.index)),t("span",{class:"gm__stash-main"},t("span",{class:"gm__stash-subject",title:s.subject},s.subject),s.date?t("span",{class:"gm__stash-date"},s.date):null),t("div",{class:"gm__stash-actions"},t("button",{class:"gm__stash-btn",type:"button",disabled:!!a,onClick:()=>n(s.index),title:"Apply stash without removing it"},"Apply"),t("button",{class:"gm__stash-btn",type:"button",disabled:!!a,onClick:()=>o(s.index),title:"Apply stash and remove it"},"Pop"),t("button",{class:"gm__stash-btn gm__stash-btn--danger",type:"button",disabled:!!a,onClick:()=>c(s.index),title:"Delete stash"},"Drop")))}export{St as GitModal};
|