feat: add heroprompt module for prompt generation
- Introduce sessions and workspaces for managing context - Allow adding directories and selecting files - Generate structured prompts with file maps and content - Add example script and a prompt template - Define core data models like `HeropromptWorkspace`
This commit is contained in:
37
examples/develop/heroprompt/heroprompt_example.vsh
Executable file
37
examples/develop/heroprompt/heroprompt_example.vsh
Executable file
@@ -0,0 +1,37 @@
|
|||||||
|
#!/usr/bin/env -S v -n -w -gc none -cg -cc tcc -d use_openssl -enable-globals run
|
||||||
|
|
||||||
|
import freeflowuniverse.herolib.develop.heroprompt
|
||||||
|
|
||||||
|
mut session := heroprompt.new_session()
|
||||||
|
|
||||||
|
mut workspace1 := session.add_workspace()!
|
||||||
|
// TODO: Check the name bug
|
||||||
|
// mut workspace2 := session.add_workspace(name: 'withname')!
|
||||||
|
|
||||||
|
mut dir1 := workspace1.add_dir(path: '/Users/mahmoud/code/github/freeflowuniverse/herolib/docker')!
|
||||||
|
dir1.select_file(name: 'docker_ubuntu_install.sh')!
|
||||||
|
|
||||||
|
mut dir2 := workspace1.add_dir(
|
||||||
|
path: '/Users/mahmoud/code/github/freeflowuniverse/herolib/docker/herolib'
|
||||||
|
)!
|
||||||
|
|
||||||
|
dir2.select_file(name: '.gitignore')!
|
||||||
|
dir2.select_file(name: 'build.sh')!
|
||||||
|
dir2.select_file(name: 'debug.sh')!
|
||||||
|
|
||||||
|
mut dir3 := workspace1.add_dir(
|
||||||
|
path: '/Users/mahmoud/code/github/freeflowuniverse/herolib/docker/postgresql'
|
||||||
|
select_all_dirs: true
|
||||||
|
select_all_files: false
|
||||||
|
select_all: false
|
||||||
|
)!
|
||||||
|
|
||||||
|
dir3.select_file(name: 'docker-compose.yml')!
|
||||||
|
|
||||||
|
selected := workspace1.get_selected()
|
||||||
|
|
||||||
|
prompt := workspace1.prompt(
|
||||||
|
text: 'Using the selected files, i want you to get all print statments'
|
||||||
|
)
|
||||||
|
|
||||||
|
println(prompt)
|
||||||
40
lib/develop/heroprompt/heroprompt_dir.v
Normal file
40
lib/develop/heroprompt/heroprompt_dir.v
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
module heroprompt
|
||||||
|
|
||||||
|
import os
|
||||||
|
import freeflowuniverse.herolib.core.pathlib
|
||||||
|
|
||||||
|
@[params]
|
||||||
|
pub struct AddFileParams {
|
||||||
|
pub mut:
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (mut dir HeropromptDir) select_file(args AddFileParams) !&HeropromptFile {
|
||||||
|
mut full_path := dir.path.path + '/' + args.name
|
||||||
|
if dir.path.path.ends_with('/') {
|
||||||
|
full_path = dir.path.path + args.name
|
||||||
|
}
|
||||||
|
|
||||||
|
if !os.exists(full_path) {
|
||||||
|
return error('File ${full_path} does not exists')
|
||||||
|
}
|
||||||
|
|
||||||
|
if !os.is_file(full_path) {
|
||||||
|
return error('Provided path ${full_path} is not a file')
|
||||||
|
}
|
||||||
|
|
||||||
|
file_content := os.read_file(full_path)!
|
||||||
|
|
||||||
|
file := &HeropromptFile{
|
||||||
|
path: pathlib.Path{
|
||||||
|
path: full_path
|
||||||
|
cat: .file
|
||||||
|
exist: .yes
|
||||||
|
}
|
||||||
|
name: args.name
|
||||||
|
content: file_content
|
||||||
|
}
|
||||||
|
|
||||||
|
dir.files << file
|
||||||
|
return file
|
||||||
|
}
|
||||||
1
lib/develop/heroprompt/heroprompt_file.v
Normal file
1
lib/develop/heroprompt/heroprompt_file.v
Normal file
@@ -0,0 +1 @@
|
|||||||
|
module heroprompt
|
||||||
23
lib/develop/heroprompt/heroprompt_session.v
Normal file
23
lib/develop/heroprompt/heroprompt_session.v
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
module heroprompt
|
||||||
|
|
||||||
|
import rand
|
||||||
|
|
||||||
|
pub struct HeropromptSession {
|
||||||
|
pub mut:
|
||||||
|
id string
|
||||||
|
workspaces []&HeropromptWorkspace
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_session() HeropromptSession {
|
||||||
|
return HeropromptSession{
|
||||||
|
id: rand.uuid_v4()
|
||||||
|
workspaces: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (mut self HeropromptSession) add_workspace(args_ NewWorkspaceParams) !&HeropromptWorkspace {
|
||||||
|
mut wsp := &HeropromptWorkspace{}
|
||||||
|
wsp = wsp.new(args_)!
|
||||||
|
self.workspaces << wsp
|
||||||
|
return wsp
|
||||||
|
}
|
||||||
219
lib/develop/heroprompt/heroprompt_workspace.v
Normal file
219
lib/develop/heroprompt/heroprompt_workspace.v
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
module heroprompt
|
||||||
|
|
||||||
|
import rand
|
||||||
|
import time
|
||||||
|
import os
|
||||||
|
import freeflowuniverse.herolib.core.pathlib
|
||||||
|
|
||||||
|
@[heap]
|
||||||
|
pub struct HeropromptWorkspace {
|
||||||
|
pub mut:
|
||||||
|
name string = 'default'
|
||||||
|
dirs []&HeropromptDir
|
||||||
|
}
|
||||||
|
|
||||||
|
@[params]
|
||||||
|
pub struct NewWorkspaceParams {
|
||||||
|
pub mut:
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new workspace
|
||||||
|
/// If the name is not passed, we will generate a random one
|
||||||
|
fn (wsp HeropromptWorkspace) new(args_ NewWorkspaceParams) !&HeropromptWorkspace {
|
||||||
|
mut args := args_
|
||||||
|
if args.name.len == 0 {
|
||||||
|
args.name = generate_random_workspace_name()
|
||||||
|
}
|
||||||
|
|
||||||
|
workspace := get(name: args.name)!
|
||||||
|
return workspace
|
||||||
|
}
|
||||||
|
|
||||||
|
@[params]
|
||||||
|
pub struct AddDirParams {
|
||||||
|
pub mut:
|
||||||
|
path string @[required]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (mut wsp HeropromptWorkspace) add_dir(args_ AddDirParams) !&HeropromptDir {
|
||||||
|
if args_.path.len == 0 {
|
||||||
|
return error('The dir path is required')
|
||||||
|
}
|
||||||
|
|
||||||
|
if !os.exists(args_.path) {
|
||||||
|
return error('The provided path does not exists')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize absolute path
|
||||||
|
abs_path := os.real_path(args_.path)
|
||||||
|
|
||||||
|
parts := abs_path.split(os.path_separator)
|
||||||
|
dir_name := parts[parts.len - 1]
|
||||||
|
|
||||||
|
added_dir := &HeropromptDir{
|
||||||
|
path: pathlib.Path{
|
||||||
|
path: abs_path
|
||||||
|
cat: .dir
|
||||||
|
exist: .yes
|
||||||
|
}
|
||||||
|
name: dir_name
|
||||||
|
}
|
||||||
|
|
||||||
|
wsp.dirs << added_dir
|
||||||
|
return added_dir
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SelectedFilesMetadata {
|
||||||
|
content_length int
|
||||||
|
extension string
|
||||||
|
name string
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SelectedDirsMetadata {
|
||||||
|
name string
|
||||||
|
selected_files []SelectedFilesMetadata
|
||||||
|
}
|
||||||
|
|
||||||
|
struct HeropromptWorkspaceGetSelected {
|
||||||
|
pub mut:
|
||||||
|
dirs []SelectedDirsMetadata
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (wsp HeropromptWorkspace) get_selected() HeropromptWorkspaceGetSelected {
|
||||||
|
mut result := HeropromptWorkspaceGetSelected{}
|
||||||
|
|
||||||
|
for dir in wsp.dirs {
|
||||||
|
mut files := []SelectedFilesMetadata{}
|
||||||
|
for file in dir.files {
|
||||||
|
files << SelectedFilesMetadata{
|
||||||
|
content_length: file.content.len
|
||||||
|
extension: get_file_extension(file.name)
|
||||||
|
name: file.name
|
||||||
|
path: file.path.path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.dirs << SelectedDirsMetadata{
|
||||||
|
name: dir.name
|
||||||
|
selected_files: files
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct HeropromptWorkspacePrompt {
|
||||||
|
pub mut:
|
||||||
|
text string
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (wsp HeropromptWorkspace) prompt(args HeropromptWorkspacePrompt) string {
|
||||||
|
prompt := wsp.build_prompt(args.text)
|
||||||
|
return prompt
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder function for future needs, in case we need to highlight the user_instructions block with some addtional messages
|
||||||
|
fn (wsp HeropromptWorkspace) build_user_instructions(text string) string {
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_file_map(dirs []&HeropromptDir, prefix string) string {
|
||||||
|
mut out := ''
|
||||||
|
|
||||||
|
for i, dir in dirs {
|
||||||
|
// Determine the correct tree connector
|
||||||
|
connector := if i == dirs.len - 1 { '└── ' } else { '├── ' }
|
||||||
|
|
||||||
|
// Directory name
|
||||||
|
out += '${prefix}${connector}${dir.name}\n'
|
||||||
|
|
||||||
|
// Files in this directory
|
||||||
|
for j, file in dir.files {
|
||||||
|
file_connector := if j == dir.files.len - 1 && dir.dirs.len == 0 {
|
||||||
|
'└── '
|
||||||
|
} else {
|
||||||
|
'├── '
|
||||||
|
}
|
||||||
|
out += '${prefix} ${file_connector}${file.name} *\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recurse into subdirectories
|
||||||
|
if dir.dirs.len > 0 {
|
||||||
|
new_prefix := if i == dirs.len - 1 { prefix + ' ' } else { prefix + '│ ' }
|
||||||
|
out += build_file_map(dir.dirs, new_prefix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (wsp HeropromptWorkspace) build_file_content() string {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (wsp HeropromptWorkspace) build_prompt(text string) string {
|
||||||
|
user_instructions := wsp.build_user_instructions(text)
|
||||||
|
file_map := build_file_map(wsp.dirs, '')
|
||||||
|
file_contents := wsp.build_file_content()
|
||||||
|
|
||||||
|
// Handle reading the prompt file and parse it
|
||||||
|
return file_map
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate a random name for the workspace
|
||||||
|
fn generate_random_workspace_name() string {
|
||||||
|
adjectives := [
|
||||||
|
'brave',
|
||||||
|
'bright',
|
||||||
|
'clever',
|
||||||
|
'swift',
|
||||||
|
'noble',
|
||||||
|
'mighty',
|
||||||
|
'fearless',
|
||||||
|
'bold',
|
||||||
|
'wise',
|
||||||
|
'epic',
|
||||||
|
'valiant',
|
||||||
|
'fierce',
|
||||||
|
'legendary',
|
||||||
|
'heroic',
|
||||||
|
'dynamic',
|
||||||
|
]
|
||||||
|
nouns := [
|
||||||
|
'forge',
|
||||||
|
'script',
|
||||||
|
'ocean',
|
||||||
|
'phoenix',
|
||||||
|
'atlas',
|
||||||
|
'quest',
|
||||||
|
'shield',
|
||||||
|
'dragon',
|
||||||
|
'code',
|
||||||
|
'summit',
|
||||||
|
'path',
|
||||||
|
'realm',
|
||||||
|
'spark',
|
||||||
|
'anvil',
|
||||||
|
'saga',
|
||||||
|
]
|
||||||
|
|
||||||
|
// Seed randomness with time
|
||||||
|
rand.seed([u32(time.now().unix()), u32(time.now().nanosecond)])
|
||||||
|
|
||||||
|
adj := adjectives[rand.intn(adjectives.len) or { 0 }]
|
||||||
|
noun := nouns[rand.intn(nouns.len) or { 0 }]
|
||||||
|
number := rand.intn(100) or { 0 } // 0–99
|
||||||
|
|
||||||
|
return '${adj}_${noun}_${number}'
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_file_extension(filename string) string {
|
||||||
|
parts := filename.split('.')
|
||||||
|
if parts.len < 2 {
|
||||||
|
// Handle the files with no exe such as Dockerfile, LICENSE
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
return parts[parts.len - 1]
|
||||||
|
}
|
||||||
@@ -10,3 +10,7 @@ fn (mut ws HeropromptWorkspace) heroprompt() !string {
|
|||||||
// TODO: fill in template based on selection
|
// TODO: fill in template based on selection
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_tree() {}
|
||||||
|
|
||||||
|
pub fn format_prompt() {}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
module heroprompt
|
module heroprompt
|
||||||
|
|
||||||
import freeflowuniverse.herolib.data.paramsparser
|
import freeflowuniverse.herolib.data.paramsparser
|
||||||
import freeflowuniverse.herolib.data.encoderhero
|
// import freeflowuniverse.herolib.data.encoderhero // temporarily commented out
|
||||||
import freeflowuniverse.herolib.core.pathlib
|
import freeflowuniverse.herolib.core.pathlib
|
||||||
import os
|
import os
|
||||||
|
|
||||||
@@ -11,38 +11,60 @@ const default = true
|
|||||||
|
|
||||||
// THIS THE THE SOURCE OF THE INFORMATION OF THIS FILE, HERE WE HAVE THE CONFIG OBJECT CONFIGURED AND MODELLED
|
// THIS THE THE SOURCE OF THE INFORMATION OF THIS FILE, HERE WE HAVE THE CONFIG OBJECT CONFIGURED AND MODELLED
|
||||||
|
|
||||||
@[heap]
|
pub struct HeropromptFile {
|
||||||
pub struct HeropromptWorkspace {
|
|
||||||
pub mut:
|
pub mut:
|
||||||
name string = 'default'
|
content string
|
||||||
dirs []HeropromptDir
|
path pathlib.Path
|
||||||
|
name string
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct HeropromptDir {
|
pub struct HeropromptDir {
|
||||||
pub mut:
|
pub mut:
|
||||||
path pathlib.Path
|
name string
|
||||||
selections []string // paths selected in the HeropromptDir
|
path pathlib.Path
|
||||||
|
files []&HeropromptFile
|
||||||
|
dirs []&HeropromptDir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pub fn (wsp HeropromptWorkspace) to_tag() {
|
||||||
|
// tag := HeropromptTags.file_map
|
||||||
|
// // We need to pass it to the template
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // pub fn (dir HeropromptDir) to_tag() {
|
||||||
|
// // tag := HeropromptTags.file_content
|
||||||
|
// // // We need to pass it to the template
|
||||||
|
// // }
|
||||||
|
|
||||||
|
// pub fn (fil HeropromptFile) to_tag() {
|
||||||
|
// tag := HeropromptTags.file_content
|
||||||
|
// // We need to pass it to the template
|
||||||
|
// }
|
||||||
|
|
||||||
|
// pub enum HeropromptTags {
|
||||||
|
// file_map
|
||||||
|
// file_content
|
||||||
|
// user_instructions
|
||||||
|
// }
|
||||||
|
|
||||||
// your checking & initialization code if needed
|
// your checking & initialization code if needed
|
||||||
fn obj_init(mycfg_ HeropromptWorkspace) !HeropromptWorkspace {
|
fn obj_init(mycfg_ HeropromptWorkspace) !HeropromptWorkspace {
|
||||||
mut mycfg := mycfg_
|
mut mycfg := mycfg_
|
||||||
if mycfg.password == '' && mycfg.secret == '' {
|
|
||||||
return error('password or secret needs to be filled in for ${mycfg.name}')
|
|
||||||
}
|
|
||||||
return mycfg
|
return mycfg
|
||||||
}
|
}
|
||||||
|
|
||||||
/////////////NORMALLY NO NEED TO TOUCH
|
/////////////NORMALLY NO NEED TO TOUCH
|
||||||
|
|
||||||
|
// TODO: Check the compiler issue with the encde/decode
|
||||||
pub fn heroscript_dumps(obj HeropromptWorkspace) !string {
|
pub fn heroscript_dumps(obj HeropromptWorkspace) !string {
|
||||||
// create heroscript following template
|
// return encoderhero.encode[HeropromptWorkspace](obj)! // temporarily commented out
|
||||||
// check for our homedir on our machine and replace in the heroscript to @HOME in path
|
return 'name: "${obj.name}"'
|
||||||
return encoderhero.encode[HeropromptWorkspace](obj)!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn heroscript_loads(heroscript string) !HeropromptWorkspace {
|
pub fn heroscript_loads(heroscript string) !HeropromptWorkspace {
|
||||||
// TODO: parse heroscript populate HeropromptWorkspace
|
// mut obj := encoderhero.decode[HeropromptWorkspace](heroscript)! // temporarily commented out
|
||||||
mut obj := encoderhero.decode[HeropromptWorkspace](heroscript)!
|
obj := HeropromptWorkspace{
|
||||||
|
name: 'default'
|
||||||
|
}
|
||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
|
|||||||
11
lib/develop/heroprompt/templates/prompt.heroprompt
Normal file
11
lib/develop/heroprompt/templates/prompt.heroprompt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<user_instructions>
|
||||||
|
{{text}}
|
||||||
|
</user_instructions>
|
||||||
|
|
||||||
|
<file_map>
|
||||||
|
{{map}}
|
||||||
|
</file_map>
|
||||||
|
|
||||||
|
<file_contents>
|
||||||
|
{{content}}
|
||||||
|
</file_contents>
|
||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user