Skip to content

Commit

Permalink
Merge pull request #438 from ieedan/improve-update-w-ai
Browse files Browse the repository at this point in the history
feat: Add `additional instructions` prompt to `✨ Update with AI ✨`
  • Loading branch information
ieedan authored Feb 13, 2025
2 parents d36c317 + 6eeff02 commit ff4d93e
Show file tree
Hide file tree
Showing 10 changed files with 456 additions and 333 deletions.
5 changes: 5 additions & 0 deletions .changeset/curvy-ducks-draw.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"jsrepo": minor
---

feat: Add `additional instructions` prompt to `✨ Update with AI ✨` to allow users to give more context to the model on how to update the file
5 changes: 5 additions & 0 deletions .changeset/four-doors-matter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"jsrepo": minor
---

feat: Add `iterate` option for `✨ Update with AI ✨` so that you can reprompt with the context of past chats and iterate on the generated file.
5 changes: 5 additions & 0 deletions .changeset/thin-fireants-clean.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"jsrepo": patch
---

feat: Remember model choice for `✨ Update with AI ✨`
185 changes: 35 additions & 150 deletions packages/cli/src/commands/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,12 @@ import {
} from '@clack/prompts';
import color from 'chalk';
import { Command, Option, program } from 'commander';
import { diffLines } from 'diff';
import { createPathsMatcher } from 'get-tsconfig';
import { detect, resolveCommand } from 'package-manager-detector';
import path from 'pathe';
import * as v from 'valibot';
import { type ModelName, models } from '../utils/ai';
import * as ascii from '../utils/ascii';
import * as url from '../utils/blocks/ts/url';
import {
type Formatter,
PROJECT_CONFIG_NAME,
Expand All @@ -32,13 +31,12 @@ import {
} from '../utils/config';
import { packageJson } from '../utils/context';
import { installDependencies } from '../utils/dependencies';
import { formatDiff } from '../utils/diff';
import { formatFile, matchJSDescendant, tryGetTsconfig } from '../utils/files';
import { loadFormatterConfig } from '../utils/format';
import { json } from '../utils/language-support';
import { returnShouldInstall } from '../utils/package';
import * as persisted from '../utils/persisted';
import { type Task, intro, nextSteps, runTasks } from '../utils/prompts';
import { type Task, intro, nextSteps, promptUpdateFile, runTasks } from '../utils/prompts';
import * as registry from '../utils/registry-providers/internal';

const schema = v.object({
Expand Down Expand Up @@ -80,7 +78,7 @@ const init = new Command('init')
'The name of the build script. (For Registry setup)',
'build:registry'
)
.option('-E, --expand', 'Expands the diff so you see everything.', false)
.option('-E, --expand', 'Expands the diff so you see the entire file.', false)
.option(
'--max-unchanged <number>',
'Maximum unchanged lines that will show without being collapsed.',
Expand Down Expand Up @@ -595,18 +593,18 @@ const promptForProviderConfig = async ({

let fullFilePath = path.join(options.cwd, configFiles[file.name]);

let originalFileContents: string | undefined;
let fileContents: string | undefined;

if (fs.existsSync(fullFilePath)) {
originalFileContents = fs.readFileSync(fullFilePath).toString();
fileContents = fs.readFileSync(fullFilePath).toString();
} else {
const dir = path.dirname(fullFilePath);

if (fs.existsSync(dir)) {
const matchedPath = matchJSDescendant(fullFilePath);

if (matchedPath) {
originalFileContents = fs.readFileSync(matchedPath).toString();
fileContents = fs.readFileSync(matchedPath).toString();

const newPath = path.relative(options.cwd, matchedPath);

Expand Down Expand Up @@ -637,152 +635,37 @@ const promptForProviderConfig = async ({
},
biomeOptions,
prettierOptions,
config: {
$schema: '',
includeTests: false,
paths: {
'*': '',
},
repos: [],
watermark: false,
configFiles: {},
formatter,
},
formatter,
});

let remoteContent = originalRemoteContent;

loading.stop(`Fetched the ${color.cyan(file.name)} from ${color.cyan(repo)}`);

let acceptedChanges = options.yes || originalFileContents === undefined;
let acceptedChanges = options.yes || fileContents === undefined;

if (originalFileContents) {
if (fileContents) {
if (!options.yes) {
process.stdout.write(`${ascii.VERTICAL_LINE}\n`);

while (true) {
const changes = diffLines(originalFileContents, remoteContent);

// print diff
const formattedDiff = formatDiff({
from: file.name,
to: fullFilePath,
changes,
expand: options.expand,
maxUnchanged: options.maxUnchanged,
prefix: () => `${ascii.VERTICAL_LINE} `,
onUnchanged: ({ from, to, prefix }) =>
`${prefix?.() ?? ''}${color.cyan(from)}${color.gray(to)} ${color.gray('(unchanged)')}\n`,
intro: ({ from, to, changes, prefix }) => {
const totalChanges = changes.filter(
(a) => a.added || a.removed
).length;

return `${prefix?.() ?? ''}${color.cyan(from)}${color.gray(to)} (${totalChanges} change${
totalChanges === 1 ? '' : 's'
})\n${prefix?.() ?? ''}\n`;
},
});

process.stdout.write(formattedDiff);

// if there are no changes then don't ask
if (changes.length > 1 || originalFileContents === '') {
acceptedChanges = options.yes;

if (!options.yes) {
// prompt the user
const confirmResult = await select({
message: 'Accept changes?',
options: [
{
label: 'Accept',
value: 'accept',
},
{
label: 'Reject',
value: 'reject',
},
{
label: `✨ ${color.yellow('Update with AI')} ✨`,
value: 'update',
},
],
});

if (isCancel(confirmResult)) {
cancel('Canceled!');
process.exit(0);
}

if (confirmResult === 'update') {
// prompt for model
const modelResult = await select({
message: 'Select a model',
options: Object.keys(models).map((key) => ({
label: key,
value: key,
})),
});

if (isCancel(modelResult)) {
cancel('Canceled!');
process.exit(0);
}

const model = modelResult as ModelName;

try {
remoteContent = await models[model].updateFile({
originalFile: {
content: originalFileContents,
path: fullFilePath,
},
newFile: {
content: originalRemoteContent,
path: file.name,
},
loading,
});
} catch (err) {
loading.stop();
log.error(color.red(`Error getting completions: ${err}`));
process.stdout.write(`${ascii.VERTICAL_LINE}\n`);
continue;
}

remoteContent = await formatFile({
file: {
content: remoteContent,
destPath: fullFilePath,
},
biomeOptions,
prettierOptions,
config: {
$schema: '',
includeTests: false,
paths: {
'*': '',
},
repos: [],
watermark: false,
configFiles: {},
formatter,
},
});

process.stdout.write(`${ascii.VERTICAL_LINE}\n`);

continue;
}

acceptedChanges = confirmResult === 'accept';

break;
}
}

break; // there were no changes or changes were automatically accepted
const from = url.join(providerState.unwrap().url, file.name);

const updateResult = await promptUpdateFile({
config: { biomeOptions, prettierOptions, formatter },
current: {
content: fileContents,
path: fullFilePath,
},
incoming: {
content: originalRemoteContent,
path: from,
},
options: {
...options,
loading,
no: false,
},
});

if (updateResult.applyChanges) {
acceptedChanges = true;
fileContents = updateResult.updatedContent;
}
}
} else {
Expand All @@ -791,12 +674,14 @@ const promptForProviderConfig = async ({
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}

fileContents = originalRemoteContent;
}

if (acceptedChanges) {
if (acceptedChanges && fileContents) {
loading.start(`Writing ${color.cyan(file.name)} to ${color.cyan(fullFilePath)}`);

fs.writeFileSync(fullFilePath, remoteContentResult.unwrap());
fs.writeFileSync(fullFilePath, fileContents);

loading.stop(`Wrote ${color.cyan(file.name)} to ${color.cyan(fullFilePath)}`);
}
Expand Down
Loading

0 comments on commit ff4d93e

Please sign in to comment.