diff --git a/src/feedback.ts b/src/feedback.ts deleted file mode 100644 index cd4c741..0000000 --- a/src/feedback.ts +++ /dev/null @@ -1,46 +0,0 @@ -import inquirer from 'inquirer' - -class HumanFeedback { - private completions: string[] - private selectedCompletion: string - - // Process the completions returned by the AI - process(completions: string[]) { - this.completions = completions - return this - } - - // Request feedback from the user - async requestFeedback() { - const choices = this.completions.map((completion, index) => ({ - name: completion, - value: index - })) - - const feedback = await inquirer.prompt([ - { - type: 'list', - name: 'userResponse', - message: 'Pick the best completion:', - choices: [...choices, new inquirer.Separator(), 'Retry', 'Decline'] - } - ]) - - switch (feedback.userResponse) { - case 'Retry': - return true - case 'Decline': - return process.exit(0) - default: - this.selectedCompletion = this.completions[feedback.userResponse] - return false - } - } - - // Return the selected completion - getResult() { - return this.selectedCompletion - } -} - -export { HumanFeedback } diff --git a/src/index.ts b/src/index.ts index 5a32c77..0378c34 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,7 +4,7 @@ export * from './llm' export * from './openai' export * from './anthropic' export * from './tokenizer' -export * from './feedback' export * from './services/metaphor' export * from './tools/metaphor' +export * from './tools/feedback' diff --git a/src/tools/feedback.ts b/src/tools/feedback.ts new file mode 100644 index 0000000..37cf136 --- /dev/null +++ b/src/tools/feedback.ts @@ -0,0 +1,210 @@ +import { checkbox, editor, select } from '@inquirer/prompts' +import { ZodTypeAny, z } from 'zod' + +import * as types from './../types' +import { BaseTaskCallBuilder } from './../task' + +enum UserActions { + Accept = 'accept', + Edit = 'edit', + Decline = 'decline', + Select = 'select' +} + +const UserActionMessages = { + [UserActions.Accept]: 'Accept inputs', + [UserActions.Edit]: 'Edit (open in editor)', + [UserActions.Decline]: 'Decline', + [UserActions.Select]: 'Select inputs to keep' +} + +export const FeedbackSingleInputSchema = (choice: T) => + z.object({ + choice + }) + +export const FeedbackSingleOutputSchema = (result: T) => + z.object({ + result: result, + accepted: z.boolean() + }) + +export class HumanFeedbackSingle< + T extends ZodTypeAny = ZodTypeAny +> extends BaseTaskCallBuilder { + private choiceSchema: T + + constructor(choiceSchema: T) { + super() + this.choiceSchema = choiceSchema + } + + public get inputSchema() { + return FeedbackSingleInputSchema(this.choiceSchema) + } + + public get outputSchema() { + return FeedbackSingleOutputSchema(this.choiceSchema) + } + + private async handleChoice( + input: types.ParsedData + ): Promise> { + const feedback = await select({ + message: [ + 'The following input was generated:', + JSON.stringify(input.choice, null, 2), + 'What would you like to do?' + ].join('\n'), + choices: [ + { + name: UserActionMessages[UserActions.Accept], + value: UserActions.Accept + }, + { + name: UserActionMessages[UserActions.Edit], + value: UserActions.Edit + }, + { + name: UserActionMessages[UserActions.Decline], + value: UserActions.Decline + } + ] + }) + switch (feedback) { + case UserActions.Edit: { + // Open the completion in the user's default editor + const editedInput = await editor({ + message: 'Edit the input:', + default: JSON.stringify(input.choice) + }) + return { + result: this.choiceSchema.parse(JSON.parse(editedInput)), + accepted: true + } + } + case UserActions.Decline: + return { result: null, accepted: false } + case UserActions.Accept: + return { result: input, accepted: true } + default: + throw new Error('Invalid feedback choice') + } + } + + public async call( + input: types.ParsedData + ): Promise> { + try { + input = this.inputSchema.parse(input) + return this.handleChoice(input) + } catch (err) { + console.error('Error parsing input:', err) + throw err + } + } +} + +export const FeedbackSelectInputSchema = (choice: T) => + z.object({ + choices: z.array(choice) + }) + +export const FeedbackSelectOutputSchema = (result: T) => + z.object({ + results: z.array(result), + accepted: z.boolean() + }) + +export class HumanFeedbackSelect< + T extends ZodTypeAny = ZodTypeAny +> extends BaseTaskCallBuilder { + private choiceSchema: T + + constructor(choiceSchema: T) { + super() + this.choiceSchema = choiceSchema + } + + public get inputSchema() { + return FeedbackSelectInputSchema(this.choiceSchema) + } + + public get outputSchema() { + return FeedbackSelectOutputSchema(this.choiceSchema) + } + + private async handleChoices( + input: types.ParsedData + ): Promise> { + // Case: input is an array of strings + const feedback = await select({ + message: [ + 'The following inputs were generated:', + ...input.choices.map( + (choice, index) => `${index + 1}. ${JSON.stringify(choice, null, 2)}` + ), + 'What would you like to do?' + ].join('\n'), + choices: [ + { + name: UserActionMessages[UserActions.Accept], + value: UserActions.Accept + }, + { + name: UserActionMessages[UserActions.Edit], + value: UserActions.Edit + }, + { + name: UserActionMessages[UserActions.Decline], + value: UserActions.Decline + }, + { + name: UserActionMessages[UserActions.Select], + value: UserActions.Select + } + ] + }) + switch (feedback) { + case UserActions.Edit: { + const edited = await editor({ + message: 'Edit the input:', + default: JSON.stringify(input.choices, null, 2) + }) + return { results: JSON.parse(edited), accepted: true } + } + case UserActions.Select: { + const choices = input.choices.map((completion) => ({ + name: completion, + value: completion + })) + const chosen = await checkbox({ + message: 'Pick items to keep:', + choices: [...choices] + }) + if (chosen.length === 0) { + return { results: [], accepted: false } + } + return { results: chosen, accepted: true } + } + case UserActions.Decline: + return { results: [], accepted: false } + case UserActions.Accept: + return { results: input.choices, accepted: true } + default: + throw new Error('Invalid feedback choice') + } + } + + public async call( + input: types.ParsedData + ): Promise> { + try { + input = this.inputSchema.parse(input) + return this.handleChoices(input) + } catch (err) { + console.error('Error parsing input:', err) + throw err + } + } +}