@jpetit/toolkit 3.0.23 → 3.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/prompts/creators/create-solution.tpl.txt +10 -0
- package/assets/prompts/creators/create-statement.tpl.txt +21 -0
- package/assets/prompts/creators/create-translation.tpl.txt +5 -0
- package/assets/prompts/creators/private-test-cases.txt +6 -0
- package/assets/prompts/creators/sample-test-cases.txt +6 -0
- package/assets/prompts/creators/system-prompt.txt +2 -0
- package/assets/prompts/examples/statement-coda.tex +7 -0
- package/assets/prompts/examples/statement.tex +19 -0
- package/assets/prompts/generators/efficiency.md +41 -0
- package/assets/prompts/generators/hard.md +47 -0
- package/assets/prompts/generators/random.md +39 -0
- package/assets/prompts/proglangs/cc.md +3 -0
- package/assets/prompts/proglangs/py.md +40 -0
- package/lib/ai.ts +60 -4
- package/lib/cleaner.ts +24 -13
- package/lib/compilers/base.ts +70 -14
- package/lib/compilers/clojure.ts +21 -10
- package/lib/compilers/gcc.ts +4 -33
- package/lib/compilers/ghc.ts +4 -40
- package/lib/compilers/gxx.ts +4 -33
- package/lib/compilers/index.ts +9 -0
- package/lib/compilers/java.ts +105 -0
- package/lib/compilers/python3.ts +44 -37
- package/lib/compilers/run-clojure.ts +101 -0
- package/lib/compilers/run-haskell.ts +26 -22
- package/lib/compilers/run-python.ts +29 -35
- package/lib/compilers/rust.ts +39 -0
- package/lib/create-with-jutgeai.ts +407 -0
- package/lib/create-with-template.ts +55 -0
- package/lib/data.ts +6 -0
- package/lib/doctor.ts +86 -6
- package/lib/generate.ts +132 -290
- package/lib/helpers.ts +48 -0
- package/lib/inspector.ts +253 -0
- package/lib/jutge_api_client.ts +4631 -0
- package/lib/maker.ts +202 -289
- package/lib/settings.ts +26 -17
- package/lib/tui.ts +25 -15
- package/lib/types.ts +40 -5
- package/lib/upload.ts +216 -0
- package/lib/utils.ts +82 -14
- package/lib/versions.ts +46 -0
- package/package.json +50 -11
- package/toolkit/about.ts +43 -0
- package/toolkit/ai.ts +44 -18
- package/toolkit/check.ts +16 -0
- package/toolkit/clean.ts +16 -26
- package/toolkit/compilers.ts +4 -4
- package/toolkit/config.ts +91 -0
- package/toolkit/create.ts +30 -58
- package/toolkit/doctor.ts +15 -11
- package/toolkit/generate.ts +195 -98
- package/toolkit/index.ts +32 -21
- package/toolkit/make.ts +12 -48
- package/toolkit/upgrade.ts +9 -0
- package/toolkit/upload.ts +19 -0
- package/toolkit/create-jutge-ai.ts +0 -101
- package/toolkit/create-template.ts +0 -55
- package/toolkit/create-wizard.ts +0 -6
- package/toolkit/init.ts +0 -56
- package/toolkit/verify.ts +0 -19
package/lib/doctor.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import tui from './tui'
|
|
2
1
|
import { execa } from 'execa'
|
|
3
|
-
import { nothing } from './utils'
|
|
4
2
|
import terminalLink from 'terminal-link'
|
|
3
|
+
import tui from './tui'
|
|
4
|
+
import { nothing } from './utils'
|
|
5
5
|
|
|
6
6
|
export async function probePython3(showInfo: boolean = false): Promise<boolean> {
|
|
7
7
|
if (showInfo) tui.command('python3 --version')
|
|
@@ -32,6 +32,38 @@ export async function probeGCC(showInfo: boolean = false): Promise<boolean> {
|
|
|
32
32
|
return stdout.startsWith('Apple clang') || stdout.startsWith('g++')
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
export async function probeHaskell(showInfo: boolean = false): Promise<boolean> {
|
|
36
|
+
if (showInfo) tui.command('ghc --version')
|
|
37
|
+
const { stdout } = await execa({ reject: false })`ghc --version`
|
|
38
|
+
const version = stdout.split('\n')[0]!.trim()
|
|
39
|
+
if (showInfo) console.log(version)
|
|
40
|
+
return stdout.startsWith('The Glorious Glasgow Haskell Compilation System')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function probeClojure(showInfo: boolean = false): Promise<boolean> {
|
|
44
|
+
if (showInfo) tui.command('clj --version')
|
|
45
|
+
const { stdout } = await execa({ reject: false })`clj --version`
|
|
46
|
+
const version = stdout.split('\n')[0]!.trim()
|
|
47
|
+
if (showInfo) console.log(version)
|
|
48
|
+
return stdout.startsWith('Clojure')
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function probeJava(showInfo: boolean = false): Promise<boolean> {
|
|
52
|
+
if (showInfo) tui.command('javac -version')
|
|
53
|
+
const { stdout } = await execa({ reject: false })`javac -version`
|
|
54
|
+
const version = stdout.split('\n')[0]!.trim()
|
|
55
|
+
if (showInfo) console.log(version)
|
|
56
|
+
return stdout.startsWith('javac')
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function probeRust(showInfo: boolean = false): Promise<boolean> {
|
|
60
|
+
if (showInfo) tui.command('rustc --version')
|
|
61
|
+
const { stdout } = await execa({ reject: false })`rustc --version`
|
|
62
|
+
const version = stdout.split('\n')[0]!.trim()
|
|
63
|
+
if (showInfo) console.log(version)
|
|
64
|
+
return stdout.startsWith('rustc')
|
|
65
|
+
}
|
|
66
|
+
|
|
35
67
|
export async function probePdfLaTeX(showInfo: boolean = false): Promise<boolean> {
|
|
36
68
|
if (showInfo) tui.command('pdflatex --version')
|
|
37
69
|
const { stdout } = await execa({ reject: false })`pdflatex --version`
|
|
@@ -77,7 +109,6 @@ export async function checkPython3(): Promise<void> {
|
|
|
77
109
|
tui.print('See https://www.python.org/downloads/')
|
|
78
110
|
}
|
|
79
111
|
}
|
|
80
|
-
|
|
81
112
|
export async function checkGCC(): Promise<void> {
|
|
82
113
|
if (await probeGCC(true)) {
|
|
83
114
|
tui.success('C/C++ seems installed')
|
|
@@ -88,6 +119,46 @@ export async function checkGCC(): Promise<void> {
|
|
|
88
119
|
}
|
|
89
120
|
}
|
|
90
121
|
|
|
122
|
+
export async function checkHaskell(): Promise<void> {
|
|
123
|
+
if (await probeHaskell(true)) {
|
|
124
|
+
tui.success('Haskell seems installed')
|
|
125
|
+
} else {
|
|
126
|
+
tui.warning('Haskell does not appear to be installed')
|
|
127
|
+
tui.print('This is not a problem if you do not plan to use Haskell solutions')
|
|
128
|
+
tui.print('See https://www.haskell.org/ghc/download.html')
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export async function checkClojure(): Promise<void> {
|
|
133
|
+
if (await probeClojure(true)) {
|
|
134
|
+
tui.success('Clojure seems installed')
|
|
135
|
+
} else {
|
|
136
|
+
tui.warning('Clojure does not appear to be installed')
|
|
137
|
+
tui.print('This is not a problem if you do not plan to use Clojure solutions')
|
|
138
|
+
tui.print('See https://clojure.org/guides/getting_started')
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export async function checkJava(): Promise<void> {
|
|
143
|
+
if (await probeJava(true)) {
|
|
144
|
+
tui.success('Java seems installed')
|
|
145
|
+
} else {
|
|
146
|
+
tui.warning('Java does not appear to be installed')
|
|
147
|
+
tui.print('You will not be able to compile/execute Java solutions')
|
|
148
|
+
tui.print('See https://www.oracle.com/java/technologies/javase-jdk11-downloads.html')
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export async function checkRust(): Promise<void> {
|
|
153
|
+
if (await probeRust(true)) {
|
|
154
|
+
tui.success('Rust seems installed')
|
|
155
|
+
} else {
|
|
156
|
+
tui.warning('Rust does not appear to be installed')
|
|
157
|
+
tui.print('You will not be able to compile/execute Rust solutions')
|
|
158
|
+
tui.print('See https://www.rust-lang.org/tools/install')
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
91
162
|
export async function checkPdfLaTeX(): Promise<void> {
|
|
92
163
|
if (await probePdfLaTeX(true)) {
|
|
93
164
|
tui.success('LaTeX seems installed')
|
|
@@ -128,14 +199,14 @@ export async function checkImageMagick(): Promise<void> {
|
|
|
128
199
|
}
|
|
129
200
|
}
|
|
130
201
|
|
|
131
|
-
export async function
|
|
202
|
+
export async function checkAIEnvVars(): Promise<void> {
|
|
132
203
|
await nothing()
|
|
133
204
|
const vars = ['OPENAI_API_KEY', 'GEMINI_API_KEY']
|
|
134
205
|
for (const v of vars) {
|
|
135
206
|
if (process.env[v]) {
|
|
136
|
-
tui.success(`${v} is set`)
|
|
207
|
+
tui.success(`${v} environment variable is set`)
|
|
137
208
|
} else {
|
|
138
|
-
tui.warning(`${v} is not set`)
|
|
209
|
+
tui.warning(`${v} environment variable is not set`)
|
|
139
210
|
tui.print(`You will not be able to use related AI models`)
|
|
140
211
|
}
|
|
141
212
|
}
|
|
@@ -155,4 +226,13 @@ export async function checkTerminal(): Promise<void> {
|
|
|
155
226
|
tui.warning('Terminal does not support hyperlinks')
|
|
156
227
|
tui.print('Links to files may not be clickable')
|
|
157
228
|
}
|
|
229
|
+
|
|
230
|
+
const vars = ['EDITOR', 'VISUAL']
|
|
231
|
+
for (const v of vars) {
|
|
232
|
+
if (process.env[v]) {
|
|
233
|
+
tui.success(`${v} environment variable is set to "${process.env[v]}"`)
|
|
234
|
+
} else {
|
|
235
|
+
tui.warning(`${v} environment variable is not set`)
|
|
236
|
+
}
|
|
237
|
+
}
|
|
158
238
|
}
|
package/lib/generate.ts
CHANGED
|
@@ -1,329 +1,171 @@
|
|
|
1
|
-
import
|
|
2
|
-
import tui from './tui'
|
|
3
|
-
import chalk from 'chalk'
|
|
4
|
-
import { appendFile, mkdir } from 'fs/promises'
|
|
5
|
-
import { oraPromise } from 'ora'
|
|
1
|
+
import Handlebars from 'handlebars'
|
|
6
2
|
import { join } from 'path'
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
export const ZProblemData = z.object({
|
|
17
|
-
title: z.string(),
|
|
18
|
-
description: z.string(),
|
|
19
|
-
author: z.string(),
|
|
20
|
-
email: z.string().email(),
|
|
21
|
-
type: z.string(),
|
|
22
|
-
proglangs: z.array(z.string()),
|
|
23
|
-
languages: z.array(z.string()),
|
|
24
|
-
model: z.string(),
|
|
25
|
-
outputDir: z.string(),
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
export type ProblemData = z.infer<typeof ZProblemData>
|
|
29
|
-
|
|
30
|
-
export async function generateProblemWithJutgeAI(info: ProblemData): Promise<void> {
|
|
31
|
-
const generator = new ProblemGenerator(info)
|
|
32
|
-
await generator.run()
|
|
33
|
-
await generator.save(info.outputDir)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
class ProblemGenerator {
|
|
37
|
-
private bot: ChatBot
|
|
38
|
-
private info: ProblemData
|
|
39
|
-
|
|
40
|
-
// generated problem parts
|
|
41
|
-
private problemStatement: string = ''
|
|
42
|
-
private problemSampleTests: string = ''
|
|
43
|
-
private problemPrivateTests: string = ''
|
|
44
|
-
private problemSolutions: Record<string, string> = {}
|
|
45
|
-
private problemTranslations: Record<string, string> = {}
|
|
46
|
-
private problemReadme: string = ''
|
|
47
|
-
|
|
48
|
-
private latexExample = `
|
|
49
|
-
\\Problem{Which one is missing?}
|
|
50
|
-
|
|
51
|
-
\\Statement
|
|
52
|
-
|
|
53
|
-
Johny has a list of objects, labeled between 1 and $n$, but he lost one of them. He wants to know which one is missing.
|
|
54
|
-
|
|
55
|
-
\\medskip
|
|
56
|
-
|
|
57
|
-
Write a program that reads sequences with all the numbers between 1 and $n$ but one, and tells which one is missing.
|
|
58
|
-
|
|
59
|
-
\\Input
|
|
60
|
-
|
|
61
|
-
Input consists of several sequences.
|
|
62
|
-
Every sequence begins with a number $n$ between~1 and~$10^4$ followed by $n - 1$ natural numbers.
|
|
63
|
-
Every number between 1 and $n$ appears exactly once, except one of them, which is missing.
|
|
64
|
-
|
|
65
|
-
\\Output
|
|
66
|
-
|
|
67
|
-
For every sequence, print the missing number.
|
|
68
|
-
`
|
|
69
|
-
|
|
70
|
-
private systemPrompt = `
|
|
71
|
-
You are an expert in coding and writing programmming problems.
|
|
72
|
-
You provide accurate information and write following the instructed format.
|
|
73
|
-
`
|
|
74
|
-
|
|
75
|
-
private statementCoda = `
|
|
76
|
-
|
|
77
|
-
\\Observation
|
|
78
|
-
|
|
79
|
-
This problem has been generated by Jutge$^{\\text{AI}}$. Remove this observation after reviewing it carefully.
|
|
3
|
+
import { cleanMardownCodeString, complete } from './ai'
|
|
4
|
+
import { languageKeys, languageNames, proglangExtensions, proglangKeys, proglangNames } from './data'
|
|
5
|
+
import { getPromptForProglang, getTitleFromStatement } from './helpers'
|
|
6
|
+
import type { Inspector } from './inspector'
|
|
7
|
+
import { settings } from './settings'
|
|
8
|
+
import tui from './tui'
|
|
9
|
+
import { projectDir, readText, readTextInDir, stripLaTeX, writeTextInDir, writeYamlInDir } from './utils'
|
|
10
|
+
import { exists } from 'fs/promises'
|
|
80
11
|
|
|
81
|
-
|
|
12
|
+
export async function addStatementTranslation(model: string, inspector: Inspector, language: string) {
|
|
13
|
+
const original = inspector.originalLanguage
|
|
82
14
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
15
|
+
if (!original) {
|
|
16
|
+
throw new Error('Original language not set, cannot generate translations')
|
|
17
|
+
}
|
|
18
|
+
if (language === original) {
|
|
19
|
+
throw new Error(`Language ${language} is the original language`)
|
|
20
|
+
}
|
|
21
|
+
if (inspector.languages.includes(language)) {
|
|
22
|
+
throw new Error(`Language ${language} already exists`)
|
|
23
|
+
}
|
|
24
|
+
if (!languageKeys.includes(language)) {
|
|
25
|
+
throw new Error(`Language ${language} is not supported`)
|
|
87
26
|
}
|
|
88
27
|
|
|
89
|
-
|
|
90
|
-
return await tui.section('Generating problem statement', async () => {
|
|
91
|
-
const statementPrompt = `
|
|
92
|
-
You are to write the statement of a programming problem
|
|
93
|
-
|
|
94
|
-
The statement must be written in LaTeX, using a few predefined macros.
|
|
95
|
-
Use LaTeX math syntax for formulas and variables.
|
|
96
|
-
Use dollars for inline maths and use \\[ and \\] for display maths.
|
|
97
|
-
Do not add input/output test cases in the statement.
|
|
98
|
-
Separate paragraphs by a blank line and \\medskip macro.
|
|
99
|
-
|
|
100
|
-
Write in the style of Salvador Roura, the famous problem setter from Jutge.org.
|
|
101
|
-
|
|
102
|
-
Here is an example for an unrelated problem, follow its structure and macros:
|
|
103
|
-
|
|
104
|
-
${this.latexExample}
|
|
105
|
-
|
|
106
|
-
The title for this problem is:
|
|
28
|
+
const statememt = await readTextInDir(inspector.directory, `problem.${original}.tex`)
|
|
107
29
|
|
|
108
|
-
${
|
|
30
|
+
await tui.section(`Translating from ${languageNames[original]} to ${languageNames[language]}`, async () => {
|
|
31
|
+
const prompt = `
|
|
32
|
+
Translate the given problem statement to ${languageNames[language]}.
|
|
109
33
|
|
|
110
|
-
|
|
34
|
+
The translation must be accurate and use proper technical terminology.
|
|
35
|
+
Maintain the LaTeX formatting and macros.
|
|
36
|
+
The texts that the program must read and write should not be translated.
|
|
37
|
+
`
|
|
38
|
+
|
|
39
|
+
const answer = cleanMardownCodeString(await complete(model, prompt, statememt))
|
|
40
|
+
|
|
41
|
+
const info = {
|
|
42
|
+
title: getTitleFromStatement(answer) || 'Error translating title',
|
|
43
|
+
translator: settings.name,
|
|
44
|
+
translator_email: settings.email,
|
|
45
|
+
model,
|
|
46
|
+
}
|
|
47
|
+
await writeTextInDir(inspector.directory, `problem.${language}.tex`, answer)
|
|
48
|
+
await writeYamlInDir(inspector.directory, `problem.${language}.yml`, info)
|
|
49
|
+
|
|
50
|
+
tui.success(`Translation completed: files problem.${language}.tex and problem.${language}.yml created`)
|
|
51
|
+
tui.warning(`Please review the generated translation`)
|
|
52
|
+
})
|
|
53
|
+
}
|
|
111
54
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
return answer
|
|
116
|
-
})
|
|
55
|
+
export async function addAlternativeSolution(model: string, inspector: Inspector, extension: string) {
|
|
56
|
+
if (!inspector.goldenSolution) {
|
|
57
|
+
throw new Error('No golden solution defined')
|
|
117
58
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const sampleTestCasesPrompt = `
|
|
122
|
-
Now, write a sample test case file to illustrate the input that the program must read according to the problem statement.
|
|
123
|
-
|
|
124
|
-
Only the input should be given, the output will be computed from it.
|
|
125
|
-
Sample test cases should be relatively small but cover interesting cases but not all edge cases.
|
|
126
|
-
`
|
|
127
|
-
const answer = cleanMardownCodeString(await this.complete(sampleTestCasesPrompt))
|
|
128
|
-
return answer
|
|
129
|
-
})
|
|
59
|
+
const originalExtension = inspector.goldenSolution.split('.').pop()
|
|
60
|
+
if (!originalExtension) {
|
|
61
|
+
throw new Error('Invalid golden solution filename')
|
|
130
62
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
Only the input should be given, the output will be computed from it.
|
|
138
|
-
The private test cases should cover edge cases.
|
|
139
|
-
Limit the number of test cases to 20.
|
|
140
|
-
`
|
|
141
|
-
const answer = cleanMardownCodeString(await this.complete(privateTestCasesPrompt))
|
|
142
|
-
return answer
|
|
143
|
-
})
|
|
63
|
+
if (!proglangKeys.includes(extension)) {
|
|
64
|
+
throw new Error(`Extension ${extension} is not supported`)
|
|
65
|
+
}
|
|
66
|
+
if (inspector.solutions.includes(`solution.${extension}`)) {
|
|
67
|
+
throw new Error(`Solution already exists`)
|
|
144
68
|
}
|
|
69
|
+
const proglang = proglangNames[extension]
|
|
145
70
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const solutions: Record<string, string> = {}
|
|
149
|
-
for (const proglang of this.info.proglangs) {
|
|
150
|
-
await tui.section(`Generating solution in ${proglangNames[proglang]}`, async () => {
|
|
151
|
-
const solutionPrompt = `
|
|
152
|
-
Now, write a solution in ${proglangNames[proglang]} to solve this problem
|
|
153
|
-
|
|
154
|
-
It must be written in an idiomatic way and only include relevant comments.
|
|
155
|
-
The code must be efficient and handle all edge cases.
|
|
156
|
-
The code must read from standard input and write to standard output.
|
|
157
|
-
The code must be well written and easy to understand to novices.
|
|
158
|
-
The code does not have to check the preconditions stated in the probleM
|
|
159
|
-
Do not use any non-standard libraries.
|
|
160
|
-
${proglang === 'cpp' ? 'Do not use fast input/output methods.' : ''}
|
|
161
|
-
${proglang === 'cpp' ? 'Add a using namespace std; declaration after the includes and do not use std:: prefixes.' : ''}
|
|
162
|
-
${proglang === 'py' ? 'Use type annotations.' : ''}
|
|
163
|
-
`
|
|
164
|
-
const answer = cleanMardownCodeString(await this.complete(solutionPrompt))
|
|
165
|
-
this.bot.forgetLastInteraction()
|
|
166
|
-
solutions[proglang] = answer
|
|
167
|
-
})
|
|
168
|
-
}
|
|
169
|
-
return solutions
|
|
170
|
-
})
|
|
71
|
+
if (!proglang) {
|
|
72
|
+
throw new Error(`Programming language for extension ${extension} not found`)
|
|
171
73
|
}
|
|
172
74
|
|
|
173
|
-
|
|
174
|
-
return await tui.section('Translating problem statements', async () => {
|
|
175
|
-
const translations: Record<string, string> = {}
|
|
176
|
-
for (const language of this.info.languages) {
|
|
177
|
-
await tui.section(`Translating to ${languageNames[language]}`, async () => {
|
|
178
|
-
const translationPrompt = `
|
|
179
|
-
Now, translate the problem statement to ${languageNames[language]}.
|
|
75
|
+
const originalProglang = proglangNames[originalExtension]
|
|
180
76
|
|
|
181
|
-
|
|
182
|
-
Maintain the LaTeX formatting and macros.
|
|
183
|
-
The texts that the program must read and write should not be translated.
|
|
184
|
-
`
|
|
185
|
-
const answer = cleanMardownCodeString(await this.complete(translationPrompt)) + this.statementCoda
|
|
186
|
-
this.bot.forgetLastInteraction()
|
|
187
|
-
translations[language] = answer
|
|
188
|
-
})
|
|
189
|
-
}
|
|
190
|
-
return translations
|
|
191
|
-
})
|
|
192
|
-
}
|
|
77
|
+
const goldenSource = await readTextInDir(inspector.directory, inspector.goldenSolution)
|
|
193
78
|
|
|
194
|
-
|
|
195
|
-
return tui.section('Generating README.md', async () => {
|
|
196
|
-
await nothing()
|
|
197
|
-
const readme = `
|
|
198
|
-
# ${this.info.title}
|
|
79
|
+
const proglangPrompt = await getPromptForProglang(proglang)
|
|
199
80
|
|
|
200
|
-
|
|
81
|
+
const prompt = `
|
|
82
|
+
Convert the given program in ${originalProglang} to ${proglang}.
|
|
201
83
|
|
|
202
|
-
|
|
84
|
+
The translation must be accurate and follow the structure and logic of the original program.
|
|
85
|
+
Each function should have a documentation comment explaining its purpose, parameters, and return values.
|
|
203
86
|
|
|
204
|
-
|
|
87
|
+
If some function in the original program is not defined, do not define it in the translated program either.
|
|
205
88
|
|
|
206
|
-
${
|
|
89
|
+
Your program should start with a comment line saying 'Generated by ${model}'.
|
|
207
90
|
|
|
208
|
-
|
|
91
|
+
${proglangPrompt}
|
|
92
|
+
`
|
|
209
93
|
|
|
210
|
-
|
|
94
|
+
const answer = cleanMardownCodeString(await complete(model, prompt, goldenSource))
|
|
211
95
|
|
|
212
|
-
|
|
96
|
+
await writeTextInDir(inspector.directory, `solution.${extension}`, answer)
|
|
213
97
|
|
|
214
|
-
|
|
98
|
+
tui.success(`solution.${extension} created`)
|
|
99
|
+
tui.warning(`Please review the generated solution`)
|
|
100
|
+
}
|
|
215
101
|
|
|
216
|
-
|
|
102
|
+
// TODO: check this function
|
|
103
|
+
export async function addMainFile(model: string, inspector: Inspector, proglang: string) {
|
|
104
|
+
if (!inspector.goldenSolution) {
|
|
105
|
+
throw new Error('No golden solution defined')
|
|
106
|
+
}
|
|
217
107
|
|
|
218
|
-
|
|
219
|
-
|
|
108
|
+
const originalExtension = inspector.goldenSolution.split('.').pop()
|
|
109
|
+
if (!originalExtension) {
|
|
110
|
+
throw new Error('Invalid golden solution filename')
|
|
111
|
+
}
|
|
112
|
+
const originalProglang = proglangNames[originalExtension]
|
|
220
113
|
|
|
221
|
-
|
|
114
|
+
const goldenSource = await readTextInDir(inspector.directory, `main.${originalExtension}`)
|
|
222
115
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
\`\`\`
|
|
116
|
+
await tui.section(`Converting main.${originalExtension} to ${proglang}`, async () => {
|
|
117
|
+
const proglangPrompt = await getPromptForProglang(proglang)
|
|
226
118
|
|
|
227
|
-
|
|
119
|
+
const prompt = `
|
|
120
|
+
Convert the given program in ${originalProglang} to ${proglang}.
|
|
228
121
|
|
|
229
|
-
The
|
|
122
|
+
The translation must be accurate and follow the structure and logic of the original program.
|
|
230
123
|
|
|
231
|
-
|
|
232
|
-
- Total output tokens: ${this.bot.totalOutputTokens}
|
|
233
|
-
- Total input cost: ${this.bot.totalInputCost.toFixed(6)} USD
|
|
234
|
-
- Total output cost: ${this.bot.totalOutputCost.toFixed(6)} USD
|
|
235
|
-
- Total estimated cost: ${(this.bot.totalInputCost + this.bot.totalOutputCost).toFixed(6)} USD
|
|
236
|
-
- Energy: ${estimatePowerConsumption(this.bot.totalInputTokens, this.bot.totalOutputTokens).wattHours.toFixed(6)} Wh
|
|
237
|
-
- CO₂ emissions: ${estimatePowerConsumption(this.bot.totalInputTokens, this.bot.totalOutputTokens).co2Grams.toFixed(6)} g CO₂
|
|
124
|
+
If some function in the original program is not defined, do not define it in the translated program either.
|
|
238
125
|
|
|
239
|
-
|
|
240
|
-
return readme
|
|
241
|
-
})
|
|
242
|
-
}
|
|
126
|
+
Your program should start with a comment line saying 'Generated by ${model}'.
|
|
243
127
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
await this.bot.init()
|
|
247
|
-
this.problemStatement = await this.generateStatement()
|
|
248
|
-
this.problemSampleTests = await this.generateSampleTests()
|
|
249
|
-
this.problemPrivateTests = await this.generatePrivateTests()
|
|
250
|
-
this.problemSolutions = await this.generateSolutions() // these are forgotten inside
|
|
251
|
-
this.bot.forgetLastInteraction() // forget private tests
|
|
252
|
-
this.bot.forgetLastInteraction() // forget sample tests
|
|
253
|
-
this.problemTranslations = await this.translateStatements()
|
|
254
|
-
this.problemReadme = await this.generateReadme()
|
|
255
|
-
}
|
|
128
|
+
${proglangPrompt}
|
|
129
|
+
`
|
|
256
130
|
|
|
257
|
-
|
|
258
|
-
await tui.section(`Saving problem to ${path}`, async () => {
|
|
259
|
-
await mkdir(path, { recursive: true })
|
|
260
|
-
await writeText(join(path, 'problem.en.tex'), this.problemStatement)
|
|
261
|
-
|
|
262
|
-
const yml = {
|
|
263
|
-
title: this.info.title,
|
|
264
|
-
author: this.info.author,
|
|
265
|
-
email: this.info.email,
|
|
266
|
-
model: this.info.model,
|
|
267
|
-
}
|
|
268
|
-
await writeYaml(`${path}/problem.en.yml`, yml)
|
|
269
|
-
|
|
270
|
-
for (const [lang, translation] of Object.entries(this.problemTranslations)) {
|
|
271
|
-
await writeText(join(path, `problem.${lang}.tex`), translation)
|
|
272
|
-
const yml = {
|
|
273
|
-
title: getTitleFromStatement(translation) || this.info.title,
|
|
274
|
-
translator: this.info.author,
|
|
275
|
-
translator_email: this.info.email,
|
|
276
|
-
original_language: 'en',
|
|
277
|
-
model: this.info.model,
|
|
278
|
-
}
|
|
279
|
-
await writeYaml(join(path, `problem.${lang}.yml`), yml)
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
await writeText(join(path, 'sample.inp'), this.problemSampleTests)
|
|
283
|
-
|
|
284
|
-
await writeText(join(path, 'test.inp'), this.problemPrivateTests)
|
|
285
|
-
|
|
286
|
-
for (const [proglang, solution] of Object.entries(this.problemSolutions)) {
|
|
287
|
-
const ext = proglang
|
|
288
|
-
await writeText(join(path, `solution.${ext}`), solution)
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
const handlerYml: any = {
|
|
292
|
-
handler: 'std',
|
|
293
|
-
}
|
|
294
|
-
await writeYaml(join(path, 'handler.yml'), handlerYml)
|
|
295
|
-
|
|
296
|
-
await writeText(join(path, 'README.md'), this.problemReadme)
|
|
297
|
-
})
|
|
298
|
-
}
|
|
131
|
+
const answer = cleanMardownCodeString(await complete(model, prompt, goldenSource))
|
|
299
132
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
133
|
+
await writeTextInDir(inspector.directory, `main.${proglangExtensions[proglang]}`, answer)
|
|
134
|
+
|
|
135
|
+
tui.success(`main.${proglang} created`)
|
|
136
|
+
tui.warning(`Please review the generated file`)
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export async function generateTestCasesGenerator(
|
|
141
|
+
model: string,
|
|
142
|
+
inspector: Inspector,
|
|
143
|
+
output: string, // file to create (template with {{type}})
|
|
144
|
+
type: 'random' | 'hard' | 'efficiency',
|
|
145
|
+
) {
|
|
146
|
+
const outputPath = Handlebars.compile(output)({ type })
|
|
147
|
+
await tui.section(`Generating test cases generator ${outputPath}`, async () => {
|
|
148
|
+
const statement = await getStatementAsText(inspector)
|
|
149
|
+
const promptPath = join(projectDir(), 'assets', 'prompts', 'generators', `${type}.md`)
|
|
150
|
+
const promptTemplate = await readText(promptPath)
|
|
151
|
+
const prompt = Handlebars.compile(promptTemplate)({ statement })
|
|
152
|
+
const answer = cleanMardownCodeString(await complete(model, '', prompt))
|
|
153
|
+
await writeTextInDir(inspector.directory, outputPath, answer)
|
|
154
|
+
tui.success(`Test cases generator ${outputPath} generated`)
|
|
155
|
+
tui.warning(`Please review the generated test cases generator`)
|
|
156
|
+
})
|
|
320
157
|
}
|
|
321
158
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
const
|
|
325
|
-
if (
|
|
326
|
-
|
|
159
|
+
// TODO: move into inspector?
|
|
160
|
+
export async function getStatementAsText(inspector: Inspector): Promise<string> {
|
|
161
|
+
const original = inspector.originalLanguage
|
|
162
|
+
if (!original) throw new Error('Original language not set')
|
|
163
|
+
if (await exists(join(inspector.directory, `problem.${original}.txt`))) {
|
|
164
|
+
const text = await readTextInDir(inspector.directory, `problem.${original}.txt`)
|
|
165
|
+
return text
|
|
166
|
+
} else {
|
|
167
|
+
const latex = await readTextInDir(inspector.directory, `problem.${original}.tex`)
|
|
168
|
+
const text = stripLaTeX(latex)
|
|
169
|
+
return text
|
|
327
170
|
}
|
|
328
|
-
return null
|
|
329
171
|
}
|
package/lib/helpers.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { exists } from 'fs/promises'
|
|
2
|
+
import { projectDir, readText } from './utils'
|
|
3
|
+
import { join } from 'path'
|
|
4
|
+
import { normalize, resolve } from 'node:path'
|
|
5
|
+
import { languageNames } from './data'
|
|
6
|
+
|
|
7
|
+
export function getTitleFromStatement(statement: string): string | null {
|
|
8
|
+
const pattern = /\\Problem\{(.*?)\}/
|
|
9
|
+
const match = statement.match(pattern)
|
|
10
|
+
if (match) {
|
|
11
|
+
return match[1]!.trim()
|
|
12
|
+
}
|
|
13
|
+
return null
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function getPromptForProglang(proglang: string): Promise<string> {
|
|
17
|
+
const location = join(projectDir(), 'assets', 'prompts', `${proglang}.md`)
|
|
18
|
+
if (await exists(location)) {
|
|
19
|
+
return await readText(location)
|
|
20
|
+
} else {
|
|
21
|
+
return ''
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/*
|
|
26
|
+
Find real problem directories from a list of directories. A real problem directory is one that contains a handler.yml file.
|
|
27
|
+
If a directory does not contain a handler.yml file, check if it contains subdirectories for each language that contain a handler.yml file.
|
|
28
|
+
If so, add those subdirectories to the list of real problem directories.
|
|
29
|
+
*/
|
|
30
|
+
export async function findRealDirectories(directories: string[]): Promise<string[]> {
|
|
31
|
+
const realDirectories: string[] = []
|
|
32
|
+
for (let dir of directories) {
|
|
33
|
+
if (dir === '.') {
|
|
34
|
+
dir = normalize(resolve(process.cwd()))
|
|
35
|
+
}
|
|
36
|
+
if (await exists(join(dir, 'handler.yml'))) {
|
|
37
|
+
realDirectories.push(dir)
|
|
38
|
+
} else {
|
|
39
|
+
for (const language of Object.keys(languageNames).sort()) {
|
|
40
|
+
const child = join(dir, language, 'handler.yml')
|
|
41
|
+
if (await exists(child)) {
|
|
42
|
+
realDirectories.push(resolve(dir, language))
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return realDirectories
|
|
48
|
+
}
|