feat(tui): add unified command parser with model switching
This commit is contained in:
@@ -0,0 +1,60 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { parseCommand, getHelpText } from './commands.js';
|
||||||
|
|
||||||
|
describe('parseCommand', () => {
|
||||||
|
it('parses /quit command', () => {
|
||||||
|
expect(parseCommand('/quit')).toEqual({ type: 'quit' });
|
||||||
|
expect(parseCommand('/exit')).toEqual({ type: 'quit' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parses /reset command', () => {
|
||||||
|
expect(parseCommand('/reset')).toEqual({ type: 'reset' });
|
||||||
|
expect(parseCommand('/clear')).toEqual({ type: 'reset' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parses /help command', () => {
|
||||||
|
expect(parseCommand('/help')).toEqual({ type: 'help' });
|
||||||
|
expect(parseCommand('/?')).toEqual({ type: 'help' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parses /status command', () => {
|
||||||
|
expect(parseCommand('/status')).toEqual({ type: 'status' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parses /fullscreen command', () => {
|
||||||
|
expect(parseCommand('/fullscreen')).toEqual({ type: 'fullscreen' });
|
||||||
|
expect(parseCommand('/fs')).toEqual({ type: 'fullscreen' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parses /model command without argument', () => {
|
||||||
|
expect(parseCommand('/model')).toEqual({ type: 'model' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parses /model command with argument', () => {
|
||||||
|
expect(parseCommand('/model local')).toEqual({ type: 'model', name: 'local' });
|
||||||
|
expect(parseCommand('/model opus')).toEqual({ type: 'model', name: 'opus' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parses /transfer command', () => {
|
||||||
|
expect(parseCommand('/transfer telegram')).toEqual({ type: 'transfer', target: 'telegram' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parses regular message', () => {
|
||||||
|
expect(parseCommand('Hello Flynn')).toEqual({ type: 'message', content: 'Hello Flynn' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns null for empty input', () => {
|
||||||
|
expect(parseCommand('')).toBeNull();
|
||||||
|
expect(parseCommand(' ')).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getHelpText', () => {
|
||||||
|
it('returns help text with all commands', () => {
|
||||||
|
const help = getHelpText();
|
||||||
|
expect(help).toContain('/help');
|
||||||
|
expect(help).toContain('/model');
|
||||||
|
expect(help).toContain('/reset');
|
||||||
|
expect(help).toContain('/quit');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
export type Command =
|
||||||
|
| { type: 'quit' }
|
||||||
|
| { type: 'reset' }
|
||||||
|
| { type: 'help' }
|
||||||
|
| { type: 'status' }
|
||||||
|
| { type: 'fullscreen' }
|
||||||
|
| { type: 'model'; name?: string }
|
||||||
|
| { type: 'transfer'; target: string }
|
||||||
|
| { type: 'message'; content: string };
|
||||||
|
|
||||||
|
export function parseCommand(input: string): Command | null {
|
||||||
|
const trimmed = input.trim();
|
||||||
|
if (!trimmed) return null;
|
||||||
|
|
||||||
|
// Quit
|
||||||
|
if (trimmed === '/quit' || trimmed === '/exit') {
|
||||||
|
return { type: 'quit' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset
|
||||||
|
if (trimmed === '/reset' || trimmed === '/clear') {
|
||||||
|
return { type: 'reset' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Help
|
||||||
|
if (trimmed === '/help' || trimmed === '/?') {
|
||||||
|
return { type: 'help' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status
|
||||||
|
if (trimmed === '/status') {
|
||||||
|
return { type: 'status' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fullscreen
|
||||||
|
if (trimmed === '/fullscreen' || trimmed === '/fs') {
|
||||||
|
return { type: 'fullscreen' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Model (with optional argument)
|
||||||
|
if (trimmed === '/model') {
|
||||||
|
return { type: 'model' };
|
||||||
|
}
|
||||||
|
if (trimmed.startsWith('/model ')) {
|
||||||
|
const name = trimmed.slice('/model '.length).trim();
|
||||||
|
return { type: 'model', name };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transfer
|
||||||
|
if (trimmed.startsWith('/transfer ')) {
|
||||||
|
const target = trimmed.slice('/transfer '.length).trim();
|
||||||
|
return { type: 'transfer', target };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regular message
|
||||||
|
return { type: 'message', content: trimmed };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getHelpText(): string {
|
||||||
|
return `
|
||||||
|
Commands:
|
||||||
|
/help, /? Show this help
|
||||||
|
/model [name] Show or switch model (local, default, fast, complex)
|
||||||
|
/reset, /clear Clear conversation history
|
||||||
|
/status Show session info and token usage
|
||||||
|
/fullscreen, /fs Switch to fullscreen mode
|
||||||
|
/transfer <dest> Transfer session to another frontend
|
||||||
|
/quit, /exit Exit TUI
|
||||||
|
`.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ModelAlias = 'local' | 'default' | 'fast' | 'complex' | 'opus' | 'sonnet' | 'ollama';
|
||||||
|
|
||||||
|
export function resolveModelAlias(alias: string): 'local' | 'default' | 'fast' | 'complex' {
|
||||||
|
const map: Record<string, 'local' | 'default' | 'fast' | 'complex'> = {
|
||||||
|
local: 'local',
|
||||||
|
ollama: 'local',
|
||||||
|
default: 'default',
|
||||||
|
opus: 'default',
|
||||||
|
fast: 'fast',
|
||||||
|
sonnet: 'fast',
|
||||||
|
complex: 'complex',
|
||||||
|
};
|
||||||
|
return map[alias.toLowerCase()] ?? 'default';
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user