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:
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
|
||||
return ''
|
||||
}
|
||||
|
||||
pub fn get_tree() {}
|
||||
|
||||
pub fn format_prompt() {}
|
||||
@@ -1,7 +1,7 @@
|
||||
module heroprompt
|
||||
|
||||
import freeflowuniverse.herolib.data.paramsparser
|
||||
import freeflowuniverse.herolib.data.encoderhero
|
||||
// import freeflowuniverse.herolib.data.encoderhero // temporarily commented out
|
||||
import freeflowuniverse.herolib.core.pathlib
|
||||
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
|
||||
|
||||
@[heap]
|
||||
pub struct HeropromptWorkspace {
|
||||
pub struct HeropromptFile {
|
||||
pub mut:
|
||||
name string = 'default'
|
||||
dirs []HeropromptDir
|
||||
content string
|
||||
path pathlib.Path
|
||||
name string
|
||||
}
|
||||
|
||||
pub struct HeropromptDir {
|
||||
pub mut:
|
||||
path pathlib.Path
|
||||
selections []string // paths selected in the HeropromptDir
|
||||
name string
|
||||
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
|
||||
fn obj_init(mycfg_ HeropromptWorkspace) !HeropromptWorkspace {
|
||||
mut mycfg := mycfg_
|
||||
if mycfg.password == '' && mycfg.secret == '' {
|
||||
return error('password or secret needs to be filled in for ${mycfg.name}')
|
||||
}
|
||||
return mycfg
|
||||
}
|
||||
|
||||
/////////////NORMALLY NO NEED TO TOUCH
|
||||
|
||||
// TODO: Check the compiler issue with the encde/decode
|
||||
pub fn heroscript_dumps(obj HeropromptWorkspace) !string {
|
||||
// create heroscript following template
|
||||
// check for our homedir on our machine and replace in the heroscript to @HOME in path
|
||||
return encoderhero.encode[HeropromptWorkspace](obj)!
|
||||
// return encoderhero.encode[HeropromptWorkspace](obj)! // temporarily commented out
|
||||
return 'name: "${obj.name}"'
|
||||
}
|
||||
|
||||
pub fn heroscript_loads(heroscript string) !HeropromptWorkspace {
|
||||
// TODO: parse heroscript populate HeropromptWorkspace
|
||||
mut obj := encoderhero.decode[HeropromptWorkspace](heroscript)!
|
||||
// mut obj := encoderhero.decode[HeropromptWorkspace](heroscript)! // temporarily commented out
|
||||
obj := HeropromptWorkspace{
|
||||
name: 'default'
|
||||
}
|
||||
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