feat: add TUI entry point with minimal readline mode
This commit is contained in:
@@ -8,6 +8,8 @@
|
|||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"dev": "tsx watch src/index.ts",
|
"dev": "tsx watch src/index.ts",
|
||||||
"start": "node dist/index.js",
|
"start": "node dist/index.js",
|
||||||
|
"tui": "tsx src/tui.ts",
|
||||||
|
"tui:dev": "tsx watch src/tui.ts",
|
||||||
"test": "vitest",
|
"test": "vitest",
|
||||||
"test:run": "vitest run",
|
"test:run": "vitest run",
|
||||||
"lint": "eslint src/",
|
"lint": "eslint src/",
|
||||||
|
|||||||
+121
@@ -0,0 +1,121 @@
|
|||||||
|
import { loadConfig } from './config/index.js';
|
||||||
|
import { SessionStore, SessionManager } from './session/index.js';
|
||||||
|
import { AnthropicClient, OpenAIClient, OllamaClient, ModelRouter } from './models/index.js';
|
||||||
|
import { MinimalTui } from './frontends/tui/index.js';
|
||||||
|
import type { Config } from './config/index.js';
|
||||||
|
import { resolve } from 'path';
|
||||||
|
import { homedir } from 'os';
|
||||||
|
import { existsSync, mkdirSync } from 'fs';
|
||||||
|
|
||||||
|
const CONFIG_PATH = process.env.FLYNN_CONFIG
|
||||||
|
?? resolve(homedir(), '.config/flynn/config.yaml');
|
||||||
|
|
||||||
|
const SYSTEM_PROMPT = `You are Flynn, a helpful personal AI assistant. You are direct, concise, and helpful. You can help with a variety of tasks including answering questions, providing information, and having conversations.
|
||||||
|
|
||||||
|
Keep responses focused and avoid unnecessary verbosity. Use markdown formatting when it improves readability.`;
|
||||||
|
|
||||||
|
function createModelRouter(config: Config): ModelRouter {
|
||||||
|
const models = config.models;
|
||||||
|
|
||||||
|
const defaultClient = new AnthropicClient({
|
||||||
|
model: models.default.model,
|
||||||
|
});
|
||||||
|
|
||||||
|
let fastClient;
|
||||||
|
let complexClient;
|
||||||
|
let localClient;
|
||||||
|
|
||||||
|
if (models.fast) {
|
||||||
|
fastClient = new AnthropicClient({ model: models.fast.model });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (models.complex) {
|
||||||
|
complexClient = new AnthropicClient({ model: models.complex.model });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (models.local) {
|
||||||
|
if (models.local.provider === 'ollama') {
|
||||||
|
localClient = new OllamaClient({
|
||||||
|
model: models.local.model,
|
||||||
|
host: models.local.endpoint,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fallbackChain = [];
|
||||||
|
for (const providerName of models.fallback_chain) {
|
||||||
|
if (providerName === 'openai') {
|
||||||
|
fallbackChain.push(new OpenAIClient({ model: 'gpt-4o' }));
|
||||||
|
} else if (providerName === 'local' && localClient) {
|
||||||
|
fallbackChain.push(localClient);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ModelRouter({
|
||||||
|
default: defaultClient,
|
||||||
|
fast: fastClient,
|
||||||
|
complex: complexClient,
|
||||||
|
local: localClient,
|
||||||
|
fallbackChain,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
console.log('Flynn TUI starting...');
|
||||||
|
|
||||||
|
if (!existsSync(CONFIG_PATH)) {
|
||||||
|
console.error(`Config file not found: ${CONFIG_PATH}`);
|
||||||
|
console.error('Copy config/default.yaml to ~/.config/flynn/config.yaml and configure it.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = loadConfig(CONFIG_PATH);
|
||||||
|
|
||||||
|
// Ensure data directory exists
|
||||||
|
const dataDir = resolve(homedir(), '.local/share/flynn');
|
||||||
|
mkdirSync(dataDir, { recursive: true });
|
||||||
|
|
||||||
|
// Initialize components
|
||||||
|
const sessionStore = new SessionStore(resolve(dataDir, 'sessions.db'));
|
||||||
|
const sessionManager = new SessionManager(sessionStore);
|
||||||
|
const modelRouter = createModelRouter(config);
|
||||||
|
|
||||||
|
// Get TUI session
|
||||||
|
const session = sessionManager.getSession('tui', 'local');
|
||||||
|
|
||||||
|
// Create and start minimal TUI
|
||||||
|
const tui = new MinimalTui({
|
||||||
|
session,
|
||||||
|
modelClient: modelRouter,
|
||||||
|
systemPrompt: SYSTEM_PROMPT,
|
||||||
|
onTransfer: (target) => {
|
||||||
|
if (target === 'telegram') {
|
||||||
|
const telegramUserId = String(config.telegram.allowed_chat_ids[0]);
|
||||||
|
sessionManager.transferSession('tui', 'local', 'telegram', telegramUserId);
|
||||||
|
console.log(`Session transferred to Telegram (${telegramUserId})\n`);
|
||||||
|
} else {
|
||||||
|
console.log(`Unknown transfer target: ${target}\n`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onFullscreen: () => {
|
||||||
|
console.log('Fullscreen mode not yet implemented.\n');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle shutdown
|
||||||
|
process.on('SIGINT', () => {
|
||||||
|
tui.stop();
|
||||||
|
sessionStore.close();
|
||||||
|
process.exit(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
await tui.start();
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
sessionStore.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((error) => {
|
||||||
|
console.error('Failed to start TUI:', error);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user