feat: implement full heroprompt workspace management
- Add create, save, get, list, and delete for workspaces - Enable adding and removing files/dirs by path or name - Integrate codewalker for recursive file discovery - Make workspaces stateful with created/updated timestamps - Update example to demonstrate new lifecycle methods
This commit is contained in:
1522
debug.logs
Normal file
1522
debug.logs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,38 +1,46 @@
|
|||||||
#!/usr/bin/env -S v -n -w -gc none -cg -cc tcc -d use_openssl -enable-globals run
|
#!/usr/bin/env -S v -n -w -gc none -cg -cc tcc -d use_openssl -enable-globals run
|
||||||
|
|
||||||
import freeflowuniverse.herolib.develop.heroprompt
|
import freeflowuniverse.herolib.develop.heroprompt
|
||||||
import freeflowuniverse.herolib.core.playbook
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
// heroscript_config := '
|
// mut workspace := heroprompt.new(
|
||||||
// !!heropromptworkspace.configure name:"test workspace" path:"${os.home_dir()}/code/github/freeflowuniverse/herolib"
|
|
||||||
// '
|
|
||||||
// mut plbook := playbook.new(
|
|
||||||
// text: heroscript_config
|
|
||||||
// )!
|
|
||||||
|
|
||||||
// heroprompt.play(mut plbook)!
|
|
||||||
|
|
||||||
// mut workspace1 := heroprompt.new_workspace(
|
|
||||||
// path: '${os.home_dir()}/code/github/freeflowuniverse/herolib'
|
// path: '${os.home_dir()}/code/github/freeflowuniverse/herolib'
|
||||||
|
// name: 'workspace'
|
||||||
// )!
|
// )!
|
||||||
|
|
||||||
mut workspace2 := heroprompt.get(
|
mut workspace := heroprompt.get(
|
||||||
name: 'test workspace'
|
name: 'example_ws'
|
||||||
|
path: '${os.home_dir()}/code/github/freeflowuniverse/herolib'
|
||||||
|
create: true
|
||||||
)!
|
)!
|
||||||
|
|
||||||
// workspace1.add_dir(path: '${os.home_dir()}/code/github/freeflowuniverse/herolib/docker')!
|
println('workspace (initial): ${workspace}')
|
||||||
// workspace1.add_file(path: '${os.home_dir()}/code/github/freeflowuniverse/herolib/docker/docker_ubuntu_install.sh')!
|
println('selected (initial): ${workspace.selected_children()}')
|
||||||
|
|
||||||
// workspace1.add_dir(path: '${os.home_dir()}/code/github/freeflowuniverse/herolib/docker/herolib')!
|
// Add a directory and a file
|
||||||
// workspace1.add_file(path: '${os.home_dir()}/code/github/freeflowuniverse/herolib/docker/herolib/.gitignore')!
|
workspace.add_dir(path: '${os.home_dir()}/code/github/freeflowuniverse/herolib/docker')!
|
||||||
// workspace1.add_file(path: '${os.home_dir()}/code/github/freeflowuniverse/herolib/docker/herolib/build.sh')!
|
workspace.add_file(path: '${os.home_dir()}/code/github/freeflowuniverse/herolib/docker/docker_ubuntu_install.sh')!
|
||||||
// workspace1.add_file(path: '${os.home_dir()}/code/github/freeflowuniverse/herolib/docker/herolib/debug.sh')!
|
println('selected (after add): ${workspace.selected_children()}')
|
||||||
|
|
||||||
// workspace1.add_dir(path: '${os.home_dir()}/code/github/freeflowuniverse/herolib/docker/postgresql')!
|
// Build a prompt from current selection (should be empty now)
|
||||||
|
mut prompt := workspace.prompt(
|
||||||
|
text: 'Using the selected files, i want you to get all print statments'
|
||||||
|
)
|
||||||
|
|
||||||
// prompt := workspace1.prompt(
|
println('--- PROMPT START ---')
|
||||||
// text: 'Using the selected files, i want you to get all print statments'
|
println(prompt)
|
||||||
// )
|
println('--- PROMPT END ---')
|
||||||
|
|
||||||
// println(prompt)
|
// Remove the file by name, then the directory by name
|
||||||
|
workspace.remove_file(name: 'docker_ubuntu_install.sh') or { println('remove_file: ${err}') }
|
||||||
|
workspace.remove_dir(name: 'docker') or { println('remove_dir: ${err}') }
|
||||||
|
println('selected (after remove): ${workspace.selected_children()}')
|
||||||
|
|
||||||
|
// List workspaces (names only)
|
||||||
|
mut all := heroprompt.list_workspaces() or { []&heroprompt.Workspace{} }
|
||||||
|
mut names := []string{}
|
||||||
|
for w in all { names << w.name }
|
||||||
|
println('workspaces: ${names}')
|
||||||
|
|
||||||
|
// Optionally delete the example workspace
|
||||||
|
workspace.delete_workspace() or { println('delete_workspace: ${err}') }
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
#!/usr/bin/env -S v -n -w -gc none -cg -cc tcc -d use_openssl -enable-globals run
|
|
||||||
|
|
||||||
import freeflowuniverse.herolib.core.playbook
|
|
||||||
import os
|
|
||||||
|
|
||||||
|
|
||||||
heroscript_config := '
|
|
||||||
!!heropromptworkspace.configure name:"test workspace" path:"${os.home_dir()}/code/github/freeflowuniverse/herolib"
|
|
||||||
'
|
|
||||||
mut plbook := playbook.new(
|
|
||||||
text: heroscript_config
|
|
||||||
)!
|
|
||||||
|
|
||||||
|
|
||||||
heroprompt.play(mut plbook)!
|
|
||||||
@@ -4,6 +4,8 @@ import freeflowuniverse.herolib.core.base
|
|||||||
import freeflowuniverse.herolib.core.playbook { PlayBook }
|
import freeflowuniverse.herolib.core.playbook { PlayBook }
|
||||||
import freeflowuniverse.herolib.ui.console
|
import freeflowuniverse.herolib.ui.console
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
__global (
|
__global (
|
||||||
heroprompt_global map[string]&Workspace
|
heroprompt_global map[string]&Workspace
|
||||||
@@ -16,13 +18,32 @@ __global (
|
|||||||
pub struct ArgsGet {
|
pub struct ArgsGet {
|
||||||
pub mut:
|
pub mut:
|
||||||
name string = 'default'
|
name string = 'default'
|
||||||
|
path string
|
||||||
fromdb bool // will load from filesystem
|
fromdb bool // will load from filesystem
|
||||||
create bool // default will not create if not exist
|
create bool // default will not create if not exist
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(args ArgsGet) !&Workspace {
|
pub fn new(args ArgsGet) !&Workspace {
|
||||||
|
// validate
|
||||||
|
if args.name.len == 0 {
|
||||||
|
return error('workspace name is required')
|
||||||
|
}
|
||||||
|
mut base_path := ''
|
||||||
|
if args.path.len > 0 {
|
||||||
|
if !os.exists(args.path) {
|
||||||
|
return error('workspace path does not exist: ${args.path}')
|
||||||
|
}
|
||||||
|
if !os.is_dir(args.path) {
|
||||||
|
return error('workspace path is not a directory: ${args.path}')
|
||||||
|
}
|
||||||
|
base_path = os.real_path(args.path)
|
||||||
|
}
|
||||||
mut obj := Workspace{
|
mut obj := Workspace{
|
||||||
name: args.name
|
name: args.name
|
||||||
|
base_path: base_path
|
||||||
|
created: time.now()
|
||||||
|
updated: time.now()
|
||||||
|
is_saved: false
|
||||||
}
|
}
|
||||||
set(obj)!
|
set(obj)!
|
||||||
return get(name: args.name)!
|
return get(name: args.name)!
|
||||||
@@ -36,7 +57,7 @@ pub fn get(args ArgsGet) !&Workspace {
|
|||||||
if r.hexists('context:heroprompt', args.name)! {
|
if r.hexists('context:heroprompt', args.name)! {
|
||||||
data := r.hget('context:heroprompt', args.name)!
|
data := r.hget('context:heroprompt', args.name)!
|
||||||
if data.len == 0 {
|
if data.len == 0 {
|
||||||
return error('Workspace with name: heroprompt does not exist, prob bug.')
|
return error('Workspace with name: ${args.name} does not exist, prob bug.')
|
||||||
}
|
}
|
||||||
mut obj := json.decode(Workspace, data)!
|
mut obj := json.decode(Workspace, data)!
|
||||||
set_in_mem(obj)!
|
set_in_mem(obj)!
|
||||||
@@ -44,7 +65,7 @@ pub fn get(args ArgsGet) !&Workspace {
|
|||||||
if args.create {
|
if args.create {
|
||||||
new(args)!
|
new(args)!
|
||||||
} else {
|
} else {
|
||||||
return error("Workspace with name 'heroprompt' does not exist")
|
return error("Workspace with name '${args.name}' does not exist")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return get(name: args.name)! // no longer from db nor create
|
return get(name: args.name)! // no longer from db nor create
|
||||||
|
|||||||
@@ -1,28 +1,28 @@
|
|||||||
module heroprompt
|
module heroprompt
|
||||||
|
|
||||||
import freeflowuniverse.herolib.data.paramsparser
|
|
||||||
import freeflowuniverse.herolib.data.encoderhero
|
import freeflowuniverse.herolib.data.encoderhero
|
||||||
import os
|
import time
|
||||||
|
|
||||||
pub const version = '0.0.0'
|
pub const version = '0.0.0'
|
||||||
const singleton = false
|
const singleton = false
|
||||||
const default = true
|
const default = true
|
||||||
|
|
||||||
/
|
|
||||||
// Workspace represents a workspace containing multiple directories
|
// Workspace represents a workspace containing multiple directories
|
||||||
// and their selected files for AI prompt generation
|
// and their selected files for AI prompt generation
|
||||||
@[heap]
|
@[heap]
|
||||||
pub struct Workspace {
|
pub struct Workspace {
|
||||||
pub mut:
|
pub mut:
|
||||||
name string = 'default' // Workspace name
|
name string = 'default' // Workspace name
|
||||||
base_path string // Base path of the workspace
|
base_path string // Base path of the workspace
|
||||||
children []HeropromptChild // List of directories and files in this workspace
|
children []HeropromptChild // List of directories and files in this workspace
|
||||||
|
created time.Time // Time of creation
|
||||||
|
updated time.Time // Time of last update
|
||||||
|
is_saved bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// your checking & initialization code if needed
|
// your checking & initialization code if needed
|
||||||
fn obj_init(mycfg_ Workspace) !Workspace {
|
fn obj_init(mycfg_ Workspace) !Workspace {
|
||||||
return mycfg
|
return mycfg_
|
||||||
}
|
}
|
||||||
|
|
||||||
/////////////NORMALLY NO NEED TO TOUCH
|
/////////////NORMALLY NO NEED TO TOUCH
|
||||||
|
|||||||
@@ -4,32 +4,14 @@ import rand
|
|||||||
import time
|
import time
|
||||||
import os
|
import os
|
||||||
import freeflowuniverse.herolib.core.pathlib
|
import freeflowuniverse.herolib.core.pathlib
|
||||||
|
import freeflowuniverse.herolib.develop.codewalker
|
||||||
|
|
||||||
|
fn (wsp &Workspace) save() !&Workspace {
|
||||||
/
|
mut tmp := wsp
|
||||||
/// Create a new workspace
|
tmp.updated = time.now()
|
||||||
/// If the name is not passed, we will generate a random one
|
tmp.is_saved = true
|
||||||
fn (wsp Workspace) new(args_ NewWorkspaceParams) !&Workspace {
|
set(tmp)!
|
||||||
mut args := args_
|
return get(name: wsp.name)!
|
||||||
if args.name.len == 0 {
|
|
||||||
args.name = generate_random_workspace_name()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate and set base path
|
|
||||||
if args.path.len > 0 {
|
|
||||||
if !os.exists(args.path) {
|
|
||||||
return error('Workspace path does not exist: ${args.path}')
|
|
||||||
}
|
|
||||||
if !os.is_dir(args.path) {
|
|
||||||
return error('Workspace path is not a directory: ${args.path}')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mut workspace := &Workspace{
|
|
||||||
name: args.name
|
|
||||||
base_path: os.real_path(args.path)
|
|
||||||
}
|
|
||||||
return workspace
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// // WorkspaceItem represents a file or directory in the workspace tree
|
// // WorkspaceItem represents a file or directory in the workspace tree
|
||||||
@@ -209,13 +191,22 @@ pub mut:
|
|||||||
// add a directory to the selection (no recursion stored; recursion is done on-demand)
|
// add a directory to the selection (no recursion stored; recursion is done on-demand)
|
||||||
pub fn (mut wsp Workspace) add_dir(args AddDirParams) !HeropromptChild {
|
pub fn (mut wsp Workspace) add_dir(args AddDirParams) !HeropromptChild {
|
||||||
if args.path.len == 0 {
|
if args.path.len == 0 {
|
||||||
return error('The dir path is required')
|
return error('the directory path is required')
|
||||||
}
|
}
|
||||||
|
|
||||||
if !os.exists(args.path) || !os.is_dir(args.path) {
|
if !os.exists(args.path) || !os.is_dir(args.path) {
|
||||||
return error('Path is not an existing directory: ${args.path}')
|
return error('path is not an existing directory: ${args.path}')
|
||||||
}
|
}
|
||||||
|
|
||||||
abs_path := os.real_path(args.path)
|
abs_path := os.real_path(args.path)
|
||||||
name := os.base(abs_path)
|
name := os.base(abs_path)
|
||||||
|
|
||||||
|
for child in wsp.children {
|
||||||
|
if child.name == name {
|
||||||
|
return error('another directory with the same name already exists: ${name}')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mut ch := HeropromptChild{
|
mut ch := HeropromptChild{
|
||||||
path: pathlib.Path{
|
path: pathlib.Path{
|
||||||
path: abs_path
|
path: abs_path
|
||||||
@@ -225,6 +216,7 @@ pub fn (mut wsp Workspace) add_dir(args AddDirParams) !HeropromptChild {
|
|||||||
name: name
|
name: name
|
||||||
}
|
}
|
||||||
wsp.children << ch
|
wsp.children << ch
|
||||||
|
wsp.save()!
|
||||||
return ch
|
return ch
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,11 +225,24 @@ pub fn (mut wsp Workspace) add_file(args AddFileParams) !HeropromptChild {
|
|||||||
if args.path.len == 0 {
|
if args.path.len == 0 {
|
||||||
return error('The file path is required')
|
return error('The file path is required')
|
||||||
}
|
}
|
||||||
|
|
||||||
if !os.exists(args.path) || !os.is_file(args.path) {
|
if !os.exists(args.path) || !os.is_file(args.path) {
|
||||||
return error('Path is not an existing file: ${args.path}')
|
return error('Path is not an existing file: ${args.path}')
|
||||||
}
|
}
|
||||||
|
|
||||||
abs_path := os.real_path(args.path)
|
abs_path := os.real_path(args.path)
|
||||||
name := os.base(abs_path)
|
name := os.base(abs_path)
|
||||||
|
|
||||||
|
for child in wsp.children {
|
||||||
|
if child.path.cat == .file && child.name == name {
|
||||||
|
return error('another file with the same name already exists: ${name}')
|
||||||
|
}
|
||||||
|
|
||||||
|
if child.path.cat == .dir && child.name == name {
|
||||||
|
return error('${name}: is a directory, cannot add file with same name')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
content := os.read_file(abs_path) or { '' }
|
content := os.read_file(abs_path) or { '' }
|
||||||
mut ch := HeropromptChild{
|
mut ch := HeropromptChild{
|
||||||
path: pathlib.Path{
|
path: pathlib.Path{
|
||||||
@@ -248,10 +253,96 @@ pub fn (mut wsp Workspace) add_file(args AddFileParams) !HeropromptChild {
|
|||||||
name: name
|
name: name
|
||||||
content: content
|
content: content
|
||||||
}
|
}
|
||||||
|
|
||||||
wsp.children << ch
|
wsp.children << ch
|
||||||
|
wsp.save()!
|
||||||
return ch
|
return ch
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Removal API
|
||||||
|
@[params]
|
||||||
|
pub struct RemoveParams {
|
||||||
|
pub mut:
|
||||||
|
path string
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove a directory from the selection (by absolute path or name)
|
||||||
|
pub fn (mut wsp Workspace) remove_dir(args RemoveParams) ! {
|
||||||
|
if args.path.len == 0 && args.name.len == 0 {
|
||||||
|
return error('either path or name is required to remove a directory')
|
||||||
|
}
|
||||||
|
mut idxs := []int{}
|
||||||
|
for i, ch in wsp.children {
|
||||||
|
if ch.path.cat != .dir {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if args.path.len > 0 && os.real_path(args.path) == ch.path.path {
|
||||||
|
idxs << i
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if args.name.len > 0 && args.name == ch.name {
|
||||||
|
idxs << i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if idxs.len == 0 {
|
||||||
|
return error('no matching directory found to remove')
|
||||||
|
}
|
||||||
|
// remove from end to start to keep indices valid
|
||||||
|
idxs.sort(a > b)
|
||||||
|
for i in idxs {
|
||||||
|
wsp.children.delete(i)
|
||||||
|
}
|
||||||
|
wsp.save()!
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove a file from the selection (by absolute path or name)
|
||||||
|
pub fn (mut wsp Workspace) remove_file(args RemoveParams) ! {
|
||||||
|
if args.path.len == 0 && args.name.len == 0 {
|
||||||
|
return error('either path or name is required to remove a file')
|
||||||
|
}
|
||||||
|
mut idxs := []int{}
|
||||||
|
for i, ch in wsp.children {
|
||||||
|
if ch.path.cat != .file {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if args.path.len > 0 && os.real_path(args.path) == ch.path.path {
|
||||||
|
idxs << i
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if args.name.len > 0 && args.name == ch.name {
|
||||||
|
idxs << i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if idxs.len == 0 {
|
||||||
|
return error('no matching file found to remove')
|
||||||
|
}
|
||||||
|
idxs.sort(a > b)
|
||||||
|
for i in idxs {
|
||||||
|
wsp.children.delete(i)
|
||||||
|
}
|
||||||
|
wsp.save()!
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete this workspace from the store
|
||||||
|
pub fn (wsp &Workspace) delete_workspace() ! {
|
||||||
|
delete(name: wsp.name)!
|
||||||
|
}
|
||||||
|
|
||||||
|
// List workspaces (wrapper over factory list)
|
||||||
|
pub fn list_workspaces() ![]&Workspace {
|
||||||
|
return list(fromdb: false)!
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list_workspaces_fromdb() ![]&Workspace {
|
||||||
|
return list(fromdb: true)!
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the currently selected children (copy)
|
||||||
|
pub fn (wsp Workspace) selected_children() []HeropromptChild {
|
||||||
|
return wsp.children.clone()
|
||||||
|
}
|
||||||
|
|
||||||
// Build utilities
|
// Build utilities
|
||||||
fn list_files_recursive(root string) []string {
|
fn list_files_recursive(root string) []string {
|
||||||
mut out := []string{}
|
mut out := []string{}
|
||||||
@@ -268,7 +359,7 @@ fn list_files_recursive(root string) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// build_file_content generates formatted content for all selected files (and all files under selected dirs)
|
// build_file_content generates formatted content for all selected files (and all files under selected dirs)
|
||||||
fn (wsp Workspace) build_file_content() string {
|
fn (wsp Workspace) build_file_content() !string {
|
||||||
mut content := ''
|
mut content := ''
|
||||||
// files selected directly
|
// files selected directly
|
||||||
for ch in wsp.children {
|
for ch in wsp.children {
|
||||||
@@ -291,16 +382,18 @@ fn (wsp Workspace) build_file_content() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// files under selected directories
|
// files under selected directories, using CodeWalker for filtered traversal
|
||||||
for ch in wsp.children {
|
for ch in wsp.children {
|
||||||
if ch.path.cat == .dir {
|
if ch.path.cat == .dir {
|
||||||
for f in list_files_recursive(ch.path.path) {
|
mut cw := codewalker.new(codewalker.CodeWalkerArgs{})!
|
||||||
|
mut fm := cw.filemap_get(path: ch.path.path)!
|
||||||
|
for rel, fc in fm.content {
|
||||||
if content.len > 0 {
|
if content.len > 0 {
|
||||||
content += '\n\n'
|
content += '\n\n'
|
||||||
}
|
}
|
||||||
content += f + '\n'
|
abs := os.join_path(ch.path.path, rel)
|
||||||
ext := get_file_extension(os.base(f))
|
content += abs + '\n'
|
||||||
fc := os.read_file(f) or { '' }
|
ext := get_file_extension(os.base(abs))
|
||||||
if fc.len == 0 {
|
if fc.len == 0 {
|
||||||
content += '(Empty file)\n'
|
content += '(Empty file)\n'
|
||||||
} else {
|
} else {
|
||||||
@@ -339,7 +432,7 @@ fn build_file_tree_fs(roots []HeropromptChild, prefix string) string {
|
|||||||
files.sort()
|
files.sort()
|
||||||
// files
|
// files
|
||||||
for j, f in files {
|
for j, f in files {
|
||||||
file_connector := if (j == files.len - 1 && dirs.len == 0) {
|
file_connector := if j == files.len - 1 && dirs.len == 0 {
|
||||||
'└── '
|
'└── '
|
||||||
} else {
|
} else {
|
||||||
'├── '
|
'├── '
|
||||||
@@ -455,7 +548,7 @@ pub mut:
|
|||||||
pub fn (wsp Workspace) prompt(args WorkspacePrompt) string {
|
pub fn (wsp Workspace) prompt(args WorkspacePrompt) string {
|
||||||
user_instructions := wsp.build_user_instructions(args.text)
|
user_instructions := wsp.build_user_instructions(args.text)
|
||||||
file_map := wsp.build_file_map()
|
file_map := wsp.build_file_map()
|
||||||
file_contents := wsp.build_file_content()
|
file_contents := wsp.build_file_content() or { '(Error building file contents)' }
|
||||||
prompt := HeropromptTmpPrompt{
|
prompt := HeropromptTmpPrompt{
|
||||||
user_instructions: user_instructions
|
user_instructions: user_instructions
|
||||||
file_map: file_map
|
file_map: file_map
|
||||||
|
|||||||
Reference in New Issue
Block a user