Initial commit: MUDlet AI Client with Telnet + Ollama integration

This commit is contained in:
truetype committed 2026-02-10 10:39:18 +01:00
commit a1b7191098
22 files changed
+2197

No files matched your search

+373
View File
@@ -0,0 +1,373 @@
# API IPC Reference
## Context Bridge
Tutte le API sono esposte tramite `window.electronAPI`:
```typescript
// TypeScript declaration
interface ElectronAPI {
// Telnet
telnetConnect: (host: string, port: number) => Promise<boolean>;
telnetDisconnect: () => Promise<void>;
telnetSend: (data: string) => Promise<boolean>;
telnetGetOutput: () => Promise<string>;
onTelnetOutput: (callback: (data: string) => void) => void;
// Ollama
ollamaChat: (messages: OllamaMessage[], options?: OllamaOptions) => Promise<OllamaResponse>;
ollamaGetModels: () => Promise<string[]>;
ollamaIsAvailable: () => Promise<boolean>;
// Autoplayer
autoplayerConfigure: (config: AutoplayerConfig) => Promise<void>;
autoplayerStart: () => Promise<void>;
autoplayerStop: () => Promise<void>;
autoplayerIsActive: () => Promise<boolean>;
autoplayerExecuteAction: (action: string) => Promise<void>;
onAutoplayerDecision: (callback: (decision: string) => void) => void;
onAutoplayerAction: (callback: (action: string) => void) => void;
onAutoplayerState: (callback: (state: GameState) => void) => void;
}
```
---
## Telnet API
### telnetConnect
Stabilisce connessione Telnet al server MUD.
```typescript
const connected = await window.electronAPI.telnetConnect('172.24.19.225', 6666);
```
**Params:**
| Param | Type | Description |
|-------|------|-------------|
| host | string | Indirizzo server MUD |
| port | number | Porta Telnet |
**Returns:** `Promise<boolean>` - true se connessione riuscita
**Errors:** Connection timeout, host unreachable
### telnetDisconnect
Chiude connessione Telnet.
```typescript
await window.electronAPI.telnetDisconnect();
```
### telnetSend
Invia comando al server MUD.
```typescript
const sent = await window.electronAPI.telnetSend('look\n');
```
**Params:**
| Param | Type | Description |
|-------|------|-------------|
| data | string | Comando da inviare (newline esclusa) |
**Returns:** `Promise<boolean>` - true se invio riuscito
### telnetGetOutput
Restituisce buffer output Telnet accumulato.
```typescript
const output = await window.electronAPI.telnetGetOutput();
```
**Returns:** `Promise<string>` - Output completo con HTML colors
### onTelnetOutput
Subscribe a nuovi output dal server.
```typescript
window.electronAPI.onTelnetOutput((data) => {
console.log('Nuovo output:', data);
});
```
**Callback Params:**
| Param | Type | Description |
|-------|------|-------------|
| data | string | Chunk output HTML |
---
## Ollama API
### ollamaChat
Invia messaggi a Ollama e riceve risposta.
```typescript
const response = await window.electronAPI.ollamaChat([
{ role: 'system', content: 'You are a MUD player' },
{ role: 'user', content: 'What should I do?' }
], {
model: 'llama3.2',
temperature: 0.7
});
```
**Params:**
| Param | Type | Description |
|-------|------|-------------|
| messages | OllamaMessage[] | Storico conversazione |
| options | OllamaOptions | Opzioni (opzionali) |
**OllamaMessage:**
```typescript
{
role: 'user' | 'assistant' | 'system';
content: string;
}
```
**OllamaOptions:**
```typescript
{
model?: string; // Default: 'llama3.2'
temperature?: number; // Default: 0.7
}
```
**Returns:** `Promise<OllamaResponse>`
```typescript
{
model: string;
response: string;
done: boolean;
}
```
### ollamaGetModels
Lista modelli disponibili sul server.
```typescript
const models = await window.electronAPI.ollamaGetModels();
// ['llama3.2', 'llama3.1', 'mistral', ...]
```
**Returns:** `Promise<string[]>` - Nomi modelli
### ollamaIsAvailable
Verifica se server Ollama raggiungibile.
```typescript
const available = await window.electronAPI.ollamaIsAvailable();
// true se server risponde
```
**Returns:** `Promise<boolean>`
---
## Autoplayer API
### autoplayerConfigure
Configura comportamento autoplayer.
```typescript
await window.electronAPI.autoplayerConfigure({
model: 'llama3.2',
systemPrompt: 'You are an explorer...',
temperature: 0.7,
autoSend: true
});
```
**AutoplayerConfig:**
```typescript
{
model: string; // Model LLM
systemPrompt: string; // Personalità bot
temperature?: number; // Creatività (0.0-1.0)
autoSend?: boolean; // Auto-esegui azioni
}
```
### autoplayerStart
Avvia loop autoplayer.
```typescript
await window.electronAPI.autoplayerStart();
```
### autoplayerStop
Ferma loop autoplayer.
```typescript
await window.electronAPI.autoplayerStop();
```
### autoplayerIsActive
Verifica se autoplayer in esecuzione.
```typescript
const active = await window.electronAPI.autoplayerIsActive();
```
**Returns:** `Promise<boolean>`
### autoplayerExecuteAction
Esegui azione manualmente (senza passare per decision engine).
```typescript
await window.electronAPI.autoplayerExecuteAction('go north');
```
### onAutoplayerDecision
Subscribe a decisioni LLM.
```typescript
window.electronAPI.onAutoplayerDecision((decision) => {
console.log('LLM decision:', decision);
});
```
**Callback Params:**
| Param | Type | Description |
|-------|------|-------------|
| decision | string | Risposta completa LLM |
### onAutoplayerAction
Subscribe ad azioni generate.
```typescript
window.electronAPI.onAutoplayerAction((action) => {
console.log('Action to execute:', action);
});
```
**Callback Params:**
| Param | Type | Description |
|-------|------|-------------|
| action | string | Comando da eseguire |
### onAutoplayerState
Subscribe a updates stato gioco parsato.
```typescript
window.electronAPI.onAutoplayerState((state) => {
console.log('Current location:', state.location);
console.log('Exits:', state.exits);
});
```
**GameState:**
```typescript
{
location: string; // Nome location corrente
description: string; // Descrizione stanza
exits: string[]; // Uscite disponibili
players: string[]; // Giocatori presenti
objects: string[]; // Oggetti nella stanza
health?: string; // HP corrente
mana?: string; // Mana corrente
prompt?: string; // Prompt del gioco
}
```
---
## Esempi Completi
### Connessione e Game Loop
```typescript
// 1. Connetti
const connected = await window.electronAPI.telnetConnect('172.24.19.225', 6666);
if (!connected) {
console.error('Connessione fallita');
return;
}
// 2. Subscribe output
window.electronAPI.onTelnetOutput((data) => {
// Aggiorna UI
setOutput(prev => prev + data);
});
// 3. Invia comando
await window.electronAPI.telnetSend('look\n');
```
### Autoplayer Workflow
```typescript
// 1. Configura
await window.electronAPI.autoplayerConfigure({
model: 'llama3.2',
systemPrompt: 'You are a helpful MUD player. Explore and interact naturally.',
temperature: 0.7,
autoSend: false // Review manuale azioni
});
// 2. Subscribe decisioni
window.electronAPI.onAutoplayerAction((action) => {
console.log('Pending action:', action);
// Possibile review manuale qui
});
// 3. Avvia
await window.electronAPI.autoplayerStart();
// 4. Per fermare
await window.electronAPI.autoplayerStop();
```
### Ollama Chat Diretta
```typescript
// Test chat con Ollama
const response = await window.electronAPI.ollamaChat([
{ role: 'system', content: 'You are a fantasy game master.' },
{ role: 'user', content: 'Describe a dark forest.' }
], { temperature: 0.8 });
console.log(response.response);
```
---
## Error Handling
```typescript
try {
await window.electronAPI.telnetConnect(host, port);
} catch (error) {
console.error('Connection error:', error);
}
// Event-based errors
window.electronAPI.onTelnetOutput((data) => {
// Gestisci errori in output
});
```
## Note
- Tutte le chiamate IPC sono asincrone (Promise-based)
- Gli eventi devono essere sottoscritti una volta (non in useEffect ripetuti)
- Cleanup event listeners quando il componente unmount
- Utilizzare `window.electronAPI` solo dopo il mount del componente
+273
View File
@@ -0,0 +1,273 @@
# Architettura
## Panoramica
MUDlet AI Client usa un'architettura a 3 layer basata su Electron:
```
┌─────────────────────────────────────────────────────────┐
│ ELECTRON MAIN │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │
│ │ TelnetService│ │ OllamaService│ │ AutoplayerService│ │
│ └──────┬──────┘ └──────┬──────┘ └────────┬────────┘ │
│ │ │ │ │
│ └────────────────┼───────────────────┘ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ IPC Handlers │ │
│ └─────────┬───────────┘ │
└────────────────────────┼────────────────────────────────┘
│
┌────────────────────────┼────────────────────────────────┐
│ PRELOAD JS │
│ contextBridge.exposeInMainWorld() │
└────────────────────────┼────────────────────────────────┘
│
┌────────────────────────┼────────────────────────────────┐
│ REACT RENDERER │
│ ┌─────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ App │ │ GameWindow │ │ AutoplayerPanel │ │
│ └─────────┘ └─────────────┘ └─────────────────────┘ │
└─────────────────────────────────────────────────────────┘
```
## Layer 1: Electron Main Process
### Main Entry (`src/main/index.ts`)
Responsabilità:
- Inizializzazione finestra BrowserWindow
- Setup IPC handlers
- Event forwarding tra servizi e renderer
- Lifecycle management (app ready, window closed, etc.)
```typescript
// Flusso dati Telnet
telnetService.on('output', (data) => {
mainWindow.webContents.send('telnet-output', data);
autoplayerService.processOutput(data); // Forward all'autoplayer
});
```
### TelnetService (`src/main/services/TelnetService.ts`)
Wrapper attorno a `telnet-client` con funzionalità aggiuntive:
| Metodo | Descrizione |
|--------|-------------|
| `connect(host, port)` | Stabilisce connessione Telnet |
| `send(data)` | Invia comando al server |
| `getOutput()` | Restituisce buffer output |
| `parseAnsiCodes(text)` | Converte ANSI escape codes in HTML |
**Eventi:**
- `output` - Emesso quando arrives nuovo output
- `connected` - Connessione stabilita
- `disconnected` - Connessione chiusa
- `error` - Errore di connessione
**Parsing ANSI:**
```typescript
// ansicolor converte sequenze ANSI in spans colorati
// Ogni span viene convertito in <span style="...">
```
### OllamaService (`src/main/services/OllamaService.ts`)
Client HTTP per Ollama API. Supporta:
| Metodo | Descrizione |
|--------|-------------|
| `chat(messages, options)` | Chat singola, attesa risposta |
| `chatStream(messages, options)` | Streaming response token-by-token |
| `getLocalModels()` | Lista modelli disponibili |
| `isAvailable()` | Check server reachability |
**API Endpoint:**
```
POST http://ollama.solarpi.net/api/chat
{
"model": "llama3.2",
"messages": [...],
"stream": true,
"options": {
"temperature": 0.7,
"num_predict": 512
}
}
```
### AutoplayerService (`src/main/services/AutoplayerService.ts`)
Decision engine che orchestrate il loop autoplayer:
```
┌─────────────────────────────────────────────────────────┐
│ AUTOPLAYER LOOP │
│ │
│ 1. OBSERVE ← processOutput(output) │
│ 2. PARSE ← parseGameState() │
│ 3. THINK ← ollamaService.chat() │
│ 4. DECIDE ← extractAction() │
│ 5. ACT ← executeAction() / queueAction() │
│ │
└─────────────────────────────────────────────────────────┘
```
**Configurazione:**
```typescript
interface AutoplayerConfig {
model: string; // Model LLM da usare
systemPrompt: string; // Comportamento del bot
temperature: number; // Creatività (0.0-1.0)
autoSend: boolean; // Auto-esegui azioni
}
```
**Game State Parser:**
```typescript
parseGameState(output: string): GameState {
// Estrae: location, exits, players, objects, description
}
```
**Decision Prompt:**
```
Context: [ultimi 30 righe output]
Sei in: [location]
Uscite disponibili: north, south, east
Cosa fai? (rispondi SOLO con il comando)
```
## Layer 2: Preload Script
espone API sicure al renderer tramite `contextBridge`:
```javascript
// Telnet
telnetConnect(host, port)
telnetSend(data)
onTelnetOutput(callback)
// Ollama
ollamaChat(messages, options)
ollamaGetModels()
ollamaIsAvailable()
// Autoplayer
autoplayerConfigure(config)
autoplayerStart()
autoplayerStop()
autoplayerExecuteAction(action)
onAutoplayerDecision(callback)
onAutoplayerAction(callback)
```
## Layer 3: React Renderer
### App (`src/renderer/App.tsx`)
Root component con navigation:
```typescript
// Navigation state
const [activePanel, setActivePanel] = useState<'connection' | 'game' | 'autoplayer'>();
// Connection status
const [isConnected, setIsConnected] = useState(false);
```
### GameWindow (`src/renderer/components/GameWindow.tsx`)
Display principale del gioco:
| Componente | Funzione |
|------------|----------|
| Output div | Visualizza output HTML con colors |
| Form | Input comandi + submit |
| Auto-scroll | Mantiene focus su output recente |
```typescript
// Subscribe a output Telnet
useEffect(() => {
window.electronAPI.onTelnetOutput((data) => {
setOutput(prev => prev + data);
});
}, []);
```
### AutoplayerPanel (`src/renderer/components/AutoplayerPanel.tsx`)
Pannello controllo autoplayer:
| Feature | UI |
|---------|-----|
| Toggle On/Off | Switch button |
| Model selection | Dropdown (da Ollama) |
| System prompt | Textarea modificabile |
| Live status | Ultime decisioni/azioni |
| Ollama availability | Check automatico |
## Flusso Dati
### Telnet → UI
```
1. Telnet server → telnet-client
2. TelnetService → emits 'output'
3. Main process → ipcRenderer.send('telnet-output')
4. GameWindow → onTelnetOutput callback
5. React state → setOutput()
6. Render → HTML display
```
### Autoplayer Decision
```
1. Output Telnet arrives
2. AutoplayerService.processOutput()
3. parseGameState() → GameState { location, exits, ... }
4. shouldMakeDecision() → true/false
5. ollamaService.chat(messages)
6. extractAction() → "go north"
7. autoSend ? executeAction() : queueAction()
8. Events: 'decision', 'action'
9. UI updates: lastDecision, lastAction
```
## Configurazione
### Environment
- Server Ollama: `http://ollama.solarpi.net` (hardcoded)
- LPMUD default: `172.24.19.225:6666`
### Tipi Condivisi
```typescript
// src/main/services/AutoplayerService.ts
interface GameState {
location: string;
description: string;
exits: string[];
players: string[];
objects: string[];
health?: string;
mana?: string;
prompt?: string;
}
interface OllamaMessage {
role: 'user' | 'assistant' | 'system';
content: string;
}
```
## Limitazioni Attuali
| Componente | Limitation | Piani |
|------------|------------|-------|
| ANSI Parsing | Solo colori basic | Parser completo |
| Game Parser | Regex-based, fragile | ML parser |
| Autoplayer | No memory persistent | SQLite storage |
| Mapper | Non implementato | Fase 3 |
| Triggers | Non implementati | Fase 3 |
| Plugins | Non implementati | Fase 4 |
+287
View File
@@ -0,0 +1,287 @@
# Workflow di Sviluppo
## Setup Ambiente
### Prerequisiti
- Node.js 18+
- npm 9+
- Git
### Installazione
```bash
# Clone repository
cd mudlet-ai-client
# Installa dipendenze
npm install
# Verifica installazione
npm run dev # Vite dev server
npm start # Electron app
```
### Struttura Rami
```
main
├── develop # Branch di sviluppo attivo
├── feature/* # Feature branches
├── bugfix/* # Bug fix branches
└── release/* # Release branches
```
## Workflow Git
### Feature Development
```bash
# Crea branch da develop
git checkout develop
git checkout -b feature/nome-feature
# Sviluppa e commit
git add .
git commit -m "feat: descrizione feature"
# Push e PR
git push origin feature/nome-feature
# Crea Pull Request su GitHub
```
### Commit Convention
```
<type>(<scope>): <description>
Types:
- feat: Nuova feature
- fix: Bug fix
- docs: Documentazione
- refactor: Code refactoring
- test: Aggiunta test
- chore: Manutenzione
Examples:
feat(telnet): add ANSI color support
fix(autoplayer): resolve action queue ordering
docs(api): update IPC documentation
```
## Build & Test
### Comandi NPM
| Comando | Descrizione |
|---------|-------------|
| `npm run dev` | Avvia Vite dev server (port 3000) |
| `npm run build` | Build production (compila TS + Vite) |
| `npm run preview` | Preview build production |
| `npm start` | Avvia Electron app |
### Build Production
```bash
# Build completo
npm run build
# Output in dist/
dist/
├── renderer/ # Bundle React
└── main/ # Bundle Electron main
```
### Debug
```bash
# DevTools aperto automaticamente
npm start
# Per debug remoto:
# 1. Apri DevTools (View → Toggle DevTools)
# 2. Console: renderer process
# 3. Terminale: main process (output console.log)
```
## Sviluppo Componenti
### Aggiungere Nuovo Servizio Main Process
1. Crea file in `src/main/services/NomeService.ts`:
```typescript
import { EventEmitter } from 'events';
interface NomeServiceEvents {
'event': [data: Type];
}
export class NomeService extends EventEmitter<NomeServiceEvents> {
// Implementazione
}
```
2. Aggiungi in `src/main/index.ts`:
```typescript
import { NomeService } from './services/NomeService';
const nomeService = new NomeService();
// IPC handlers
ipcMain.handle('nome-action', async (_, arg) => {
return nomeService.action(arg);
});
// Event forwarding
nomeService.on('event', (data) => {
if (mainWindow) {
mainWindow.webContents.send('nome-event', data);
}
});
```
3. Aggiorna `src/preload/index.js`:
```javascript
nomeAction: (arg) => ipcRenderer.invoke('nome-action', arg),
onNomeEvent: (callback) => ipcRenderer.on('nome-event', (_, data) => callback(data)),
```
### Aggiungere Componente UI
1. Crea file in `src/renderer/components/NomeComponente.tsx`:
```typescript
import { useState, useEffect } from 'react';
interface Props {
prop1: string;
onAction: (data: Type) => void;
}
export function NomeComponente({ prop1, onAction }: Props) {
// Implementazione
return <div>...</div>;
}
```
2. Importa in `App.tsx`:
```typescript
import { NomeComponente } from './components/NomeComponente';
// Usa nel render
<NomeComponente prop1="value" onAction={handler} />
```
## IPC Communication Pattern
### Request-Response (Renderer → Main)
```typescript
// Renderer
const result = await window.electronAPI.someAction(arg);
// Main
ipcMain.handle('some-action', async (_, arg) => {
return service.action(arg);
});
```
### Events (Main → Renderer)
```typescript
// Renderer
window.electronAPI.onSomeEvent((data) => {
console.log(data);
});
// Main
service.on('event', (data) => {
if (mainWindow) {
mainWindow.webContents.send('someEvent', data);
}
});
```
### Two-Way Communication
```typescript
// 1. Renderer invia richiesta
const action = await window.electronAPI.getDecision(context);
// 2. Main processa
ipcMain.handle('getDecision', async (_, context) => {
return autoplayerService.decide(context);
});
// 3. Risultato tornato al renderer
```
## Testing
### Test Manuali
```bash
# 1. Avvia dev server
npm run dev
# 2. Avvia Electron (nuovo terminale)
npm start
# 3. Testa connessione
# - Host: 172.24.19.225
# - Porta: 6666
# 4. Testa autoplayer
# - Attiva toggle
# - Verifica decisioni
```
### Checklist Pre-Release
- [ ] TypeScript senza errori (`npm run build`)
- [ ] Componenti renderizzano correttamente
- [ ] Connessione Telnet funziona
- [ ] Ollama reachable e risponde
- [ ] Autoplayer genera azioni sensate
- [ ] No memory leaks (DevTools → Memory)
- [ ] UI responsive
## Deployment
### Electron Build
```bash
# Installa electron-builder
npm install electron-builder --save-dev
# Build per piattaforma corrente
npm run build
npx electron-builder
# Output in release/
```
### Update MUD_AUTOPLAYER_PLAN.md
Dopo ogni fase completata:
```markdown
### Fase N: Nome (DATA) - ✅ COMPLETATO
- [x] Task 1
- [x] Task 2
- [ ] Task 3 (futuro)
```
## Risorse
| Risorsa | URL |
|---------|-----|
| Electron Docs | https://www.electronjs.org/docs |
| React Docs | https://react.dev |
| TypeScript | https://www.typescriptlang.org/docs |
| Vite | https://vitejs.dev |
| Tailwind | https://tailwindcss.com/docs |
| Ollama API | https://github.com/ollama/ollama/blob/main/docs/api.md |