From ea2a0a97bdb1527d509c25a32a036094b322e759 Mon Sep 17 00:00:00 2001 From: nontangent Date: Fri, 2 Dec 2022 03:05:09 +0900 Subject: [PATCH 1/4] docs: update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f3fc68d..3e2f20c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Ng Atomic(Beta) +# Ng Atomic(Alpha) This is an atomic design system framework based on Angular for all web platforms such as React, Vue or of cource Angular.
ng add @ng-atomic/schematics
@@ -61,7 +61,7 @@ $ ng g @ng-atomic/schematics:components ## @ng-atomic/components Base components for Atomic Design System -Look at [the storybook](). +Look at [the storybook](https://nontangent.github.io/ng-atomic/storybook/). ## @ng-atomic/elements From ca406ebd51ec7413a3df0e3382f2e49fb99bbdfb Mon Sep 17 00:00:00 2001 From: nontangent Date: Fri, 2 Dec 2022 03:06:24 +0900 Subject: [PATCH 2/4] docs: update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 3e2f20c..6eec2bb 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +Not recommended because the interface is unstable. Don't believe what it says! + # Ng Atomic(Alpha) This is an atomic design system framework based on Angular for all web platforms such as React, Vue or of cource Angular. From 0ba20d206778e8656480aa77eca842bae0e87bb6 Mon Sep 17 00:00:00 2001 From: nontangent Date: Sat, 3 Dec 2022 00:57:42 +0900 Subject: [PATCH 3/4] refactor(schematics): clean code --- libs/schematics/src/gpt3/distance.spec.ts | 45 ++++++++ libs/schematics/src/gpt3/distance.ts | 68 ++++++++++++ libs/schematics/src/gpt3/helpers.spec.ts | 28 ++--- libs/schematics/src/gpt3/helpers.ts | 65 +---------- libs/schematics/src/gpt3/index.spec.ts | 23 ++-- libs/schematics/src/gpt3/index.ts | 104 ++++-------------- libs/schematics/src/gpt3/prompter.spec.ts | 28 +++++ libs/schematics/src/gpt3/prompter.ts | 93 ++++++++++++++++ libs/schematics/src/gpt3/schematics-x.spec.ts | 42 +++++++ libs/schematics/src/gpt3/schematics-x.ts | 82 ++++++++++++++ 10 files changed, 401 insertions(+), 177 deletions(-) create mode 100644 libs/schematics/src/gpt3/distance.spec.ts create mode 100644 libs/schematics/src/gpt3/distance.ts create mode 100644 libs/schematics/src/gpt3/prompter.spec.ts create mode 100644 libs/schematics/src/gpt3/prompter.ts create mode 100644 libs/schematics/src/gpt3/schematics-x.spec.ts create mode 100644 libs/schematics/src/gpt3/schematics-x.ts diff --git a/libs/schematics/src/gpt3/distance.spec.ts b/libs/schematics/src/gpt3/distance.spec.ts new file mode 100644 index 0000000..def0604 --- /dev/null +++ b/libs/schematics/src/gpt3/distance.spec.ts @@ -0,0 +1,45 @@ +import { calculateDistance, convertByWords, parseFilePath, standarization } from './distance'; + +describe('standarization', () => { + it('', () => { + expect(standarization( + '/libs/example/src/lib/domain/models/first-model.ts', + '/libs/example/src/lib/domain/models/second-model.ts', + )).toEqual([ + '/0/1/2/3/4/5/6.7', + '/0/1/2/3/4/5/8.7', + ]) + }); +}); + +describe('calculateDistance()', () => { + it('', () => { + expect(calculateDistance( + '/libs/example/src/lib/domain/models/first-model.ts', + '/libs/example/src/lib/domain/models/second-model.ts', + )).toEqual(1) + }); + + it('', () => { + expect(calculateDistance( + '/libs/example/src/lib/domain/models/first-model.component.ts', + '/libs/example/src/lib/domain/models/second-model.module.ts', + )).toEqual(2) + }); +}); + +describe('parseFilePath', () => { + it('should parse file path', () => { + expect(parseFilePath('/projects/app/src/app/_shared/components/example/example.component.ts')).toEqual([ + '/', 'projects', '/', 'app', '/', 'src', '/', 'app', '/', '_shared', '/', + 'components', '/', 'example', '/', 'example', '.', 'component', '.', 'ts' + ]); + }); +}); + +describe('convertByWords', () => { + xit('should convert from a string to a number', () => { + expect(convertByWords('this/is/test/path', ['this', 'is', 'test', 'path'])).toBe('0/1/2/3'); + }); +}); + diff --git a/libs/schematics/src/gpt3/distance.ts b/libs/schematics/src/gpt3/distance.ts new file mode 100644 index 0000000..75adbed --- /dev/null +++ b/libs/schematics/src/gpt3/distance.ts @@ -0,0 +1,68 @@ +export function standarization(str1: string, str2: string): [string, string] { + const wordSet = new Set([str1, str2].map(path => parseFilePath(path)).flat()); + wordSet.delete('.'); + wordSet.delete('/'); + return [convertByWords(str1, [...wordSet]), convertByWords(str2, [...wordSet])]; +} + +export function calculateDistance(str1: string, str2: string): number { + const [s1, s2] = standarization(str1, str2); + return levenshteinDistance(s1, s2) + depthDistance(str1, str2); +} + +const LANG_MAP = `0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+-=[]{};:'",.<>/?|\\~\` `.split(''); + +export function convertByWords(str: string, words: string[]): string { + return parseFilePath(str).map(c => ~words.indexOf(c) ? LANG_MAP[words.indexOf(c)] : c).join(''); +} + +export function parseFilePath(filePath: string, characters = ['.', '/']): string[] { + const result = []; + let word = ''; + for (let i = 0; i < filePath.length; i++) { + const char = filePath[i]; + if (characters.includes(char)) { + if (word.length) result.push(word); + result.push(char); + word = ''; + } else { + word += char; + } + } + result.push(word); + return result; +} + +function depthDistance(str1: string, str2: string): number { + const depth1 = str1.split('/').length; + const depth2 = str2.split('/').length; + return Math.abs(depth1 - depth2); +} + + +function levenshteinDistance(str1: string, str2: string): number { + const cost = (a: string, b: string): number => { + return a === b ? 0 : 1; + } + + const d = []; + for (let i = 0; i <= str1.length; i++) { + d[i] = []; + d[i][0] = i; + } + for (let j = 0; j <= str2.length; j++) { + d[0][j] = j; + } + + for (let i = 1; i <= str1.length; i++) { + for (let j = 1; j <= str2.length; j++) { + d[i][j] = Math.min( + d[i - 1][j] + 1, + d[i][j - 1] + 1, + d[i - 1][j - 1] + cost(str1[i - 1], str2[j - 1]) + ); + } + } + + return d[str1.length][str2.length]; +} diff --git a/libs/schematics/src/gpt3/helpers.spec.ts b/libs/schematics/src/gpt3/helpers.spec.ts index 3133a1c..5adfa6a 100644 --- a/libs/schematics/src/gpt3/helpers.spec.ts +++ b/libs/schematics/src/gpt3/helpers.spec.ts @@ -1,4 +1,4 @@ -import { getEstimateSimilarFilePaths, convertByWords, parseFilePath } from './helpers'; +import { getEstimateSimilarFilePaths } from './helpers'; describe('getEstimateSimilarFilePaths', () => { const FILES = [ @@ -18,8 +18,18 @@ describe('getEstimateSimilarFilePaths', () => { '/projects/app/src/app/_shared/components/test/test.component.ts', '/projects/app/src/app/_shared/components/test/test.stories.ts', '/projects/app/src/app/_shared/components/test/index.ts', + '/libs/nm-common/src/lib/domain/models/invoice.ts', + '/libs/nm-common/src/lib/domain/models/contract.ts', ]; + it('', () => { + const result = getEstimateSimilarFilePaths('/libs/nm-common/src/lib/domain/models/customer.ts', FILES); + expect(result).toEqual([ + '/libs/nm-common/src/lib/domain/models/invoice.ts', + '/libs/nm-common/src/lib/domain/models/contract.ts', + ]); + }); + it('', () => { const file = '/projects/app/src/app/_shared/components/expected/expected.component.ts'; const files = getEstimateSimilarFilePaths(file, FILES); @@ -38,19 +48,3 @@ describe('getEstimateSimilarFilePaths', () => { ]); }); }); - -describe('parseFilePath', () => { - it('should parse file path', () => { - expect(parseFilePath('/projects/app/src/app/_shared/components/example/example.component.ts')).toEqual([ - '/', 'projects', '/', 'app', '/', 'src', '/', 'app', '/', '_shared', '/', - 'components', '/', 'example', '/', 'example', '.', 'component', '.', 'ts' - ]); - }); -}); - -describe('convertByWords', () => { - xit('should convert from a string to a number', () => { - expect(convertByWords('this/is/test/path', ['this', 'is', 'test', 'path'])).toBe('0/1/2/3'); - }); -}); - diff --git a/libs/schematics/src/gpt3/helpers.ts b/libs/schematics/src/gpt3/helpers.ts index 2e8abf3..b3fcd9f 100644 --- a/libs/schematics/src/gpt3/helpers.ts +++ b/libs/schematics/src/gpt3/helpers.ts @@ -1,68 +1,7 @@ - -export function parseFilePath(filePath: string, characters = ['.', '/']): string[] { - const result = []; - let word = ''; - for (let i = 0; i < filePath.length; i++) { - const char = filePath[i]; - if (characters.includes(char)) { - if (word.length) result.push(word); - result.push(char); - word = ''; - } else { - word += char; - } - } - result.push(word); - return result; -} - -export const convertByWords = (str: string, words: string[]): string => { - return parseFilePath(str).map(c => ~words.indexOf(c) ? words.indexOf(c) : c).join(''); -} +import { calculateDistance } from "./distance"; export function getEstimateSimilarFilePaths(path: string, files: string[]): string[] { - const wordSet = new Set([path, ...files].map(path => parseFilePath(path)).flat()); - wordSet.delete('.'); - wordSet.delete('/'); - const words = Array.from(wordSet); - const results = files.map(file => { - const distance = levenshteinDistance(convertByWords(path, words), convertByWords(file, words)) + depthDistance(path, file); - return [distance, file] as [number, string]; - }); + const results = files.map(file => [calculateDistance(path, file), file] as [number, string]); const min = Math.min(...results.map(([distance]) => distance)); return results.filter(([distance]) => distance === min).map(([_, file]) => file); } - -function depthDistance(str1: string, str2: string): number { - const depth1 = str1.split('/').length; - const depth2 = str2.split('/').length; - return Math.abs(depth1 - depth2); -} - - -function levenshteinDistance(str1: string, str2: string): number { - const cost = (a: string, b: string): number => { - return a === b ? 0 : 1; - } - - const d = []; - for (let i = 0; i <= str1.length; i++) { - d[i] = []; - d[i][0] = i; - } - for (let j = 0; j <= str2.length; j++) { - d[0][j] = j; - } - - for (let i = 1; i <= str1.length; i++) { - for (let j = 1; j <= str2.length; j++) { - d[i][j] = Math.min( - d[i - 1][j] + 1, - d[i][j - 1] + 1, - d[i - 1][j - 1] + cost(str1[i - 1], str2[j - 1]) - ); - } - } - - return d[str1.length][str2.length]; -} \ No newline at end of file diff --git a/libs/schematics/src/gpt3/index.spec.ts b/libs/schematics/src/gpt3/index.spec.ts index 7d3c535..4e96e69 100644 --- a/libs/schematics/src/gpt3/index.spec.ts +++ b/libs/schematics/src/gpt3/index.spec.ts @@ -1,7 +1,6 @@ import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; import path from 'path'; import { createWorkspace } from '../_testing'; -import { parseJsonCodeBlock, parseCodeBlocks } from './index'; jest.setTimeout(300 * 1000); @@ -11,7 +10,7 @@ describe('Gpt Schematics', () => { const runner = new SchematicTestRunner('@ng-atomic/schematics', COLLECTION_PATH); let tree: UnitTestTree; - describe('Angular Workspace', () => { + xdescribe('Angular Workspace', () => { beforeEach(async () => { tree = await createWorkspace(runner, tree); tree = await runner.runSchematicAsync('atomic-component', { @@ -80,11 +79,11 @@ const TEST = ` // }); // }); -describe('parseJsonCodeBlock', () => { - it('should parse json code block', () => { - expect(parseJsonCodeBlock(TEST)).toBeTruthy(); - }); -}); +// describe('parseJsonCodeBlock', () => { +// it('should parse json code block', () => { +// expect(parseJsonCodeBlock(TEST)).toBeTruthy(); +// }); +// }); const CODE_BLOCKS = ` @@ -149,8 +148,8 @@ export class ExpectedComponent implements OnInit { \`\`\` `; -describe('parseCodeBlocks', () => { - it('should parse code blocks', () => { - expect(parseCodeBlocks(CODE_BLOCKS)).toBeTruthy(); - }); -}); +// describe('parseCodeBlocks', () => { +// it('should parse code blocks', () => { +// expect(parseCodeBlocks(CODE_BLOCKS)).toBeTruthy(); +// }); +// }); diff --git a/libs/schematics/src/gpt3/index.ts b/libs/schematics/src/gpt3/index.ts index 9bea044..229b335 100644 --- a/libs/schematics/src/gpt3/index.ts +++ b/libs/schematics/src/gpt3/index.ts @@ -1,101 +1,35 @@ import { Rule, Tree, chain, DirEntry, FileEntry } from '@angular-devkit/schematics'; import { Schema } from '../atomic-component/schema'; import { createDefaultPath } from '@schematics/angular/utility/workspace'; -import { Configuration, OpenAIApi } from 'openai'; -import { join, parse } from 'path'; -import { getEstimateSimilarFilePaths } from './helpers'; +import { join } from 'path'; +import { SchematicsX } from './schematics-x'; -const config = new Configuration({apiKey: process.env['OPEN_AI_TOKEN']}); -const openai = new OpenAIApi(config); -export const gpt3 = (options: Schema): Rule => async (tree: Tree) => { - options.path = join(await createDefaultPath(tree, options.project), options?.path ?? ''); - - const files = getFiles(tree.root); - const path = join(tree.root.path, options.path, options.name); - const estimatedFiles = parse(path).ext.length ? [path] : await getPredicatedFiles(files, path); - - for (const file of estimatedFiles) { - const fileEntries = getEstimateSimilarFilePaths(file, files).map(file => tree.get(file)); - const fileEntry = await getPredicatedFileEntry(file, fileEntries); - tree.create(file, fileEntry.content); - } - - return chain([]); -}; - -async function getPredicatedFiles(files: string[], dirPath: string): Promise { - const fileStart = dirPath.split('/').slice(-1)[0]; - const prompt = makeDirectoryPrompt(files, join(dirPath, fileStart)); - const res = await openai.createCompletion({ - model: 'code-davinci-002', - prompt, - temperature: 0, - max_tokens: 512, - stop: '\n\`\`\`', - }); - const results = completeToJson(`${prompt}${res.data.choices?.[0].text}`); - return results.filter(result => result.startsWith(dirPath)); -} - -async function getPredicatedFileEntry(path: string, fileEntries: FileEntry[] = []): Promise { - const prompt = makeFilePrompt(fileEntries, path); - const res = await openai.createCompletion({ - model: 'code-davinci-002', - prompt, - temperature: 0, - max_tokens: 256, - stop: '\n\`\`\`', - }); - const entries = parseCodeBlocks(`${prompt}${res.data.choices?.[0].text}`); - return entries.find(entry => entry.path === path); -} - -function makeDirectoryPrompt(files: string[], name: string): string { - return `\n\`\`\`tree.json\n[\n${files.map(file => `\t\"${file}\",`).join('\n')}\n\t\"${name}`; -} - -function makeFilePrompt(files: FileEntry[], path: string): string { - return `${makeFileCodeBlocks(files)}\n\n\`\`\`${path}\n`; -} - -function makeFileCodeBlocks(files: FileEntry[]): string { - return files.map(file => makeFileCodeBlock(file.path, file.content.toString())).join('\n\n'); +export function getFilePaths(tree: Tree, path: string = '/'): string[] { + return getFiles(tree.getDir(path)).map(p => join(path, p)); } -function makeFileCodeBlock(name: string, content: string): string { - return `\`\`\`${name}\n${content}\n\`\`\``; +export async function resolvePath(tree, options: {project?: string, path?: string}): Promise { + const defaultPath = await createDefaultPath(tree, options.project); + return join(defaultPath, options?.path ?? ''); } -export function completeToJson(text: string): string[] { - while(text.length) { - let suffixes = ['"]\n```', ']\n```', '\n```', '```', '``', '`', '']; +export const gpt3 = (options: Schema): Rule => async (tree: Tree) => { + options.path = await resolvePath(tree, options); + + const files = getFilePaths(tree, options.path); + const path = join(tree.root.path, options.path, options.name); + const schematicsX = new SchematicsX(); + const entries = await schematicsX.generate(files.map(file => tree.get(file)), path); - for (const suffix of suffixes) { - try { - return parseJsonCodeBlock(text + suffix); - } catch { } + for (const entry of entries) { + if (!tree.exists(entry.path)) { + tree.create(entry.path, entry.content); } - - text = text.slice(0, -1); } -} -export function parseJsonCodeBlock(text: string): string[] { - const code = getCodeBlocks(text); - return JSON.parse(code); -} - -function getCodeBlocks(text: string): string { - return text.match(/\`\`\`tree\.json\n([\s\S]*)\`\`\`/)?.[1]; -} - -export function parseCodeBlocks(text: string): FileEntry[] { - return text.split('```').filter((_, i) => i % 2).map(code => { - const [path, ...lines] = code.split('\n'); - return {path, content: Buffer.from(lines.join('\n'))} as FileEntry; - }); -} + return chain([]); +}; function getFiles(dir: DirEntry): string[] { const files: string[] = []; diff --git a/libs/schematics/src/gpt3/prompter.spec.ts b/libs/schematics/src/gpt3/prompter.spec.ts new file mode 100644 index 0000000..e9aee77 --- /dev/null +++ b/libs/schematics/src/gpt3/prompter.spec.ts @@ -0,0 +1,28 @@ +import { OpenAiPrompter } from './prompter'; + +jest.setTimeout(3000 * 1000); + +const PROMPT_JSON = ` +\`tree.json\` is 15 lines. + +\`\`\`tree.json +[ + "/root/app.json", +`.slice(1); + +describe('OpenAiPrompter', () => { + let prompter: OpenAiPrompter; + + beforeEach(() => { + prompter = new OpenAiPrompter(''); + }); + + xit('', async () => { + prompter.write(PROMPT_JSON); + await prompter.autoWriteUntilEnd(); + const fileEntry = prompter.getFileEntry('tree.json'); + const body = fileEntry.content.toString(); + expect(JSON.parse(body)).toBeTruthy(); + }); + +}); \ No newline at end of file diff --git a/libs/schematics/src/gpt3/prompter.ts b/libs/schematics/src/gpt3/prompter.ts new file mode 100644 index 0000000..c00dbbf --- /dev/null +++ b/libs/schematics/src/gpt3/prompter.ts @@ -0,0 +1,93 @@ +import { FileEntry } from "@angular-devkit/schematics"; +import { Configuration, OpenAIApi } from "openai"; + +export interface Options { + model?: 'text-curie-001' | 'code-davinci-002' | 'code-cushman-001', + +} + +export class OpenAiPrompter { + private config = new Configuration({apiKey: process.env['OPEN_AI_TOKEN']}); + + constructor(private _prompt: string) { } + private openai = new OpenAIApi(this.config); + private stop = '\n\`\`\`'; + + get prompt(): string { + return this._prompt; + } + + async autoWrite(options?: Options) { + try { + const res = await this.openai.createCompletion({ + model: options?.model ?? 'code-cushman-001', + prompt: this._prompt, + temperature: 0, + max_tokens: 512, + stop: this.stop, + }); + + this._prompt += res.data.choices?.[0].text; + this._prompt += res.data.choices?.[0].finish_reason === 'stop' ? this.stop : ''; + } catch (res) { + console.error(res.response.data.error); + throw new Error('OpenAI API Error'); + } + } + + async autoWriteUntilEnd(options?: Options, maxRepeat: number = 3) { + for (let i = 0; i < maxRepeat; i++) { + if(this.isEnd()) return; + await this.autoWrite(); + } + } + + async write(text: string) { + this._prompt += text; + } + + isEnd() { + return this._prompt.endsWith(this.stop); + } + + getFileEntries(): FileEntry[] { + return this._prompt.split('```').filter((_, i) => i % 2).map(code => { + const [path, ...lines] = code.split('\n'); + return {path, content: Buffer.from(lines.join('\n').slice(0, -1))} as FileEntry; + }); + } + + getFileEntry(path: string): FileEntry | null { + const entries = this.getFileEntries(); + return entries.find(file => file.path === path) ?? null; + } +} + + +export class JsonPrompter extends OpenAiPrompter { + + parseJson(prompt: string): string[] { + return JSON.parse(prompt.match(/\`\`\`tree\.json\n([\s\S]*)\`\`\`/)?.[1]); + } + + getJsonFuzzy(): string[] { + let text = this.prompt; + while(text.length) { + let suffixes = ['"]\n```', ']\n```', '\n```', '```', '``', '`', '']; + + for (const suffix of suffixes) { + try { return this.parseJson(text + suffix); } catch { } + } + + text = text.slice(0, -1); + } + } + +} + +// const results = completeToJson(`${prompt}${res.data.choices?.[0].text}`); +// return [...new Set(results)].filter(result => result.startsWith(dirPath)); +// } + + + diff --git a/libs/schematics/src/gpt3/schematics-x.spec.ts b/libs/schematics/src/gpt3/schematics-x.spec.ts new file mode 100644 index 0000000..55e04e1 --- /dev/null +++ b/libs/schematics/src/gpt3/schematics-x.spec.ts @@ -0,0 +1,42 @@ +import { FileEntry } from '@angular-devkit/schematics'; +import { SchematicsX } from './schematics-x'; + +const FILE_ENTRIES = [ + { + path: '/projects/app/src/app/_shared/components/test/test.module.ts', + content: Buffer.from(`test`), + }, + { + path: '/projects/app/src/app/_shared/components/example/example.module.ts', + content: Buffer.from(`example`), + }, +] as FileEntry[]; + +describe('SchematicsX', () => { + let schematicsX: SchematicsX; + + beforeEach(() => { + schematicsX = new SchematicsX(); + }); + + describe('generateFileEntry', () => { + it('should create', async () => { + const PATH = '/projects/app/src/app/_shared/components/expected/expected.module.ts'; + const entry = await schematicsX.generateFileEntry(PATH, FILE_ENTRIES); + expect(entry.content.toString()).toEqual(`expected`); + }); + }); + + describe('predicateGenerateFilePaths', () => { + it('should be succeeded', async () => { + const PATH = '/projects/app/src/app/_shared/components/expected'; + const context = FILE_ENTRIES.map(entry => entry.path); + const paths = await schematicsX.predicateGenerateFilePaths(context, PATH); + expect(paths).toEqual([ + '/projects/app/src/app/_shared/components/expected/expected.module.ts', + ]); + }); + }) + + +}); diff --git a/libs/schematics/src/gpt3/schematics-x.ts b/libs/schematics/src/gpt3/schematics-x.ts new file mode 100644 index 0000000..f8dc68c --- /dev/null +++ b/libs/schematics/src/gpt3/schematics-x.ts @@ -0,0 +1,82 @@ +import { FileEntry } from "@angular-devkit/schematics"; +import { join, parse, resolve } from "path"; +import { getEstimateSimilarFilePaths } from "./helpers"; +import { JsonPrompter, OpenAiPrompter } from "./prompter"; + + +export class SchematicsX { + + async generate(files: FileEntry[], path: string): Promise { + const generateFilePaths = await this.getGenerateFilePaths(files.map(file => file.path), path); + console.log('Estimated => ', generateFilePaths); + + return Promise.all(generateFilePaths.map(filePath => { + return this.generateFileEntry(filePath, files); + })); + } + + async generateFileEntry(path: string, files: FileEntry[] = []): Promise { + const similarFilePaths = getEstimateSimilarFilePaths(path, files.map(file => file.path)); + const fileEntries = files.filter(file => similarFilePaths.includes(file.path)); + console.log(`Estimating generate content of '${path}' by`, similarFilePaths, '...'); + return this._generateFileEntry(path, fileEntries); + } + + async _generateFileEntry(path: string, fileEntries: FileEntry[]): Promise { + const prompter = new OpenAiPrompter(makeFilePrompt(fileEntries, path)); + await prompter.autoWriteUntilEnd(); + return prompter.getFileEntry(path); + } + + async getGenerateFilePaths(filePaths: string[], generatePath: string): Promise { + if (parse(generatePath).ext.length) { + return [generatePath]; + } else { + console.log('Estimating generate files...'); + return this.predicateGenerateFilePaths(filePaths, generatePath); + }; + } + + async predicateGenerateFilePaths(filePaths: string[], generatePath: string): Promise { + const baseDir = getBasePath(filePaths); + const _filePaths = filePaths.map(path => path.replace(baseDir, '')); + const _generatePath = generatePath.replace(baseDir, ''); + const paths = await this._predicateGenerateFilePaths(_filePaths, _generatePath); + return paths.map(path => join(baseDir, path)); + } + + private async _predicateGenerateFilePaths(filePaths: string[], generatePath: string): Promise { + const parentDir = resolve(generatePath, '..'); + const prompt = makeDirectoryPrompt(filePaths.filter(path => path.startsWith(parentDir)), generatePath); + const prompter = new JsonPrompter(prompt); + await prompter.autoWrite(); + const paths = prompter.getJsonFuzzy(); + return [...new Set(paths)].filter(path => path.startsWith(generatePath)); + } +} + +export function getBasePath(paths: string[]): string { + const basePath = paths.reduce((acc, path) => { + const pathParts = path.split('/'); + const accParts = acc.split('/'); + const parts = pathParts.filter((part, i) => part === accParts[i]); + return parts.join('/'); + }, paths[0]); + return basePath; +} + +function makeDirectoryPrompt(files: string[], name: string): string { + return `\`tree.json\` has 30 lines.\n\n\`\`\`tree.json\n[\n${files.map(file => `\t\"${file}\",`).join('\n')}\n\t\"${name}`; +} + +function makeFilePrompt(files: FileEntry[], path: string): string { + return `${makeFileCodeBlocks(files)}\n\n\`\`\`${path}\n`; +} + +function makeFileCodeBlocks(files: FileEntry[]): string { + return files.map(file => makeFileCodeBlock(file.path, file.content.toString())).join('\n\n'); +} + +function makeFileCodeBlock(name: string, content: string): string { + return `\`\`\`${name}\n${content}\n\`\`\``; +} \ No newline at end of file From e7887cbbd7c356bc069d61cce002d7c23aff6813 Mon Sep 17 00:00:00 2001 From: nontangent Date: Sat, 3 Dec 2022 01:09:50 +0900 Subject: [PATCH 4/4] fix(schematics): change log message --- libs/schematics/src/gpt3/schematics-x.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/schematics/src/gpt3/schematics-x.ts b/libs/schematics/src/gpt3/schematics-x.ts index f8dc68c..c70c503 100644 --- a/libs/schematics/src/gpt3/schematics-x.ts +++ b/libs/schematics/src/gpt3/schematics-x.ts @@ -8,7 +8,7 @@ export class SchematicsX { async generate(files: FileEntry[], path: string): Promise { const generateFilePaths = await this.getGenerateFilePaths(files.map(file => file.path), path); - console.log('Estimated => ', generateFilePaths); + console.log('Estimated! => ', generateFilePaths, '\n'); return Promise.all(generateFilePaths.map(filePath => { return this.generateFileEntry(filePath, files); @@ -18,7 +18,7 @@ export class SchematicsX { async generateFileEntry(path: string, files: FileEntry[] = []): Promise { const similarFilePaths = getEstimateSimilarFilePaths(path, files.map(file => file.path)); const fileEntries = files.filter(file => similarFilePaths.includes(file.path)); - console.log(`Estimating generate content of '${path}' by`, similarFilePaths, '...'); + console.log(`Estimating content of '${path}' by`, similarFilePaths, '...\n'); return this._generateFileEntry(path, fileEntries); } @@ -32,7 +32,7 @@ export class SchematicsX { if (parse(generatePath).ext.length) { return [generatePath]; } else { - console.log('Estimating generate files...'); + console.log('Estimating the paths of files to be generated...\n'); return this.predicateGenerateFilePaths(filePaths, generatePath); }; }