morpheus-cli 0.3.2 → 0.3.3
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/README.md +18 -4
- package/dist/channels/telegram.js +85 -10
- package/dist/config/schemas.js +2 -1
- package/dist/http/api.js +19 -0
- package/dist/runtime/oracle.js +8 -0
- package/dist/runtime/telephonist.js +164 -46
- package/dist/runtime/tools/factory.js +18 -0
- package/dist/ui/assets/index-CwvCMGLo.css +1 -0
- package/dist/ui/assets/{index-DqzvLXXS.js → index-D9REy_tK.js} +23 -23
- package/dist/ui/index.html +2 -2
- package/package.json +3 -2
- package/dist/ui/assets/index-f1sqiqOo.css +0 -1
package/README.md
CHANGED
|
@@ -114,7 +114,7 @@ Additionally, you can use environment variables for API keys instead of storing
|
|
|
114
114
|
|----------|-------------|----------|
|
|
115
115
|
| `OPENAI_API_KEY` | OpenAI API key (if using GPT) | No |
|
|
116
116
|
| `ANTHROPIC_API_KEY` | Anthropic API key (if using Claude) | No |
|
|
117
|
-
| `GOOGLE_API_KEY` | Google AI key (for Gemini
|
|
117
|
+
| `GOOGLE_API_KEY` | Google AI key (for Gemini LLM/Audio) | No |
|
|
118
118
|
| `OPENROUTER_API_KEY` | OpenRouter API key (if using OpenRouter) | No |
|
|
119
119
|
| `THE_ARCHITECT_PASS` | Web Dashboard access password | Recommended |
|
|
120
120
|
| `TELEGRAM_BOT_TOKEN` | Telegram BotFather token | No |
|
|
@@ -207,11 +207,22 @@ Track your token usage across different providers and models directly from the W
|
|
|
207
207
|
|
|
208
208
|
### 🎙️ Audio Transcription (Telegram)
|
|
209
209
|
Send voice messages directly to the Telegram bot. Morpheus will:
|
|
210
|
-
1. Transcribe the audio using
|
|
210
|
+
1. Transcribe the audio using the configured provider.
|
|
211
211
|
2. Process the text as a standard prompt.
|
|
212
212
|
3. Reply with the answer.
|
|
213
213
|
|
|
214
|
-
|
|
214
|
+
Supported audio providers:
|
|
215
|
+
|
|
216
|
+
| Provider | Method | Model example |
|
|
217
|
+
|---|---|---|
|
|
218
|
+
| **Google Gemini** | Native audio file upload | `gemini-2.5-flash-lite` |
|
|
219
|
+
| **OpenAI** | Whisper API (`/audio/transcriptions`) | `whisper-1` |
|
|
220
|
+
| **OpenRouter** | `input_audio` via `@openrouter/sdk` (multimodal models) | `google/gemini-2.5-flash` |
|
|
221
|
+
| **Ollama** | Whisper local via OpenAI-compatible endpoint | `whisper` |
|
|
222
|
+
|
|
223
|
+
> Ollama requires a Whisper model loaded: `ollama pull whisper`
|
|
224
|
+
|
|
225
|
+
*Configure `audio.provider` and `audio.apiKey` in Settings or `config.yaml`.*
|
|
215
226
|
|
|
216
227
|
### 🤖 Telegram Commands
|
|
217
228
|
The Morpheus Telegram bot supports several commands for interacting with the agent:
|
|
@@ -303,7 +314,10 @@ ui:
|
|
|
303
314
|
# Audio Transcription Support
|
|
304
315
|
audio:
|
|
305
316
|
enabled: true
|
|
306
|
-
|
|
317
|
+
provider: "google" # google | openai | openrouter | anthropic | ollama
|
|
318
|
+
model: "gemini-2.5-flash-lite"
|
|
319
|
+
apiKey: "YOUR_API_KEY" # Optional if using same provider as LLM
|
|
320
|
+
base_url: "" # Required for openrouter/ollama
|
|
307
321
|
maxDurationSeconds: 300
|
|
308
322
|
```
|
|
309
323
|
|
|
@@ -7,18 +7,20 @@ import os from 'os';
|
|
|
7
7
|
import { spawn } from 'child_process';
|
|
8
8
|
import { ConfigManager } from '../config/manager.js';
|
|
9
9
|
import { DisplayManager } from '../runtime/display.js';
|
|
10
|
-
import {
|
|
10
|
+
import { createTelephonist } from '../runtime/telephonist.js';
|
|
11
11
|
import { readPid, isProcessRunning, checkStalePid } from '../runtime/lifecycle.js';
|
|
12
12
|
import { SQLiteChatMessageHistory } from '../runtime/memory/sqlite.js';
|
|
13
13
|
import { SatiRepository } from '../runtime/memory/sati/repository.js';
|
|
14
14
|
import { MCPManager } from '../config/mcp-manager.js';
|
|
15
|
+
import { Construtor } from '../runtime/tools/factory.js';
|
|
15
16
|
export class TelegramAdapter {
|
|
16
17
|
bot = null;
|
|
17
18
|
isConnected = false;
|
|
18
19
|
display = DisplayManager.getInstance();
|
|
19
20
|
config = ConfigManager.getInstance();
|
|
20
21
|
oracle;
|
|
21
|
-
telephonist =
|
|
22
|
+
telephonist = null;
|
|
23
|
+
telephonistProvider = null;
|
|
22
24
|
history = new SQLiteChatMessageHistory({ sessionId: '' });
|
|
23
25
|
HELP_MESSAGE = `/start - Show this welcome message and available commands
|
|
24
26
|
/status - Check the status of the Morpheus agent
|
|
@@ -30,6 +32,7 @@ export class TelegramAdapter {
|
|
|
30
32
|
/newsession - Archive current session and start fresh
|
|
31
33
|
/sessions - List all sessions with titles and switch between them
|
|
32
34
|
/restart - Restart the Morpheus agent
|
|
35
|
+
/mcpreload - Reload MCP servers without restarting
|
|
33
36
|
/mcp or /mcps - List registered MCP servers`;
|
|
34
37
|
constructor(oracle) {
|
|
35
38
|
this.oracle = oracle;
|
|
@@ -96,12 +99,17 @@ export class TelegramAdapter {
|
|
|
96
99
|
await ctx.reply("Audio transcription is currently disabled.");
|
|
97
100
|
return;
|
|
98
101
|
}
|
|
99
|
-
const apiKey = config.audio.apiKey ||
|
|
102
|
+
const apiKey = config.audio.apiKey ||
|
|
103
|
+
(config.llm.provider === config.audio.provider ? config.llm.api_key : undefined);
|
|
100
104
|
if (!apiKey) {
|
|
101
|
-
this.display.log(`Audio transcription failed: No
|
|
102
|
-
await ctx.reply(
|
|
105
|
+
this.display.log(`Audio transcription failed: No API key available for provider '${config.audio.provider}'`, { source: 'Telephonist', level: 'error' });
|
|
106
|
+
await ctx.reply(`Audio transcription requires an API key for provider '${config.audio.provider}'. Please configure \`audio.apiKey\` or use the same provider as your LLM.`);
|
|
103
107
|
return;
|
|
104
108
|
}
|
|
109
|
+
if (!this.telephonist || this.telephonistProvider !== config.audio.provider) {
|
|
110
|
+
this.telephonist = createTelephonist(config.audio);
|
|
111
|
+
this.telephonistProvider = config.audio.provider;
|
|
112
|
+
}
|
|
105
113
|
const duration = ctx.message.voice.duration;
|
|
106
114
|
if (duration > config.audio.maxDurationSeconds) {
|
|
107
115
|
await ctx.reply(`Voice message too long. Max duration is ${config.audio.maxDurationSeconds}s.`);
|
|
@@ -141,7 +149,8 @@ export class TelegramAdapter {
|
|
|
141
149
|
}
|
|
142
150
|
}
|
|
143
151
|
catch (error) {
|
|
144
|
-
|
|
152
|
+
const detail = error?.cause?.message || error?.response?.data?.error?.message || error.message;
|
|
153
|
+
this.display.log(`Audio processing error for @${user}: ${detail}`, { source: 'Telephonist', level: 'error' });
|
|
145
154
|
await ctx.reply("Sorry, I failed to process your audio message.");
|
|
146
155
|
}
|
|
147
156
|
finally {
|
|
@@ -265,6 +274,32 @@ export class TelegramAdapter {
|
|
|
265
274
|
}
|
|
266
275
|
await ctx.reply('Action cancelled.');
|
|
267
276
|
});
|
|
277
|
+
this.bot.action(/^toggle_mcp_/, async (ctx) => {
|
|
278
|
+
const data = ctx.callbackQuery.data;
|
|
279
|
+
// format: toggle_mcp_enable_<name> or toggle_mcp_disable_<name>
|
|
280
|
+
const match = data.match(/^toggle_mcp_(enable|disable)_(.+)$/);
|
|
281
|
+
if (!match) {
|
|
282
|
+
await ctx.answerCbQuery('Invalid action');
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
const [, action, serverName] = match;
|
|
286
|
+
const enable = action === 'enable';
|
|
287
|
+
try {
|
|
288
|
+
await MCPManager.setServerEnabled(serverName, enable);
|
|
289
|
+
await ctx.answerCbQuery(`${enable ? '✅ Enabled' : '❌ Disabled'}: ${serverName}`);
|
|
290
|
+
if (ctx.updateType === 'callback_query') {
|
|
291
|
+
ctx.deleteMessage().catch(() => { });
|
|
292
|
+
}
|
|
293
|
+
const user = ctx.from?.username || ctx.from?.first_name || 'unknown';
|
|
294
|
+
this.display.log(`MCP '${serverName}' ${enable ? 'enabled' : 'disabled'} by @${user}`, { source: 'Telegram', level: 'info' });
|
|
295
|
+
await this.handleMcpListCommand(ctx, user);
|
|
296
|
+
await ctx.reply(`⚠️ Use /mcpreload for the changes to take effect.`);
|
|
297
|
+
}
|
|
298
|
+
catch (error) {
|
|
299
|
+
await ctx.answerCbQuery('Failed to update MCP');
|
|
300
|
+
await ctx.reply(`❌ Failed to ${enable ? 'enable' : 'disable'} MCP '${serverName}': ${error.message}`);
|
|
301
|
+
}
|
|
302
|
+
});
|
|
268
303
|
this.bot.launch().catch((err) => {
|
|
269
304
|
if (this.isConnected) {
|
|
270
305
|
this.display.log(`Telegram bot error: ${err}`, { source: 'Telegram', level: 'error' });
|
|
@@ -347,6 +382,9 @@ export class TelegramAdapter {
|
|
|
347
382
|
case '/restart':
|
|
348
383
|
await this.handleRestartCommand(ctx, user);
|
|
349
384
|
break;
|
|
385
|
+
case '/mcpreload':
|
|
386
|
+
await this.handleMcpReloadCommand(ctx, user);
|
|
387
|
+
break;
|
|
350
388
|
case '/mcp':
|
|
351
389
|
case '/mcps':
|
|
352
390
|
await this.handleMcpListCommand(ctx, user);
|
|
@@ -686,19 +724,43 @@ How can I assist you today?`;
|
|
|
686
724
|
this.display.log(`Error checking restart notification: ${error.message}`, { source: 'Telegram', level: 'error' });
|
|
687
725
|
}
|
|
688
726
|
}
|
|
727
|
+
async handleMcpReloadCommand(ctx, user) {
|
|
728
|
+
try {
|
|
729
|
+
await ctx.reply('🔄 Reloading MCP servers...');
|
|
730
|
+
await this.oracle.reloadTools();
|
|
731
|
+
await ctx.reply('✅ MCP servers reloaded successfully.');
|
|
732
|
+
this.display.log(`MCP reload triggered by @${user}`, { source: 'Telegram', level: 'info' });
|
|
733
|
+
}
|
|
734
|
+
catch (error) {
|
|
735
|
+
await ctx.reply(`❌ Failed to reload MCP servers: ${error.message}`);
|
|
736
|
+
this.display.log(`MCP reload failed: ${error.message}`, { source: 'Telegram', level: 'error' });
|
|
737
|
+
}
|
|
738
|
+
}
|
|
689
739
|
async handleMcpListCommand(ctx, user) {
|
|
690
740
|
try {
|
|
691
|
-
const servers = await
|
|
741
|
+
const [servers, probeResults] = await Promise.all([
|
|
742
|
+
MCPManager.listServers(),
|
|
743
|
+
Construtor.probe(),
|
|
744
|
+
]);
|
|
692
745
|
if (servers.length === 0) {
|
|
693
746
|
await ctx.reply('*No MCP Servers Configured*\n\nThere are currently no MCP servers configured in the system.', { parse_mode: 'Markdown' });
|
|
694
747
|
return;
|
|
695
748
|
}
|
|
749
|
+
const probeMap = new Map(probeResults.map(r => [r.name, r]));
|
|
696
750
|
let response = `*MCP Servers (${servers.length})*\n\n`;
|
|
751
|
+
const keyboard = [];
|
|
697
752
|
servers.forEach((server, index) => {
|
|
698
|
-
const
|
|
753
|
+
const enabledStatus = server.enabled ? '✅ Enabled' : '❌ Disabled';
|
|
699
754
|
const transport = server.config.transport.toUpperCase();
|
|
755
|
+
const probe = probeMap.get(server.name);
|
|
756
|
+
const connectionStatus = probe
|
|
757
|
+
? probe.ok
|
|
758
|
+
? `🟢 Connected (${probe.toolCount} tools)`
|
|
759
|
+
: `🔴 Failed`
|
|
760
|
+
: '⚪ Unknown';
|
|
700
761
|
response += `*${index + 1}. ${server.name}*\n`;
|
|
701
|
-
response += `Status: ${
|
|
762
|
+
response += `Status: ${enabledStatus}\n`;
|
|
763
|
+
response += `Connection: ${connectionStatus}\n`;
|
|
702
764
|
response += `Transport: ${transport}\n`;
|
|
703
765
|
if (server.config.transport === 'stdio') {
|
|
704
766
|
response += `Command: \`${server.config.command}\`\n`;
|
|
@@ -709,9 +771,22 @@ How can I assist you today?`;
|
|
|
709
771
|
else if (server.config.transport === 'http') {
|
|
710
772
|
response += `URL: \`${server.config.url}\`\n`;
|
|
711
773
|
}
|
|
774
|
+
if (probe && !probe.ok && probe.error) {
|
|
775
|
+
const shortError = probe.error.length > 80 ? probe.error.slice(0, 80) + '…' : probe.error;
|
|
776
|
+
response += `Error: \`${shortError}\`\n`;
|
|
777
|
+
}
|
|
712
778
|
response += '\n';
|
|
779
|
+
if (server.enabled) {
|
|
780
|
+
keyboard.push([{ text: `❌ Disable ${server.name}`, callback_data: `toggle_mcp_disable_${server.name}` }]);
|
|
781
|
+
}
|
|
782
|
+
else {
|
|
783
|
+
keyboard.push([{ text: `✅ Enable ${server.name}`, callback_data: `toggle_mcp_enable_${server.name}` }]);
|
|
784
|
+
}
|
|
785
|
+
});
|
|
786
|
+
await ctx.reply(response, {
|
|
787
|
+
parse_mode: 'Markdown',
|
|
788
|
+
reply_markup: { inline_keyboard: keyboard },
|
|
713
789
|
});
|
|
714
|
-
await ctx.reply(response, { parse_mode: 'Markdown' });
|
|
715
790
|
}
|
|
716
791
|
catch (error) {
|
|
717
792
|
this.display.log('Error listing MCP servers: ' + (error instanceof Error ? error.message : String(error)), { source: 'Telegram', level: 'error' });
|
package/dist/config/schemas.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { DEFAULT_CONFIG } from '../types/config.js';
|
|
3
3
|
export const AudioConfigSchema = z.object({
|
|
4
|
-
provider: z.enum(['google']).default(DEFAULT_CONFIG.audio.provider),
|
|
4
|
+
provider: z.enum(['google', 'openai', 'openrouter', 'ollama']).default(DEFAULT_CONFIG.audio.provider),
|
|
5
5
|
model: z.string().min(1).default(DEFAULT_CONFIG.audio.model),
|
|
6
6
|
enabled: z.boolean().default(DEFAULT_CONFIG.audio.enabled),
|
|
7
7
|
apiKey: z.string().optional(),
|
|
8
|
+
base_url: z.string().optional(),
|
|
8
9
|
maxDurationSeconds: z.number().default(DEFAULT_CONFIG.audio.maxDurationSeconds),
|
|
9
10
|
supportedMimeTypes: z.array(z.string()).default(DEFAULT_CONFIG.audio.supportedMimeTypes),
|
|
10
11
|
});
|
package/dist/http/api.js
CHANGED
|
@@ -10,6 +10,7 @@ import { spawn } from 'child_process';
|
|
|
10
10
|
import { z } from 'zod';
|
|
11
11
|
import { MCPManager } from '../config/mcp-manager.js';
|
|
12
12
|
import { MCPServerConfigSchema } from '../config/schemas.js';
|
|
13
|
+
import { Construtor } from '../runtime/tools/factory.js';
|
|
13
14
|
async function readLastLines(filePath, n) {
|
|
14
15
|
try {
|
|
15
16
|
const content = await fs.readFile(filePath, 'utf8');
|
|
@@ -466,6 +467,24 @@ export function createApiRouter(oracle) {
|
|
|
466
467
|
res.status(status).json({ error: 'Failed to toggle MCP server.', details: error });
|
|
467
468
|
}
|
|
468
469
|
});
|
|
470
|
+
router.post('/mcp/reload', async (_req, res) => {
|
|
471
|
+
try {
|
|
472
|
+
await oracle.reloadTools();
|
|
473
|
+
res.json({ ok: true, message: 'MCP tools reloaded successfully.' });
|
|
474
|
+
}
|
|
475
|
+
catch (error) {
|
|
476
|
+
res.status(500).json({ error: 'Failed to reload MCP tools.', details: String(error) });
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
router.get('/mcp/status', async (_req, res) => {
|
|
480
|
+
try {
|
|
481
|
+
const results = await Construtor.probe();
|
|
482
|
+
res.json({ servers: results });
|
|
483
|
+
}
|
|
484
|
+
catch (error) {
|
|
485
|
+
res.status(500).json({ error: 'Failed to probe MCP servers.', details: String(error) });
|
|
486
|
+
}
|
|
487
|
+
});
|
|
469
488
|
// Keep PUT for backward compatibility if needed, or remove.
|
|
470
489
|
// Tasks says Implement POST. I'll remove PUT to avoid confusion or redirect it.
|
|
471
490
|
router.put('/config', async (req, res) => {
|
package/dist/runtime/oracle.js
CHANGED
|
@@ -319,4 +319,12 @@ You maintain intent until resolution.
|
|
|
319
319
|
}
|
|
320
320
|
await this.history.clear();
|
|
321
321
|
}
|
|
322
|
+
async reloadTools() {
|
|
323
|
+
if (!this.provider) {
|
|
324
|
+
throw new Error("Oracle not initialized. Call initialize() first.");
|
|
325
|
+
}
|
|
326
|
+
const tools = await Construtor.create();
|
|
327
|
+
this.provider = await ProviderFactory.create(this.config.llm, tools);
|
|
328
|
+
this.display.log(`MCP tools reloaded (${tools.length} tools)`, { source: 'Oracle' });
|
|
329
|
+
}
|
|
322
330
|
}
|
|
@@ -1,55 +1,173 @@
|
|
|
1
1
|
import { GoogleGenAI } from '@google/genai';
|
|
2
|
-
|
|
2
|
+
import OpenAI from 'openai';
|
|
3
|
+
import { OpenRouter } from '@openrouter/sdk';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
class GeminiTelephonist {
|
|
6
|
+
model;
|
|
7
|
+
constructor(model) {
|
|
8
|
+
this.model = model;
|
|
9
|
+
}
|
|
10
|
+
async transcribe(filePath, mimeType, apiKey) {
|
|
11
|
+
const ai = new GoogleGenAI({ apiKey });
|
|
12
|
+
const uploadResult = await ai.files.upload({
|
|
13
|
+
file: filePath,
|
|
14
|
+
config: { mimeType }
|
|
15
|
+
});
|
|
16
|
+
const response = await ai.models.generateContent({
|
|
17
|
+
model: this.model,
|
|
18
|
+
contents: [
|
|
19
|
+
{
|
|
20
|
+
role: 'user',
|
|
21
|
+
parts: [
|
|
22
|
+
{
|
|
23
|
+
fileData: {
|
|
24
|
+
fileUri: uploadResult.uri,
|
|
25
|
+
mimeType: uploadResult.mimeType
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
{ text: "Transcribe this audio message accurately. Return only the transcribed text without any additional commentary." }
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
});
|
|
33
|
+
const text = response.text;
|
|
34
|
+
if (!text) {
|
|
35
|
+
throw new Error('No transcription generated');
|
|
36
|
+
}
|
|
37
|
+
const usage = response.usageMetadata;
|
|
38
|
+
const usageMetadata = {
|
|
39
|
+
input_tokens: usage?.promptTokenCount ?? 0,
|
|
40
|
+
output_tokens: usage?.candidatesTokenCount ?? 0,
|
|
41
|
+
total_tokens: usage?.totalTokenCount ?? 0,
|
|
42
|
+
input_token_details: {
|
|
43
|
+
cache_read: usage?.cachedContentTokenCount ?? 0
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
return { text, usage: usageMetadata };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Uses OpenAI Whisper API (/audio/transcriptions).
|
|
51
|
+
* Also used for Ollama local Whisper via OpenAI-compatible endpoint.
|
|
52
|
+
*/
|
|
53
|
+
class WhisperTelephonist {
|
|
54
|
+
model;
|
|
55
|
+
baseURL;
|
|
56
|
+
constructor(model, baseURL) {
|
|
57
|
+
this.model = model;
|
|
58
|
+
this.baseURL = baseURL;
|
|
59
|
+
}
|
|
60
|
+
async transcribe(filePath, _mimeType, apiKey) {
|
|
61
|
+
const client = new OpenAI({
|
|
62
|
+
apiKey,
|
|
63
|
+
...(this.baseURL ? { baseURL: this.baseURL } : {})
|
|
64
|
+
});
|
|
65
|
+
const transcription = await client.audio.transcriptions.create({
|
|
66
|
+
model: this.model,
|
|
67
|
+
file: fs.createReadStream(filePath),
|
|
68
|
+
});
|
|
69
|
+
const text = transcription.text;
|
|
70
|
+
if (!text) {
|
|
71
|
+
throw new Error('No transcription generated');
|
|
72
|
+
}
|
|
73
|
+
// Whisper API does not return token usage
|
|
74
|
+
const usageMetadata = {
|
|
75
|
+
input_tokens: 0,
|
|
76
|
+
output_tokens: 0,
|
|
77
|
+
total_tokens: 0,
|
|
78
|
+
};
|
|
79
|
+
return { text, usage: usageMetadata };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Uses OpenRouter SDK with input_audio content type.
|
|
84
|
+
* Supports any multimodal model on OpenRouter that accepts audio input
|
|
85
|
+
* (e.g. google/gemini-2.5-flash, openai/gpt-4o-audio-preview).
|
|
86
|
+
*/
|
|
87
|
+
class OpenRouterTelephonist {
|
|
88
|
+
model;
|
|
89
|
+
constructor(model) {
|
|
90
|
+
this.model = model;
|
|
91
|
+
}
|
|
3
92
|
async transcribe(filePath, mimeType, apiKey) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const response = await ai.models.generateContent({
|
|
14
|
-
model: 'gemini-2.5-flash-lite',
|
|
15
|
-
contents: [
|
|
93
|
+
const client = new OpenRouter({ apiKey });
|
|
94
|
+
// Derive audio format from mimeType (e.g. 'audio/ogg' → 'ogg')
|
|
95
|
+
const format = mimeType.split('/')[1]?.split(';')[0] || 'ogg';
|
|
96
|
+
const audioBuffer = fs.readFileSync(filePath);
|
|
97
|
+
const base64Audio = audioBuffer.toString('base64');
|
|
98
|
+
const result = await client.chat.send({
|
|
99
|
+
chatGenerationParams: {
|
|
100
|
+
model: this.model,
|
|
101
|
+
messages: [
|
|
16
102
|
{
|
|
17
103
|
role: 'user',
|
|
18
|
-
|
|
104
|
+
content: [
|
|
19
105
|
{
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
mimeType: uploadResult.mimeType
|
|
23
|
-
}
|
|
106
|
+
type: 'text',
|
|
107
|
+
text: 'Transcribe this audio message accurately. Return only the transcribed text without any additional commentary.',
|
|
24
108
|
},
|
|
25
|
-
{
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
};
|
|
45
|
-
return { text, usage: usageMetadata };
|
|
46
|
-
}
|
|
47
|
-
catch (error) {
|
|
48
|
-
// Wrap error for clarity
|
|
49
|
-
if (error instanceof Error) {
|
|
50
|
-
throw new Error(`Audio transcription failed: ${error.message}`);
|
|
51
|
-
}
|
|
52
|
-
throw error;
|
|
109
|
+
{
|
|
110
|
+
type: 'input_audio',
|
|
111
|
+
inputAudio: {
|
|
112
|
+
data: base64Audio,
|
|
113
|
+
format: format,
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
stream: false,
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
const message = result?.choices?.[0]?.message;
|
|
123
|
+
const text = typeof message?.content === 'string'
|
|
124
|
+
? message.content
|
|
125
|
+
: message?.content?.[0]?.text ?? '';
|
|
126
|
+
if (!text) {
|
|
127
|
+
throw new Error('No transcription generated');
|
|
53
128
|
}
|
|
129
|
+
const usage = result?.usage;
|
|
130
|
+
const usageMetadata = {
|
|
131
|
+
input_tokens: usage?.prompt_tokens ?? 0,
|
|
132
|
+
output_tokens: usage?.completion_tokens ?? 0,
|
|
133
|
+
total_tokens: usage?.total_tokens ?? 0,
|
|
134
|
+
};
|
|
135
|
+
return { text, usage: usageMetadata };
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Factory function that creates the appropriate ITelephonist implementation
|
|
140
|
+
* based on the audio provider configuration.
|
|
141
|
+
*
|
|
142
|
+
* Supported providers:
|
|
143
|
+
* - google: Google Gemini (native audio file upload)
|
|
144
|
+
* - openai: OpenAI Whisper API (/audio/transcriptions)
|
|
145
|
+
* - openrouter: OpenRouter SDK with input_audio (multimodal models)
|
|
146
|
+
* - ollama: Ollama local Whisper via OpenAI-compatible endpoint
|
|
147
|
+
*/
|
|
148
|
+
export function createTelephonist(config) {
|
|
149
|
+
switch (config.provider) {
|
|
150
|
+
case 'google':
|
|
151
|
+
return new GeminiTelephonist(config.model);
|
|
152
|
+
case 'openai':
|
|
153
|
+
return new WhisperTelephonist(config.model);
|
|
154
|
+
case 'openrouter':
|
|
155
|
+
return new OpenRouterTelephonist(config.model);
|
|
156
|
+
case 'ollama':
|
|
157
|
+
// Ollama exposes an OpenAI-compatible /v1/audio/transcriptions endpoint
|
|
158
|
+
// Requires a Whisper model loaded: `ollama pull whisper`
|
|
159
|
+
return new WhisperTelephonist(config.model, (config.base_url || 'http://localhost:11434') + '/v1');
|
|
160
|
+
default:
|
|
161
|
+
throw new Error(`Unsupported audio provider: '${config.provider}'. Supported: google, openai, openrouter, ollama.`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// Legacy export for backward compatibility
|
|
165
|
+
export class Telephonist {
|
|
166
|
+
delegate;
|
|
167
|
+
constructor() {
|
|
168
|
+
this.delegate = new GeminiTelephonist('gemini-2.5-flash-lite');
|
|
169
|
+
}
|
|
170
|
+
async transcribe(filePath, mimeType, apiKey) {
|
|
171
|
+
return this.delegate.transcribe(filePath, mimeType, apiKey);
|
|
54
172
|
}
|
|
55
173
|
}
|
|
@@ -41,6 +41,24 @@ function wrapToolWithSanitizedSchema(tool) {
|
|
|
41
41
|
return tool;
|
|
42
42
|
}
|
|
43
43
|
export class Construtor {
|
|
44
|
+
static async probe() {
|
|
45
|
+
const mcpServers = await loadMCPConfig();
|
|
46
|
+
const results = [];
|
|
47
|
+
for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
|
|
48
|
+
try {
|
|
49
|
+
const client = new MultiServerMCPClient({
|
|
50
|
+
mcpServers: { [serverName]: serverConfig },
|
|
51
|
+
onConnectionError: "ignore",
|
|
52
|
+
});
|
|
53
|
+
const tools = await client.getTools();
|
|
54
|
+
results.push({ name: serverName, ok: true, toolCount: tools.length });
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
results.push({ name: serverName, ok: false, toolCount: 0, error: String(error) });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return results;
|
|
61
|
+
}
|
|
44
62
|
static async create() {
|
|
45
63
|
const display = DisplayManager.getInstance();
|
|
46
64
|
const mcpServers = await loadMCPConfig();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:Courier New,Courier,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}body{--tw-bg-opacity: 1;background-color:rgb(240 244 248 / var(--tw-bg-opacity, 1));font-family:Courier New,Courier,monospace;--tw-text-opacity: 1;color:rgb(26 26 26 / var(--tw-text-opacity, 1));transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.3s}body:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(0 0 0 / var(--tw-bg-opacity, 1));--tw-text-opacity: 1;color:rgb(0 143 17 / var(--tw-text-opacity, 1))}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{--tw-bg-opacity: 1;background-color:rgb(240 244 248 / var(--tw-bg-opacity, 1))}:is(.dark *)::-webkit-scrollbar-track{--tw-bg-opacity: 1;background-color:rgb(0 0 0 / var(--tw-bg-opacity, 1))}::-webkit-scrollbar-thumb{--tw-bg-opacity: 1;background-color:rgb(179 212 252 / var(--tw-bg-opacity, 1))}:is(.dark *)::-webkit-scrollbar-thumb{--tw-bg-opacity: 1;background-color:rgb(0 59 0 / var(--tw-bg-opacity, 1))}::-webkit-scrollbar-thumb:hover{--tw-bg-opacity: 1;background-color:rgb(74 144 226 / var(--tw-bg-opacity, 1))}:is(.dark *)::-webkit-scrollbar-thumb:hover{--tw-bg-opacity: 1;background-color:rgb(0 143 17 / var(--tw-bg-opacity, 1))}.container{width:100%}@media(min-width:640px){.container{max-width:640px}}@media(min-width:768px){.container{max-width:768px}}@media(min-width:1024px){.container{max-width:1024px}}@media(min-width:1280px){.container{max-width:1280px}}@media(min-width:1536px){.container{max-width:1536px}}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.bottom-0{bottom:0}.left-0{left:0}.left-3{left:.75rem}.right-0{right:0}.top-0{top:0}.top-1\/2{top:50%}.z-10{z-index:10}.z-50{z-index:50}.col-span-2{grid-column:span 2 / span 2}.mx-4{margin-left:1rem;margin-right:1rem}.mx-auto{margin-left:auto;margin-right:auto}.my-2{margin-top:.5rem;margin-bottom:.5rem}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mr-1{margin-right:.25rem}.mr-2{margin-right:.5rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-10{margin-top:2.5rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-10{height:2.5rem}.h-11{height:2.75rem}.h-12{height:3rem}.h-2{height:.5rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-64{height:16rem}.h-8{height:2rem}.h-9{height:2.25rem}.h-full{height:100%}.h-screen{height:100vh}.max-h-\[90vh\]{max-height:90vh}.min-h-0{min-height:0px}.min-h-screen{min-height:100vh}.w-10{width:2.5rem}.w-11{width:2.75rem}.w-12{width:3rem}.w-16{width:4rem}.w-2{width:.5rem}.w-32{width:8rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-64{width:16rem}.w-8{width:2rem}.w-80{width:20rem}.w-full{width:100%}.min-w-0{min-width:0px}.min-w-max{min-width:-moz-max-content;min-width:max-content}.max-w-4xl{max-width:56rem}.max-w-6xl{max-width:72rem}.max-w-\[80\%\]{max-width:80%}.max-w-lg{max-width:32rem}.max-w-md{max-width:28rem}.max-w-none{max-width:none}.max-w-sm{max-width:24rem}.max-w-xl{max-width:36rem}.max-w-xs{max-width:20rem}.flex-1{flex:1 1 0%}.flex-shrink-0,.shrink-0{flex-shrink:0}.flex-grow{flex-grow:1}.caption-bottom{caption-side:bottom}.-translate-y-1\/2{--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-1{--tw-translate-x: .25rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-6{--tw-translate-x: 1.5rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes bounce{0%,to{transform:translateY(-25%);animation-timing-function:cubic-bezier(.8,0,1,1)}50%{transform:none;animation-timing-function:cubic-bezier(0,0,.2,1)}}.animate-bounce{animation:bounce 1s infinite}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize-none{resize:none}.resize{resize:both}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-col-reverse{flex-direction:column-reverse}.items-start{align-items:flex-start}.items-center{align-items:center}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-6{gap:1.5rem}.space-x-1>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.25rem * var(--tw-space-x-reverse));margin-left:calc(.25rem * calc(1 - var(--tw-space-x-reverse)))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-1\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.375rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.375rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.space-y-8>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(2rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(2rem * var(--tw-space-y-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse: 0;border-top-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px * var(--tw-divide-y-reverse))}.divide-azure-border>:not([hidden])~:not([hidden]){--tw-divide-opacity: 1;border-color:rgb(179 212 252 / var(--tw-divide-opacity, 1))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:1rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-xl{border-radius:.75rem}.rounded-t-md{border-top-left-radius:.375rem;border-top-right-radius:.375rem}.rounded-bl-none{border-bottom-left-radius:0}.rounded-br-none{border-bottom-right-radius:0}.border{border-width:1px}.border-x{border-left-width:1px;border-right-width:1px}.border-b{border-bottom-width:1px}.border-b-2{border-bottom-width:2px}.border-l{border-left-width:1px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-azure-border{--tw-border-opacity: 1;border-color:rgb(179 212 252 / var(--tw-border-opacity, 1))}.border-azure-primary{--tw-border-opacity: 1;border-color:rgb(0 102 204 / var(--tw-border-opacity, 1))}.border-gray-100{--tw-border-opacity: 1;border-color:rgb(243 244 246 / var(--tw-border-opacity, 1))}.border-gray-200{--tw-border-opacity: 1;border-color:rgb(229 231 235 / var(--tw-border-opacity, 1))}.border-matrix-primary{--tw-border-opacity: 1;border-color:rgb(0 59 0 / var(--tw-border-opacity, 1))}.border-red-200{--tw-border-opacity: 1;border-color:rgb(254 202 202 / var(--tw-border-opacity, 1))}.border-red-300{--tw-border-opacity: 1;border-color:rgb(252 165 165 / var(--tw-border-opacity, 1))}.border-red-500{--tw-border-opacity: 1;border-color:rgb(239 68 68 / var(--tw-border-opacity, 1))}.border-red-500\/50{border-color:#ef444480}.border-transparent{border-color:transparent}.bg-azure-active{--tw-bg-opacity: 1;background-color:rgb(187 222 251 / var(--tw-bg-opacity, 1))}.bg-azure-bg{--tw-bg-opacity: 1;background-color:rgb(240 244 248 / var(--tw-bg-opacity, 1))}.bg-azure-border{--tw-bg-opacity: 1;background-color:rgb(179 212 252 / var(--tw-bg-opacity, 1))}.bg-azure-hover{--tw-bg-opacity: 1;background-color:rgb(227 242 253 / var(--tw-bg-opacity, 1))}.bg-azure-primary{--tw-bg-opacity: 1;background-color:rgb(0 102 204 / var(--tw-bg-opacity, 1))}.bg-azure-primary\/10{background-color:#0066cc1a}.bg-azure-surface{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.bg-azure-surface\/50{background-color:#ffffff80}.bg-azure-surface\/60{background-color:#fff9}.bg-azure-surface\/80{background-color:#fffc}.bg-black\/50{background-color:#00000080}.bg-gray-100{--tw-bg-opacity: 1;background-color:rgb(243 244 246 / var(--tw-bg-opacity, 1))}.bg-gray-200{--tw-bg-opacity: 1;background-color:rgb(229 231 235 / var(--tw-bg-opacity, 1))}.bg-gray-50{--tw-bg-opacity: 1;background-color:rgb(249 250 251 / var(--tw-bg-opacity, 1))}.bg-green-500\/20{background-color:#22c55e33}.bg-matrix-highlight{--tw-bg-opacity: 1;background-color:rgb(0 255 65 / var(--tw-bg-opacity, 1))}.bg-purple-100{--tw-bg-opacity: 1;background-color:rgb(243 232 255 / var(--tw-bg-opacity, 1))}.bg-red-50{--tw-bg-opacity: 1;background-color:rgb(254 242 242 / var(--tw-bg-opacity, 1))}.bg-red-500{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity, 1))}.bg-red-500\/20{background-color:#ef444433}.bg-red-900\/10{background-color:#7f1d1d1a}.bg-red-900\/20{background-color:#7f1d1d33}.bg-slate-300{--tw-bg-opacity: 1;background-color:rgb(203 213 225 / var(--tw-bg-opacity, 1))}.bg-transparent{background-color:transparent}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.bg-\[linear-gradient\(rgba\(18\,16\,16\,0\)_50\%\,rgba\(0\,0\,0\,0\.1\)_50\%\)\,linear-gradient\(90deg\,rgba\(0\,255\,0\,0\.03\)\,rgba\(0\,255\,0\,0\.01\)\)\]{background-image:linear-gradient(#12101000 50%,#0000001a 50%),linear-gradient(90deg,#00ff0008,#00ff0003)}.bg-\[length\:100\%_2px\,3px_100\%\]{background-size:100% 2px,3px 100%}.p-0{padding:0}.p-1{padding:.25rem}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-8{padding-left:2rem;padding-right:2rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-4{padding-bottom:1rem}.pb-px{padding-bottom:1px}.pl-10{padding-left:2.5rem}.pl-4{padding-left:1rem}.pr-4{padding-right:1rem}.pt-0{padding-top:0}.pt-16{padding-top:4rem}.pt-4{padding-top:1rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-middle{vertical-align:middle}.font-mono{font-family:Courier New,Courier,monospace}.font-sans{font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji"}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-\[10px\]{font-size:10px}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.italic{font-style:italic}.leading-none{line-height:1}.leading-relaxed{line-height:1.625}.tracking-tight{letter-spacing:-.025em}.tracking-tighter{letter-spacing:-.05em}.tracking-wide{letter-spacing:.025em}.tracking-wider{letter-spacing:.05em}.tracking-widest{letter-spacing:.1em}.text-azure-accent{--tw-text-opacity: 1;color:rgb(33 150 243 / var(--tw-text-opacity, 1))}.text-azure-primary{--tw-text-opacity: 1;color:rgb(0 102 204 / var(--tw-text-opacity, 1))}.text-azure-text-muted{--tw-text-opacity: 1;color:rgb(136 153 168 / var(--tw-text-opacity, 1))}.text-azure-text-primary{--tw-text-opacity: 1;color:rgb(26 26 26 / var(--tw-text-opacity, 1))}.text-azure-text-primary\/80{color:#1a1a1acc}.text-azure-text-secondary{--tw-text-opacity: 1;color:rgb(92 107 125 / var(--tw-text-opacity, 1))}.text-blue-500{--tw-text-opacity: 1;color:rgb(59 130 246 / var(--tw-text-opacity, 1))}.text-gray-400{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.text-gray-500{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.text-gray-700{--tw-text-opacity: 1;color:rgb(55 65 81 / var(--tw-text-opacity, 1))}.text-gray-800{--tw-text-opacity: 1;color:rgb(31 41 55 / var(--tw-text-opacity, 1))}.text-green-500{--tw-text-opacity: 1;color:rgb(34 197 94 / var(--tw-text-opacity, 1))}.text-green-600{--tw-text-opacity: 1;color:rgb(22 163 74 / var(--tw-text-opacity, 1))}.text-matrix-highlight{--tw-text-opacity: 1;color:rgb(0 255 65 / var(--tw-text-opacity, 1))}.text-purple-600{--tw-text-opacity: 1;color:rgb(147 51 234 / var(--tw-text-opacity, 1))}.text-red-500{--tw-text-opacity: 1;color:rgb(239 68 68 / var(--tw-text-opacity, 1))}.text-red-600{--tw-text-opacity: 1;color:rgb(220 38 38 / var(--tw-text-opacity, 1))}.text-red-700{--tw-text-opacity: 1;color:rgb(185 28 28 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.text-zinc-50{--tw-text-opacity: 1;color:rgb(250 250 250 / var(--tw-text-opacity, 1))}.underline-offset-4{text-underline-offset:4px}.placeholder-azure-text-secondary\/50::-moz-placeholder{color:#5c6b7d80}.placeholder-azure-text-secondary\/50::placeholder{color:#5c6b7d80}.opacity-0{opacity:0}.opacity-20{opacity:.2}.opacity-30{opacity:.3}.opacity-50{opacity:.5}.opacity-70{opacity:.7}.opacity-80{opacity:.8}.shadow{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-md{--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.outline-none{outline:2px solid transparent;outline-offset:2px}.outline{outline-style:solid}.ring-offset-white{--tw-ring-offset-color: #fff}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur-sm{--tw-backdrop-blur: blur(4px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}.duration-300{transition-duration:.3s}.hover\:border-azure-primary:hover{--tw-border-opacity: 1;border-color:rgb(0 102 204 / var(--tw-border-opacity, 1))}.hover\:border-red-400:hover{--tw-border-opacity: 1;border-color:rgb(248 113 113 / var(--tw-border-opacity, 1))}.hover\:bg-amber-50:hover{--tw-bg-opacity: 1;background-color:rgb(255 251 235 / var(--tw-bg-opacity, 1))}.hover\:bg-azure-active:hover{--tw-bg-opacity: 1;background-color:rgb(187 222 251 / var(--tw-bg-opacity, 1))}.hover\:bg-azure-hover:hover{--tw-bg-opacity: 1;background-color:rgb(227 242 253 / var(--tw-bg-opacity, 1))}.hover\:bg-azure-hover\/50:hover{background-color:#e3f2fd80}.hover\:bg-azure-primary\/10:hover{background-color:#0066cc1a}.hover\:bg-azure-primary\/90:hover{background-color:#0066cce6}.hover\:bg-azure-secondary:hover{--tw-bg-opacity: 1;background-color:rgb(74 144 226 / var(--tw-bg-opacity, 1))}.hover\:bg-blue-50:hover{--tw-bg-opacity: 1;background-color:rgb(239 246 255 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-100:hover{--tw-bg-opacity: 1;background-color:rgb(243 244 246 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-200:hover{--tw-bg-opacity: 1;background-color:rgb(229 231 235 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-50:hover{--tw-bg-opacity: 1;background-color:rgb(249 250 251 / var(--tw-bg-opacity, 1))}.hover\:bg-green-50:hover{--tw-bg-opacity: 1;background-color:rgb(240 253 244 / var(--tw-bg-opacity, 1))}.hover\:bg-red-400:hover{--tw-bg-opacity: 1;background-color:rgb(248 113 113 / var(--tw-bg-opacity, 1))}.hover\:bg-red-50:hover{--tw-bg-opacity: 1;background-color:rgb(254 242 242 / var(--tw-bg-opacity, 1))}.hover\:bg-red-500\/90:hover{background-color:#ef4444e6}.hover\:bg-transparent:hover{background-color:transparent}.hover\:text-amber-500:hover{--tw-text-opacity: 1;color:rgb(245 158 11 / var(--tw-text-opacity, 1))}.hover\:text-azure-primary:hover{--tw-text-opacity: 1;color:rgb(0 102 204 / var(--tw-text-opacity, 1))}.hover\:text-azure-secondary:hover{--tw-text-opacity: 1;color:rgb(74 144 226 / var(--tw-text-opacity, 1))}.hover\:text-azure-text-primary:hover{--tw-text-opacity: 1;color:rgb(26 26 26 / var(--tw-text-opacity, 1))}.hover\:text-blue-500:hover{--tw-text-opacity: 1;color:rgb(59 130 246 / var(--tw-text-opacity, 1))}.hover\:text-blue-700:hover{--tw-text-opacity: 1;color:rgb(29 78 216 / var(--tw-text-opacity, 1))}.hover\:text-red-500:hover{--tw-text-opacity: 1;color:rgb(239 68 68 / var(--tw-text-opacity, 1))}.hover\:text-red-600:hover{--tw-text-opacity: 1;color:rgb(220 38 38 / var(--tw-text-opacity, 1))}.hover\:text-red-700:hover{--tw-text-opacity: 1;color:rgb(185 28 28 / var(--tw-text-opacity, 1))}.hover\:underline:hover{text-decoration-line:underline}.focus\:border-azure-primary:focus{--tw-border-opacity: 1;border-color:rgb(0 102 204 / var(--tw-border-opacity, 1))}.focus\:border-red-500:focus{--tw-border-opacity: 1;border-color:rgb(239 68 68 / var(--tw-border-opacity, 1))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-1:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-2:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-azure-primary:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(0 102 204 / var(--tw-ring-opacity, 1))}.focus\:ring-red-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(239 68 68 / var(--tw-ring-opacity, 1))}.focus\:ring-zinc-950:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(12 12 12 / var(--tw-ring-opacity, 1))}.focus\:ring-offset-2:focus{--tw-ring-offset-width: 2px}.focus\:ring-offset-azure-bg:focus{--tw-ring-offset-color: #F0F4F8}.focus-visible\:outline-none:focus-visible{outline:2px solid transparent;outline-offset:2px}.focus-visible\:ring-2:focus-visible{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus-visible\:ring-zinc-950:focus-visible{--tw-ring-opacity: 1;--tw-ring-color: rgb(12 12 12 / var(--tw-ring-opacity, 1))}.focus-visible\:ring-offset-2:focus-visible{--tw-ring-offset-width: 2px}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:bg-azure-border:disabled{--tw-bg-opacity: 1;background-color:rgb(179 212 252 / var(--tw-bg-opacity, 1))}.disabled\:opacity-50:disabled{opacity:.5}.group:hover .group-hover\:text-azure-primary{--tw-text-opacity: 1;color:rgb(0 102 204 / var(--tw-text-opacity, 1))}.group:hover .group-hover\:opacity-100{opacity:1}.dark\:divide-matrix-primary\/30:is(.dark *)>:not([hidden])~:not([hidden]){border-color:#003b004d}.dark\:rounded-none:is(.dark *){border-radius:0}.dark\:border:is(.dark *){border-width:1px}.dark\:border-x:is(.dark *){border-left-width:1px;border-right-width:1px}.dark\:border-t:is(.dark *){border-top-width:1px}.dark\:border-green-500:is(.dark *){--tw-border-opacity: 1;border-color:rgb(34 197 94 / var(--tw-border-opacity, 1))}.dark\:border-green-800:is(.dark *){--tw-border-opacity: 1;border-color:rgb(22 101 52 / var(--tw-border-opacity, 1))}.dark\:border-matrix-highlight:is(.dark *){--tw-border-opacity: 1;border-color:rgb(0 255 65 / var(--tw-border-opacity, 1))}.dark\:border-matrix-highlight\/20:is(.dark *){border-color:#00ff4133}.dark\:border-matrix-primary:is(.dark *){--tw-border-opacity: 1;border-color:rgb(0 59 0 / var(--tw-border-opacity, 1))}.dark\:border-matrix-primary\/30:is(.dark *){border-color:#003b004d}.dark\:border-matrix-primary\/50:is(.dark *){border-color:#003b0080}.dark\:border-matrix-secondary:is(.dark *){--tw-border-opacity: 1;border-color:rgb(0 143 17 / var(--tw-border-opacity, 1))}.dark\:border-red-900:is(.dark *){--tw-border-opacity: 1;border-color:rgb(127 29 29 / var(--tw-border-opacity, 1))}.dark\:border-red-900\/50:is(.dark *){border-color:#7f1d1d80}.dark\:border-zinc-800:is(.dark *){--tw-border-opacity: 1;border-color:rgb(39 39 42 / var(--tw-border-opacity, 1))}.dark\:bg-black:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(0 0 0 / var(--tw-bg-opacity, 1))}.dark\:bg-black\/40:is(.dark *){background-color:#0006}.dark\:bg-black\/50:is(.dark *){background-color:#00000080}.dark\:bg-green-400\/20:is(.dark *){background-color:#4ade8033}.dark\:bg-green-900:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(20 83 45 / var(--tw-bg-opacity, 1))}.dark\:bg-matrix-base:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(13 2 8 / var(--tw-bg-opacity, 1))}.dark\:bg-matrix-base\/50:is(.dark *){background-color:#0d020880}.dark\:bg-matrix-highlight:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(0 255 65 / var(--tw-bg-opacity, 1))}.dark\:bg-matrix-highlight\/10:is(.dark *){background-color:#00ff411a}.dark\:bg-matrix-primary:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(0 59 0 / var(--tw-bg-opacity, 1))}.dark\:bg-matrix-primary\/20:is(.dark *){background-color:#003b0033}.dark\:bg-matrix-primary\/50:is(.dark *){background-color:#003b0080}.dark\:bg-matrix-secondary:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(0 143 17 / var(--tw-bg-opacity, 1))}.dark\:bg-purple-900\/30:is(.dark *){background-color:#581c874d}.dark\:bg-red-400\/20:is(.dark *){background-color:#f8717133}.dark\:bg-red-600:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.dark\:bg-red-900:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(127 29 29 / var(--tw-bg-opacity, 1))}.dark\:bg-red-900\/50:is(.dark *){background-color:#7f1d1d80}.dark\:bg-red-950\/30:is(.dark *){background-color:#450a0a4d}.dark\:bg-red-950\/50:is(.dark *){background-color:#450a0a80}.dark\:bg-zinc-800:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(39 39 42 / var(--tw-bg-opacity, 1))}.dark\:bg-zinc-900:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(24 24 27 / var(--tw-bg-opacity, 1))}.dark\:bg-zinc-900\/50:is(.dark *){background-color:#18181b80}.dark\:bg-zinc-950:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(12 12 12 / var(--tw-bg-opacity, 1))}.dark\:bg-zinc-950\/50:is(.dark *){background-color:#0c0c0c80}.dark\:bg-zinc-950\/60:is(.dark *){background-color:#0c0c0c99}.dark\:bg-zinc-950\/70:is(.dark *){background-color:#0c0c0cb3}.dark\:text-black:is(.dark *){--tw-text-opacity: 1;color:rgb(0 0 0 / var(--tw-text-opacity, 1))}.dark\:text-blue-400:is(.dark *){--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.dark\:text-gray-200:is(.dark *){--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity, 1))}.dark\:text-gray-300:is(.dark *){--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity, 1))}.dark\:text-green-400:is(.dark *){--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}.dark\:text-green-500:is(.dark *){--tw-text-opacity: 1;color:rgb(34 197 94 / var(--tw-text-opacity, 1))}.dark\:text-matrix-bg:is(.dark *){--tw-text-opacity: 1;color:rgb(0 0 0 / var(--tw-text-opacity, 1))}.dark\:text-matrix-highlight:is(.dark *){--tw-text-opacity: 1;color:rgb(0 255 65 / var(--tw-text-opacity, 1))}.dark\:text-matrix-highlight\/50:is(.dark *){color:#00ff4180}.dark\:text-matrix-highlight\/80:is(.dark *){color:#00ff41cc}.dark\:text-matrix-primary:is(.dark *){--tw-text-opacity: 1;color:rgb(0 59 0 / var(--tw-text-opacity, 1))}.dark\:text-matrix-secondary:is(.dark *){--tw-text-opacity: 1;color:rgb(0 143 17 / var(--tw-text-opacity, 1))}.dark\:text-matrix-secondary\/50:is(.dark *){color:#008f1180}.dark\:text-matrix-secondary\/70:is(.dark *){color:#008f11b3}.dark\:text-purple-400:is(.dark *){--tw-text-opacity: 1;color:rgb(192 132 252 / var(--tw-text-opacity, 1))}.dark\:text-red-200:is(.dark *){--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity, 1))}.dark\:text-red-300:is(.dark *){--tw-text-opacity: 1;color:rgb(252 165 165 / var(--tw-text-opacity, 1))}.dark\:text-red-400:is(.dark *){--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.dark\:text-white:is(.dark *){--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.dark\:text-zinc-50:is(.dark *){--tw-text-opacity: 1;color:rgb(250 250 250 / var(--tw-text-opacity, 1))}.dark\:placeholder-green-900:is(.dark *)::-moz-placeholder{--tw-placeholder-opacity: 1;color:rgb(20 83 45 / var(--tw-placeholder-opacity, 1))}.dark\:placeholder-green-900:is(.dark *)::placeholder{--tw-placeholder-opacity: 1;color:rgb(20 83 45 / var(--tw-placeholder-opacity, 1))}.dark\:placeholder-matrix-secondary\/50:is(.dark *)::-moz-placeholder{color:#008f1180}.dark\:placeholder-matrix-secondary\/50:is(.dark *)::placeholder{color:#008f1180}.dark\:opacity-20:is(.dark *){opacity:.2}.dark\:shadow-\[0_0_15px_rgba\(34\,197\,94\,0\.3\)\]:is(.dark *){--tw-shadow: 0 0 15px rgba(34,197,94,.3);--tw-shadow-colored: 0 0 15px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.dark\:ring-offset-zinc-950:is(.dark *){--tw-ring-offset-color: #0c0c0c}.dark\:hover\:border-matrix-highlight:hover:is(.dark *){--tw-border-opacity: 1;border-color:rgb(0 255 65 / var(--tw-border-opacity, 1))}.hover\:dark\:border-matrix-highlight:is(.dark *):hover{--tw-border-opacity: 1;border-color:rgb(0 255 65 / var(--tw-border-opacity, 1))}.dark\:hover\:bg-amber-900\/20:hover:is(.dark *){background-color:#78350f33}.dark\:hover\:bg-blue-900\/20:hover:is(.dark *){background-color:#1e3a8a33}.dark\:hover\:bg-green-700:hover:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(21 128 61 / var(--tw-bg-opacity, 1))}.dark\:hover\:bg-matrix-highlight:hover:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(0 255 65 / var(--tw-bg-opacity, 1))}.dark\:hover\:bg-matrix-highlight\/10:hover:is(.dark *){background-color:#00ff411a}.dark\:hover\:bg-matrix-highlight\/90:hover:is(.dark *){background-color:#00ff41e6}.dark\:hover\:bg-matrix-primary\/10:hover:is(.dark *){background-color:#003b001a}.dark\:hover\:bg-matrix-primary\/20:hover:is(.dark *){background-color:#003b0033}.dark\:hover\:bg-matrix-primary\/50:hover:is(.dark *){background-color:#003b0080}.dark\:hover\:bg-matrix-secondary:hover:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(0 143 17 / var(--tw-bg-opacity, 1))}.dark\:hover\:bg-red-800\/60:hover:is(.dark *){background-color:#991b1b99}.dark\:hover\:bg-red-900\/20:hover:is(.dark *){background-color:#7f1d1d33}.dark\:hover\:bg-red-900\/90:hover:is(.dark *){background-color:#7f1d1de6}.dark\:hover\:bg-zinc-700\/80:hover:is(.dark *){background-color:#3f3f46cc}.dark\:hover\:bg-zinc-900:hover:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(24 24 27 / var(--tw-bg-opacity, 1))}.hover\:dark\:bg-matrix-secondary:is(.dark *):hover{--tw-bg-opacity: 1;background-color:rgb(0 143 17 / var(--tw-bg-opacity, 1))}.hover\:dark\:bg-red-500:is(.dark *):hover{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity, 1))}.dark\:hover\:text-amber-500:hover:is(.dark *){--tw-text-opacity: 1;color:rgb(245 158 11 / var(--tw-text-opacity, 1))}.dark\:hover\:text-blue-300:hover:is(.dark *){--tw-text-opacity: 1;color:rgb(147 197 253 / var(--tw-text-opacity, 1))}.dark\:hover\:text-blue-400:hover:is(.dark *){--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.dark\:hover\:text-matrix-highlight:hover:is(.dark *){--tw-text-opacity: 1;color:rgb(0 255 65 / var(--tw-text-opacity, 1))}.dark\:hover\:text-matrix-secondary:hover:is(.dark *){--tw-text-opacity: 1;color:rgb(0 143 17 / var(--tw-text-opacity, 1))}.dark\:hover\:text-red-300:hover:is(.dark *){--tw-text-opacity: 1;color:rgb(252 165 165 / var(--tw-text-opacity, 1))}.dark\:hover\:text-red-500:hover:is(.dark *){--tw-text-opacity: 1;color:rgb(239 68 68 / var(--tw-text-opacity, 1))}.hover\:dark\:text-matrix-highlight:is(.dark *):hover{--tw-text-opacity: 1;color:rgb(0 255 65 / var(--tw-text-opacity, 1))}.hover\:dark\:text-matrix-secondary:is(.dark *):hover{--tw-text-opacity: 1;color:rgb(0 143 17 / var(--tw-text-opacity, 1))}.dark\:focus\:border-green-500:focus:is(.dark *){--tw-border-opacity: 1;border-color:rgb(34 197 94 / var(--tw-border-opacity, 1))}.dark\:focus\:border-matrix-highlight:focus:is(.dark *){--tw-border-opacity: 1;border-color:rgb(0 255 65 / var(--tw-border-opacity, 1))}.dark\:focus\:ring-matrix-highlight:focus:is(.dark *){--tw-ring-opacity: 1;--tw-ring-color: rgb(0 255 65 / var(--tw-ring-opacity, 1))}.dark\:focus\:ring-matrix-secondary:focus:is(.dark *){--tw-ring-opacity: 1;--tw-ring-color: rgb(0 143 17 / var(--tw-ring-opacity, 1))}.dark\:focus\:ring-zinc-300:focus:is(.dark *){--tw-ring-opacity: 1;--tw-ring-color: rgb(212 212 216 / var(--tw-ring-opacity, 1))}.dark\:focus\:ring-offset-black:focus:is(.dark *){--tw-ring-offset-color: #000}.dark\:focus-visible\:ring-zinc-300:focus-visible:is(.dark *){--tw-ring-opacity: 1;--tw-ring-color: rgb(212 212 216 / var(--tw-ring-opacity, 1))}.dark\:disabled\:bg-matrix-primary\/50:disabled:is(.dark *){background-color:#003b0080}.group:hover .dark\:group-hover\:text-matrix-highlight:is(.dark *){--tw-text-opacity: 1;color:rgb(0 255 65 / var(--tw-text-opacity, 1))}@media(min-width:640px){.sm\:max-w-md{max-width:28rem}.sm\:flex-row{flex-direction:row}.sm\:items-center{align-items:center}.sm\:justify-end{justify-content:flex-end}.sm\:space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.5rem * var(--tw-space-x-reverse));margin-left:calc(.5rem * calc(1 - var(--tw-space-x-reverse)))}.sm\:text-left{text-align:left}}@media(min-width:768px){.md\:max-w-2xl{max-width:42rem}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.md\:flex-row{flex-direction:row}.md\:items-center{align-items:center}.md\:justify-between{justify-content:space-between}.md\:p-6{padding:1.5rem}.md\:p-8{padding:2rem}.md\:pt-0{padding-top:0}}@media(min-width:1024px){.lg\:flex{display:flex}.lg\:hidden{display:none}.lg\:flex-row{flex-direction:row}.lg\:pt-0{padding-top:0}}@media(min-width:1280px){.xl\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}.\[\&\:has\(\[role\=checkbox\]\)\]\:pr-0:has([role=checkbox]){padding-right:0}.\[\&_p\]\:leading-relaxed p{line-height:1.625}
|