-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: remove Ink from chat * restore init with ink
- Loading branch information
1 parent
97ab170
commit ab21baa
Showing
29 changed files
with
569 additions
and
840 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import { CHATS_SAVE_DIRECTORY } from '../../file-utils.js'; | ||
import { | ||
exit, | ||
getVerbose, | ||
output, | ||
outputVerbose, | ||
outputWarning, | ||
setVerbose, | ||
} from '../../output/index.js'; | ||
import { getProvider, getProviderConfig } from './providers.js'; | ||
import { messages } from './state.js'; | ||
import { saveConversation } from './utils.js'; | ||
|
||
export function processChatCommand(input: string) { | ||
if (!input.startsWith('/')) { | ||
return false; | ||
} | ||
|
||
const command = input.split(' ')[0]; | ||
if (command === '/exit') { | ||
exit(); | ||
} | ||
|
||
if (command === '/help') { | ||
outputHelp(); | ||
return true; | ||
} | ||
|
||
if (command === '/info') { | ||
outputInfo(); | ||
return true; | ||
} | ||
|
||
if (command === '/forget') { | ||
// Clear all messages | ||
messages.length = 0; | ||
return true; | ||
} | ||
|
||
if (command === '/verbose') { | ||
setVerbose(!getVerbose()); | ||
output(`Verbose mode: ${getVerbose() ? 'on' : 'off'}`); | ||
return true; | ||
} | ||
|
||
if (input === '/save') { | ||
const saveConversationMessage = saveConversation(messages); | ||
output(saveConversationMessage); | ||
return true; | ||
} | ||
|
||
outputWarning(`Unknown command: ${command}`); | ||
return true; | ||
} | ||
|
||
export function outputHelp() { | ||
const lines = [ | ||
'', | ||
'Available commands:', | ||
' - /exit: Exit the CLI', | ||
' - /info: Show current provider, model, and system prompt', | ||
' - /forget: AI will forget previous messages', | ||
` - /save: Save in a text file in ${CHATS_SAVE_DIRECTORY}`, | ||
' - /verbose: Toggle verbose output', | ||
'', | ||
]; | ||
|
||
output(lines.join('\n')); | ||
} | ||
|
||
export function outputInfo() { | ||
const provider = getProvider(); | ||
const providerConfig = getProviderConfig(); | ||
|
||
const lines = [ | ||
'', | ||
'Info:', | ||
` - Provider: ${provider.label}`, | ||
` - Model: ${providerConfig.model}`, | ||
` - System prompt: ${providerConfig.systemPrompt}`, | ||
'', | ||
]; | ||
output(lines.join('\n')); | ||
|
||
const rawMessages = JSON.stringify( | ||
messages.map((m) => `${m.role}: ${m.content}`), | ||
null, | ||
2, | ||
); | ||
outputVerbose(`Messages: ${rawMessages}\n`); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { AssistantResponse } from '@callstack/byorg-core'; | ||
import { formatSpeed, formatTime } from '../../format.js'; | ||
import { colorAssistant, colorVerbose } from '../../output/colors.js'; | ||
import { getVerbose } from '../../output/index.js'; | ||
|
||
export function formatResponse(response: AssistantResponse) { | ||
let result = colorAssistant(response.content); | ||
if (getVerbose()) { | ||
result += ` ${colorVerbose(formatResponseStats(response))}`; | ||
} | ||
|
||
return result; | ||
} | ||
|
||
function formatResponseStats(message: AssistantResponse) { | ||
return `${formatTime(message.usage.responseTime)} ${formatSpeed( | ||
message.usage?.outputTokens, | ||
message.usage.responseTime, | ||
)}`; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,32 +1,98 @@ | ||
import { Application, createApp, Message } from '@callstack/byorg-core'; | ||
import type { CommandModule } from 'yargs'; | ||
import * as React from 'react'; | ||
import { render } from 'ink'; | ||
import { ThemeProvider } from '@inkjs/ui'; | ||
import { parseConfigFile } from '../../config-file.js'; | ||
import * as output from '../../output.js'; | ||
import { inkTheme } from '../../theme/ink-theme.js'; | ||
import { initChatState } from './state/init.js'; | ||
import { promptOptions, type PromptOptions } from './prompt-options.js'; | ||
import { ChatUi } from './ui/ChatUi.js'; | ||
import { colorAssistant } from '../../output/colors.js'; | ||
import { | ||
clearCurrentLine, | ||
output, | ||
outputError, | ||
readUserInput, | ||
setInterruptHandler, | ||
setVerbose, | ||
} from '../../output/index.js'; | ||
import { processChatCommand } from './commands.js'; | ||
import { cliOptions, type CliOptions } from './cli-options.js'; | ||
import { formatResponse } from './format.js'; | ||
import { getProvider, getProviderConfig, initProvider } from './providers.js'; | ||
import { spinnerStart, spinnerStop, spinnerUpdate } from './spinner.js'; | ||
import { messages } from './state.js'; | ||
import { texts } from './texts.js'; | ||
|
||
export const command: CommandModule<{}, PromptOptions> = { | ||
export const command: CommandModule<{}, CliOptions> = { | ||
command: ['chat', '$0'], | ||
describe: 'Start a conversation with AI assistant.', | ||
builder: promptOptions, | ||
builder: cliOptions, | ||
handler: (args) => run(args._.join(' '), args), | ||
}; | ||
|
||
function run(initialPrompt: string, options: PromptOptions) { | ||
let shouldExit = false; | ||
|
||
async function run(initialPrompt: string, options: CliOptions) { | ||
setVerbose(options.verbose ?? false); | ||
|
||
try { | ||
const configFile = parseConfigFile(); | ||
initChatState(options, configFile, initialPrompt); | ||
render( | ||
<ThemeProvider theme={inkTheme}> | ||
<ChatUi /> | ||
</ThemeProvider>, | ||
); | ||
initProvider(options, configFile); | ||
const app = createApp({ | ||
chatModel: getProvider().getChatModel(getProviderConfig()), | ||
systemPrompt: () => getProviderConfig().systemPrompt, | ||
}); | ||
|
||
if (initialPrompt) { | ||
messages.push({ role: 'user', content: initialPrompt }); | ||
} | ||
|
||
setInterruptHandler(() => { | ||
spinnerStop(); | ||
clearCurrentLine(); | ||
console.log('\nBye...'); | ||
shouldExit = true; | ||
}); | ||
|
||
if (messages.length > 0) { | ||
messages.forEach((m) => outputMessage(m)); | ||
await processMessages(app, messages); | ||
} | ||
|
||
while (!shouldExit) { | ||
const userMessage = await readUserInput(); | ||
if (processChatCommand(userMessage)) { | ||
continue; | ||
} | ||
|
||
messages.push({ role: 'user', content: userMessage }); | ||
await processMessages(app, messages); | ||
} | ||
} catch (error) { | ||
output.outputError(error); | ||
outputError(error); | ||
process.exit(1); | ||
} | ||
} | ||
|
||
async function processMessages(app: Application, messages: Message[]) { | ||
const stream = getProviderConfig().stream; | ||
|
||
const onPartialUpdate = (content: string) => { | ||
spinnerUpdate(colorAssistant(`${texts.assistantLabel} ${content}`)); | ||
}; | ||
|
||
spinnerStart(colorAssistant(texts.assistantLabel)); | ||
const { response } = await app.processMessages(messages, { | ||
onPartialResponse: stream ? onPartialUpdate : undefined, | ||
}); | ||
|
||
if (response.role === 'assistant') { | ||
messages.push({ role: 'assistant', content: response.content }); | ||
spinnerStop(`${formatResponse(response)}\n`); | ||
} else { | ||
spinnerStop(response.content); | ||
} | ||
} | ||
|
||
function outputMessage(message: Message) { | ||
if (message.role === 'user') { | ||
output(`${texts.userLabel} ${message.content}`); | ||
} else { | ||
output(colorAssistant(`${texts.assistantLabel} ${message.content}`)); | ||
} | ||
} |
Oops, something went wrong.