diff --git a/aiprompts/ai_instruct/documentation_from_v.md b/aiprompts/ai_instruct/documentation_from_v.md new file mode 100644 index 00000000..e4387461 --- /dev/null +++ b/aiprompts/ai_instruct/documentation_from_v.md @@ -0,0 +1,52 @@ +params: + +- filepath: /Users/despiegk/code/github/freeflowuniverse/herolib/lib/clients/openai + +make a dense overview of the code above, easy to understand for AI + +the result is 1 markdown file called codeoverview.md and is stored in $filepath + +try to figure out which functions are more important and which are less important, so that the most important functions are at the top of section you are working on + +the template is as follows + +```md +# the name of the module + +2-5 liner description + +## factory + +is there factory, which one and quick example how to call, don’t say in which file not relevant +show how to import the module is as follows: import freeflowuniverse.herolib. +and then starting from lib e.g. lib/clients/mycelium would result in import freeflowuniverse.herolib. clients.mycelium + +## overview + +quick overview as list with identations, of the structs and its methods + +## structs + +### structname + +now list the methods & arguments, for arguments use table + +for each method show the arguments needed to call the method, and what it returns + +### methods + +- if any methods which are on module +- only show public methods, don't show the get/set/exists methods on module level as part of factory. + + +``` + +don't mention what we don't show because of rules above. + +the only output we want is markdown file as follows + +===WRITE=== +$filepath +===CONTENT=== +$the content of the generated markdown file +===END=== \ No newline at end of file diff --git a/aiprompts/ai_instruct/documentation_from_v_md.md b/aiprompts/ai_instruct/documentation_from_v_md.md new file mode 100644 index 00000000..b8ac036e --- /dev/null +++ b/aiprompts/ai_instruct/documentation_from_v_md.md @@ -0,0 +1,22 @@ +remove all navigation elements, and index +for each method, move the args as used in the methods to the method section so its easier to read + +start of output file is: + +# the name of the module + +2-5 liner description + +## factory + +is there factory, which one and quick example how to call, don’t say in which file not relevant +show how to import the module is as follows: import freeflowuniverse.herolib. +and then starting from lib e.g. lib/clients/mycelium would result in import freeflowuniverse.herolib. clients.mycelium + +## structs and methods + +quick overview as list with identations, of the structs and its methods + + +ONLY OUTPUT THE MARKDOWN FILE, NOTHING ELSE + diff --git a/doc.vsh b/doc.vsh index acceea21..9d2fb736 100755 --- a/doc.vsh +++ b/doc.vsh @@ -36,6 +36,11 @@ if os.system('v doc -m -f html . -readme -comments -no-timestamp -o ../docs') != panic('Failed to generate HTML documentation') } +if os.system('v doc -m -f md . -no-color -o ../vdocs/') != 0 { + panic('Failed to generate Hero markdown documentation') +} + + os.chdir(abs_dir_of_script) or { panic('Failed to change directory to abs_dir_of_script: ${err}') } @@ -47,9 +52,9 @@ println('Generating Markdown documentation...') // panic('Failed to generate V markdown documentation') // } -if os.system('v doc -m -no-color -f md -o vdocs/') != 0 { - panic('Failed to generate Hero markdown documentation') -} +// if os.system('v doc -m -no-color -f md . -o vdocs/') != 0 { +// panic('Failed to generate Hero markdown documentation') +// } // Open documentation in browser on non-Linux systems $if !linux { diff --git a/examples/biztools/bizmodel_complete.vsh b/examples/biztools/bizmodel_complete.vsh index 0a8119bf..f1548c9c 100755 --- a/examples/biztools/bizmodel_complete.vsh +++ b/examples/biztools/bizmodel_complete.vsh @@ -3,10 +3,9 @@ import freeflowuniverse.herolib.biz.bizmodel import os - heroscript := os.join_path(os.dir(@FILE), 'examples/complete.heroscript') // Execute the script and print results -bizmodel.play(heroscript_path:heroscript)! -mut bm := bizmodel.get("threefold")! +bizmodel.play(heroscript_path: heroscript)! +mut bm := bizmodel.get('threefold')! bm.sheet.pprint(nr_columns: 10)! diff --git a/examples/biztools/bizmodel_export.vsh b/examples/biztools/bizmodel_export.vsh index 3632caf7..2b7d2135 100755 --- a/examples/biztools/bizmodel_export.vsh +++ b/examples/biztools/bizmodel_export.vsh @@ -12,10 +12,9 @@ import os heroscript := os.join_path(os.dir(@FILE), 'examples/complete.heroscript') // Execute the script and print results -bizmodel.play(heroscript_path:heroscript)! +bizmodel.play(heroscript_path: heroscript)! - -mut bm := bizmodel.get("threefold")! +mut bm := bizmodel.get('threefold')! bm.sheet.pprint(nr_columns: 10)! // buildpath := '${os.home_dir()}/hero/var/mdbuild/bizmodel' @@ -32,5 +31,5 @@ bm.sheet.pprint(nr_columns: 10)! bm.export( name: 'example_report' title: 'Example Business Model' - path: '/tmp/bizmodel_export' + path: '/tmp/bizmodel_export' )! diff --git a/examples/biztools/bizmodel_full.vsh b/examples/biztools/bizmodel_full.vsh index d12c6168..8cf206d0 100755 --- a/examples/biztools/bizmodel_full.vsh +++ b/examples/biztools/bizmodel_full.vsh @@ -3,10 +3,9 @@ import freeflowuniverse.herolib.biz.bizmodel import os - heroscript := os.join_path(os.dir(@FILE), 'examples/full') // Execute the script and print results -bizmodel.play(heroscript_path:heroscript)! -mut bm := bizmodel.get("threefold")! +bizmodel.play(heroscript_path: heroscript)! +mut bm := bizmodel.get('threefold')! bm.sheet.pprint(nr_columns: 25)! diff --git a/examples/biztools/costs.vsh b/examples/biztools/costs.vsh index 7e2e847d..4c1e078a 100755 --- a/examples/biztools/costs.vsh +++ b/examples/biztools/costs.vsh @@ -49,4 +49,4 @@ bizmodel.play(heroscript: heroscript)! mut bm := bizmodel.get('test')! -bm.sheet.pprint(nr_columns: 20)! \ No newline at end of file +bm.sheet.pprint(nr_columns: 20)! diff --git a/examples/biztools/funding.vsh b/examples/biztools/funding.vsh index a3a91b20..ca8783c6 100755 --- a/examples/biztools/funding.vsh +++ b/examples/biztools/funding.vsh @@ -21,4 +21,4 @@ bizmodel.play(heroscript: heroscript)! mut bm := bizmodel.get('test')! -bm.sheet.pprint(nr_columns: 20)! \ No newline at end of file +bm.sheet.pprint(nr_columns: 20)! diff --git a/examples/clients/aiclient_example.vsh b/examples/clients/aiclient_example.vsh new file mode 100755 index 00000000..e4e4e02b --- /dev/null +++ b/examples/clients/aiclient_example.vsh @@ -0,0 +1,20 @@ +#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run + +import freeflowuniverse.herolib.clients.openai +import freeflowuniverse.herolib.core.playcmds + +playcmds.run( + heroscript: ' + !!openai.configure name: "default" key: "" url: "https://openrouter.ai/api/v1" model_default: "gpt-oss-120b" + ' +)! + +// name:'default' is the default, you can change it to whatever you want +mut client := openai.get()! + +mut r := client.chat_completion( + model: 'gpt-3.5-turbo' + message: 'Hello, world!' + temperature: 0.5 + max_completion_tokens: 1024 +)! diff --git a/examples/core/generate.vsh b/examples/core/generate.vsh index dc0596fa..77d4a124 100755 --- a/examples/core/generate.vsh +++ b/examples/core/generate.vsh @@ -4,7 +4,7 @@ import freeflowuniverse.herolib.core.generator.generic as generator import freeflowuniverse.herolib.core.pathlib mut args := generator.GeneratorArgs{ - path: '~/code/github/freeflowuniverse/herolib/lib/clients/postgresql_client' + path: '~/code/github/freeflowuniverse/herolib/lib/clients/postgresql_client' force: true } @@ -13,5 +13,4 @@ mut args := generator.GeneratorArgs{ // force: true // } - generator.scan(args)! diff --git a/examples/data/compress_gzip_example.vsh b/examples/data/compress_gzip_example.vsh index e5e17b84..e3b84f09 100644 --- a/examples/data/compress_gzip_example.vsh +++ b/examples/data/compress_gzip_example.vsh @@ -1,6 +1,5 @@ #!/usr/bin/env -S v -cg -gc none -cc tcc -d use_openssl -enable-globals run -import freeflowuniverse.herolib... import compress.gzip // Define some sample data to compress @@ -18,12 +17,12 @@ println('Compressed Data Length: ${compressed_data.len}') // Decompress the data decompressed_data := gzip.decompress(compressed_data)! -println('Decompressed Data: "${decompressed_data.string()}"') +println('Decompressed Data: "${decompressed_data.bytestr()}"') println('Decompressed Data Length: ${decompressed_data.len}') // Verify if the decompressed data matches the original data if data.bytes() == decompressed_data { - println('Compression and decompression successful! Data matches.') + println('Compression and decompression successful! Data matches.') } else { - println('Error: Decompressed data does not match original data.') -} \ No newline at end of file + println('Error: Decompressed data does not match original data.') +} diff --git a/examples/hero/db/psql2.vsh b/examples/hero/db/psql2.vsh index 94677751..c5912314 100755 --- a/examples/hero/db/psql2.vsh +++ b/examples/hero/db/psql2.vsh @@ -1,30 +1,27 @@ #!/usr/bin/env -S v -n -cg -w -gc none -cc tcc -d use_openssl -enable-globals run + // #!/usr/bin/env -S v -n -w -enable-globals run import freeflowuniverse.herolib.clients.postgresql_client import freeflowuniverse.herolib.core.playbook import freeflowuniverse.herolib.hero.models.circle import freeflowuniverse.herolib.core.playcmds import freeflowuniverse.herolib.hero.db.hero_db - import db.pg // psql -h /tmp -U myuser -d mydb mut db := pg.connect(pg.Config{ - host: '/tmp' - port: 5432 - user: 'myuser' - password: 'mypassword' - dbname: 'mydb' - })! + host: '/tmp' + port: 5432 + user: 'myuser' + password: 'mypassword' + dbname: 'mydb' +})! -mut r:=db.exec("select * from users;")! +mut r := db.exec('select * from users;')! println(r) - - - // // Configure PostgreSQL client // heroscript := " // !!postgresql_client.configure @@ -50,7 +47,6 @@ heroscript := " mut plbook := playbook.new(text: heroscript)! postgresql_client.play(mut plbook)! - // //Get the configured client mut db_client := postgresql_client.get(name: 'aaa')! @@ -78,7 +74,6 @@ mut db_client := postgresql_client.get(name: 'aaa')! // println('Database and table setup completed successfully!') - // // Create HeroDB for Circle type // mut circle_db := hero_db.new[circle.Circle]()! @@ -112,5 +107,4 @@ mut db_client := postgresql_client.get(name: 'aaa')! // // Search circles by status // active_circles := circle_db.search_by_index("status", "active")! - -//https://www.moncefbelyamani.com/how-to-install-postgresql-on-a-mac-with-homebrew-and-lunchy/ \ No newline at end of file +// https://www.moncefbelyamani.com/how-to-install-postgresql-on-a-mac-with-homebrew-and-lunchy/ diff --git a/examples/web/docusaurus_example.vsh b/examples/web/docusaurus_example.vsh index 4548bae0..21364fcc 100755 --- a/examples/web/docusaurus_example.vsh +++ b/examples/web/docusaurus_example.vsh @@ -2,7 +2,8 @@ import freeflowuniverse.herolib.core.playcmds -mut plbook := heroscript.new(heroscript: ' +mut plbook := heroscript.new( + heroscript: ' !!docusaurus.define path_build: "/tmp/docusaurus_build" @@ -18,6 +19,5 @@ mut plbook := heroscript.new(heroscript: ' git_pull:1 !!docusaurus.build -')! - - +' +)! diff --git a/lib/biz/bizmodel/act.v b/lib/biz/bizmodel/act.v index c6ca9d7d..9b519ab2 100644 --- a/lib/biz/bizmodel/act.v +++ b/lib/biz/bizmodel/act.v @@ -84,12 +84,12 @@ pub fn (mut m BizModel) export_overview_action(action Action) !Action { } fn (mut m BizModel) new_report_action(action Action) !Action { - mut p:=action.params + mut p := action.params path := p.get_default('path', '')! name := p.get_default('name', '')! title := p.get_default('title', '')! description := p.get_default('description', '')! - m.export(path:path,name:name,title:title,description:description)! + m.export(path: path, name: name, title: title, description: description)! return action } diff --git a/lib/biz/bizmodel/export.v b/lib/biz/bizmodel/export.v index e8a8e756..5ca4aac5 100644 --- a/lib/biz/bizmodel/export.v +++ b/lib/biz/bizmodel/export.v @@ -36,7 +36,6 @@ pub fn (b BizModel) export(args ExportArgs) ! { // b.export_fundraising(export) } - pub fn (model BizModel) write_introduction(args ExportArgs) ! { mut index_page := pathlib.get_file(path: '${args.path}/introduction.md')! // mut tmpl_index := $tmpl('templates/index.md') diff --git a/lib/biz/bizmodel/play_cost.v b/lib/biz/bizmodel/play_cost.v index 0d426cd8..93a928c0 100644 --- a/lib/biz/bizmodel/play_cost.v +++ b/lib/biz/bizmodel/play_cost.v @@ -13,9 +13,7 @@ import math // costcenter:'marketing_cc' // cost_percent_revenue:'1%' fn (mut m BizModel) cost_define_action(action Action) !Action { - mut name := action.params.get('name') or { - return error('Cost name is required.') - } + mut name := action.params.get('name') or { return error('Cost name is required.') } mut descr := action.params.get_default('descr', '')! if descr.len == 0 { descr = action.params.get_default('description', '')! @@ -32,7 +30,8 @@ fn (mut m BizModel) cost_define_action(action Action) !Action { return error('Costcenter `${costcenter}` not found. Please define it first.') } - cost_percent_revenue := action.params.get_percentage_default('cost_percent_revenue','0%')! + cost_percent_revenue := action.params.get_percentage_default('cost_percent_revenue', + '0%')! indexation := action.params.get_percentage_default('indexation', '0%')! @@ -55,7 +54,6 @@ fn (mut m BizModel) cost_define_action(action Action) !Action { )! cost_row = cost_row.action(action: .reverse)! - if cost_percent_revenue > 0 { // println(cost_row) mut revtotal := m.sheet.row_get('revenue_total')! @@ -73,9 +71,6 @@ fn (mut m BizModel) cost_define_action(action Action) !Action { return action } - - - fn (mut sim BizModel) cost_total() ! { sim.sheet.group2row( name: 'cost_total' diff --git a/lib/biz/bizmodel/play_costcenter.v b/lib/biz/bizmodel/play_costcenter.v index b4bed5b0..e3fb361b 100644 --- a/lib/biz/bizmodel/play_costcenter.v +++ b/lib/biz/bizmodel/play_costcenter.v @@ -9,9 +9,7 @@ import freeflowuniverse.herolib.core.texttools // descr:'Marketing Cost Center' // department:'marketing' fn (mut m BizModel) costcenter_define_action(action Action) !Action { - mut name := action.params.get('name') or { - return error('Costcenter name is required.') - } + mut name := action.params.get('name') or { return error('Costcenter name is required.') } mut descr := action.params.get_default('descr', '')! if descr.len == 0 { descr = action.params.get_default('description', '')! diff --git a/lib/biz/bizmodel/play_hr.v b/lib/biz/bizmodel/play_hr.v index 1d19a69b..36301c5c 100644 --- a/lib/biz/bizmodel/play_hr.v +++ b/lib/biz/bizmodel/play_hr.v @@ -102,7 +102,6 @@ fn (mut m BizModel) employee_define_action(action Action) !Action { return action } - fn (mut sim BizModel) hrcost_total() ! { sim.sheet.group2row( name: 'hr_cost_total' diff --git a/lib/biz/bizmodel/play_pl.v b/lib/biz/bizmodel/play_pl.v index 003cccd1..521bbef0 100644 --- a/lib/biz/bizmodel/play_pl.v +++ b/lib/biz/bizmodel/play_pl.v @@ -1,7 +1,7 @@ module bizmodel import arrays -import freeflowuniverse.herolib.core.playbook { Action, PlayBook } +import freeflowuniverse.herolib.core.playbook import freeflowuniverse.herolib.ui.console // revenue_total calculates and aggregates the total revenue and cost of goods sold (COGS) for the business model @@ -22,5 +22,4 @@ fn (mut sim BizModel) pl_total() ! { // println(pl_total) // if true{panic("sdsd")} - } diff --git a/lib/biz/bizmodel/play_product_revenue_total.v b/lib/biz/bizmodel/play_product_revenue_total.v index 10f15971..57b9336b 100644 --- a/lib/biz/bizmodel/play_product_revenue_total.v +++ b/lib/biz/bizmodel/play_product_revenue_total.v @@ -28,5 +28,4 @@ fn (mut sim BizModel) revenue_total() ! { // println(revenue_total) // println(cogs_total) // println(margin_total) - } diff --git a/lib/clients/openai/audio/README.md b/lib/clients/openai/audio/README.md new file mode 100644 index 00000000..b6562685 --- /dev/null +++ b/lib/clients/openai/audio/README.md @@ -0,0 +1,19 @@ +# Quick Example: Transcribing Audio + +```v + +import freeflowuniverse.herolib.clients.openai + +mut client:= openai.get()! //will be the default client, key is in `AIKEY` on environment variable or `OPENROUTER_API_KEY` + +// Assuming you have an audio file named 'audio.mp3' in the same directory +// For a real application, handle file paths dynamically +audio_file_path := 'audio.mp3' + +resp := client.audio.create_transcription( + file: audio_file_path, + model: 'whisper-1' +)! + +``` + diff --git a/lib/clients/openai/audio.v b/lib/clients/openai/audio/audio.v similarity index 73% rename from lib/clients/openai/audio.v rename to lib/clients/openai/audio/audio.v index f5f980e7..8e9a9ce5 100644 --- a/lib/clients/openai/audio.v +++ b/lib/clients/openai/audio/audio.v @@ -1,9 +1,88 @@ -module openai +module audio import json import freeflowuniverse.herolib.core.httpconnection import os import net.http +import freeflowuniverse.herolib.clients.openai { OpenAI } + +type OpenAIAlias = OpenAI + +pub enum Voice { + alloy + ash + coral + echo + fable + onyx + nova + sage + shimmer +} + +fn voice_str(x Voice) string { + return match x { + .alloy { + 'alloy' + } + .ash { + 'ash' + } + .coral { + 'coral' + } + .echo { + 'echo' + } + .fable { + 'fable' + } + .onyx { + 'onyx' + } + .nova { + 'nova' + } + .sage { + 'sage' + } + .shimmer { + 'shimmer' + } + } +} + +pub enum AudioFormat { + mp3 + opus + aac + flac + wav + pcm +} + +fn audio_format_str(x AudioFormat) string { + return match x { + .mp3 { + 'mp3' + } + .opus { + 'opus' + } + .aac { + 'aac' + } + .flac { + 'flac' + } + .wav { + 'wav' + } + .pcm { + 'pcm' + } + } +} pub enum AudioRespType { json @@ -44,6 +123,7 @@ fn audio_resp_type_str(i AudioRespType) string { } } +@[params] pub struct AudioArgs { pub mut: filepath string @@ -60,17 +140,17 @@ pub mut: // create transcription from an audio file // supported audio formats are mp3, mp4, mpeg, mpga, m4a, wav, or webm -pub fn (mut f OpenAI) create_transcription(args AudioArgs) !AudioResponse { +pub fn (mut f OpenAIAlias) create_transcription(args AudioArgs) !AudioResponse { return f.create_audio_request(args, 'audio/transcriptions') } // create translation to english from an audio file // supported audio formats are mp3, mp4, mpeg, mpga, m4a, wav, or webm -pub fn (mut f OpenAI) create_tranlation(args AudioArgs) !AudioResponse { +pub fn (mut f OpenAIAlias) create_tranlation(args AudioArgs) !AudioResponse { return f.create_audio_request(args, 'audio/translations') } -fn (mut f OpenAI) create_audio_request(args AudioArgs, endpoint string) !AudioResponse { +fn (mut f OpenAIAlias) create_audio_request(args AudioArgs, endpoint string) !AudioResponse { file_content := os.read_file(args.filepath)! ext := os.file_ext(args.filepath) mut file_mime_type := '' @@ -131,7 +211,7 @@ pub: speed f32 } -pub fn (mut f OpenAI) create_speech(args CreateSpeechArgs) ! { +pub fn (mut f OpenAIAlias) create_speech(args CreateSpeechArgs) ! { mut output_file := os.open_file(args.output_path, 'w+')! req := CreateSpeechRequest{ diff --git a/lib/clients/openai/audio/audio_test.v b/lib/clients/openai/audio/audio_test.v new file mode 100644 index 00000000..1409fbfe --- /dev/null +++ b/lib/clients/openai/audio/audio_test.v @@ -0,0 +1,26 @@ +module audio_test + +import os +import clients.openai +import clients.openai.audio +import clients.openai.openai_factory_ { get } +import freeflowuniverse.crystallib.osal { play } + +fn test_audio() { + key := os.getenv('OPENAI_API_KEY') + heroscript := '!!openai.configure api_key: "${key}"' + + play(heroscript: heroscript)! + + mut client := get()! + + // create speech + client.create_speech( + input: 'the quick brown fox jumps over the lazy dog' + output_path: '/tmp/output.mp3' + voice: audio.Voice.alloy + response_format: audio.AudioFormat.mp3 + )! + + assert os.exists('/tmp/output.mp3') +} diff --git a/lib/clients/openai/client_test.v b/lib/clients/openai/client_test.v index f48a96cb..a658e428 100644 --- a/lib/clients/openai/client_test.v +++ b/lib/clients/openai/client_test.v @@ -1,6 +1,10 @@ module openai import os +import openai.audio +import openai.files +import openai.finetune +import openai.images fn test_chat_completion() { mut client := get()! @@ -24,83 +28,3 @@ fn test_chat_completion() { assert res.choices[0].message.content == 'AI is getting out of hand' } - -// fn test_embeddings() { -// key := os.getenv('OPENAI_API_KEY') -// heroscript := '!!openai.configure api_key: "${key}"' - -// play(heroscript: heroscript)! - -// mut client := get()! - -// res := client.create_embeddings( -// input: ['The food was delicious and the waiter..'] -// model: .text_embedding_ada -// )! - -// assert res.data.len == 1 -// assert res.data[0].embedding.len == 1536 -// } - -// fn test_files() { -// key := os.getenv('OPENAI_API_KEY') -// heroscript := '!!openai.configure api_key: "${key}"' - -// play(heroscript: heroscript)! - -// mut client := get()! -// uploaded_file := client.upload_file( -// filepath: '${os.dir(@FILE) + '/testdata/testfile.txt'}' -// purpose: .assistants -// )! - -// assert uploaded_file.filename == 'testfile.txt' -// assert uploaded_file.purpose == 'assistants' - -// got_file := client.get_file(uploaded_file.id)! -// assert got_file == uploaded_file - -// uploaded_file2 := client.upload_file( -// filepath: '${os.dir(@FILE) + '/testdata/testfile2.txt'}' -// purpose: .assistants -// )! - -// assert uploaded_file2.filename == 'testfile2.txt' -// assert uploaded_file2.purpose == 'assistants' - -// mut got_list := client.list_files()! - -// assert got_list.data.len >= 2 // there could be other older files - -// mut ids := []string{} -// for file in got_list.data { -// ids << file.id -// } - -// assert uploaded_file.id in ids -// assert uploaded_file2.id in ids - -// for file in got_list.data { -// client.delete_file(file.id)! -// } - -// got_list = client.list_files()! -// assert got_list.data.len == 0 -// } - -// fn test_audio() { -// key := os.getenv('OPENAI_API_KEY') -// heroscript := '!!openai.configure api_key: "${key}"' - -// play(heroscript: heroscript)! - -// mut client := get()! - -// // create speech -// client.create_speech( -// input: 'the quick brown fox jumps over the lazy dog' -// output_path: '/tmp/output.mp3' -// )! - -// assert os.exists('/tmp/output.mp3') -// } diff --git a/lib/clients/openai/completions.v b/lib/clients/openai/completions.v index 09e8416c..4c538c04 100644 --- a/lib/clients/openai/completions.v +++ b/lib/clients/openai/completions.v @@ -2,63 +2,61 @@ module openai import json -pub struct ChatCompletion { -pub mut: - id string - object string - created u32 - choices []Choice - usage Usage -} - -pub struct Choice { -pub mut: - index int - message MessageRaw - finish_reason string -} - -pub struct Message { -pub mut: - role RoleType - content string -} - -pub struct Usage { -pub mut: - prompt_tokens int - completion_tokens int - total_tokens int -} - -pub struct Messages { -pub mut: - messages []Message -} - -pub struct MessageRaw { -pub mut: - role string - content string -} - -struct ChatMessagesRaw { -mut: - model string - messages []MessageRaw - temperature f64 = 0.5 - max_completion_tokens int = 32000 -} - @[params] pub struct CompletionArgs { pub mut: model string - msgs Messages - temperature f64 = 0.5 + messages []Message // optional because we can use message, which means we just pass a string + message string + temperature f64 = 0.2 max_completion_tokens int = 32000 } +struct Message { +mut: + role RoleType + content string +} + +pub enum RoleType { + system + user + assistant + function +} + +fn roletype_str(x RoleType) string { + return match x { + .system { + 'system' + } + .user { + 'user' + } + .assistant { + 'assistant' + } + .function { + 'function' + } + } +} + +struct Usage { +mut: + prompt_tokens int + completion_tokens int + total_tokens int +} + +struct ChatCompletion { +mut: + id string + created u32 + result string + usage Usage +} + // creates a new chat completion given a list of messages // each message consists of message content and the role of the author pub fn (mut f OpenAI) chat_completion(args_ CompletionArgs) !ChatCompletion { @@ -71,19 +69,38 @@ pub fn (mut f OpenAI) chat_completion(args_ CompletionArgs) !ChatCompletion { temperature: args.temperature max_completion_tokens: args.max_completion_tokens } - for msg in args.msgs.messages { + for msg in args.messages { mr := MessageRaw{ role: roletype_str(msg.role) content: msg.content } m.messages << mr } + if args.message != '' { + mr := MessageRaw{ + role: 'user' + content: args.message + } + m.messages << mr + } data := json.encode(m) // println('data: ${data}') mut conn := f.connection()! r := conn.post_json_str(prefix: 'chat/completions', data: data)! // println('res: ${r}') - res := json.decode(ChatCompletion, r)! - return res + res := json.decode(ChatCompletionRaw, r)! + + mut result := '' + for choice in res.choices { + result += choice.message.content + } + + mut chat_completion_result := ChatCompletion{ + id: res.id + created: res.created + result: result + usage: res.usage + } + return chat_completion_result } diff --git a/lib/clients/openai/embeddings/README.md b/lib/clients/openai/embeddings/README.md new file mode 100644 index 00000000..bb97ccd5 --- /dev/null +++ b/lib/clients/openai/embeddings/README.md @@ -0,0 +1,17 @@ + +# Quick Example: Creating Embeddings + +```v + +import freeflowuniverse.herolib.clients.openai + +mut client:= openai.get()! //will be the default client, key is in `AIKEY` on environment variable or `OPENROUTER_API_KEY` + +text_to_embed := 'The quick brown fox jumps over the lazy dog.' + +resp := client.embeddings.create_embedding( + input: text_to_embed, + model: 'text-embedding-ada-002' +)! + +``` \ No newline at end of file diff --git a/lib/clients/openai/embeddings.v b/lib/clients/openai/embeddings/embeddings.v similarity index 81% rename from lib/clients/openai/embeddings.v rename to lib/clients/openai/embeddings/embeddings.v index d499a3ce..36ac097c 100644 --- a/lib/clients/openai/embeddings.v +++ b/lib/clients/openai/embeddings/embeddings.v @@ -1,6 +1,9 @@ -module openai +module embeddings import json +import freeflowuniverse.herolib.clients.openai { OpenAI, Usage } + +type OpenAIAlias = OpenAI pub enum EmbeddingModel { text_embedding_ada @@ -42,7 +45,7 @@ pub mut: usage Usage } -pub fn (mut f OpenAI) create_embeddings(args EmbeddingCreateArgs) !EmbeddingResponse { +pub fn (mut f OpenAIAlias) create_embeddings(args EmbeddingCreateArgs) !EmbeddingResponse { req := EmbeddingCreateRequest{ input: args.input model: embedding_model_str(args.model) diff --git a/lib/clients/openai/embeddings/embeddings_test.v b/lib/clients/openai/embeddings/embeddings_test.v new file mode 100644 index 00000000..200089a3 --- /dev/null +++ b/lib/clients/openai/embeddings/embeddings_test.v @@ -0,0 +1,24 @@ +module embeddings_test + +import os +import clients.openai +import clients.openai.embeddings +import clients.openai.openai_factory_ { get } +import freeflowuniverse.crystallib.osal { play } + +fn test_embeddings() { + key := os.getenv('OPENAI_API_KEY') + heroscript := '!!openai.configure api_key: "${key}"' + + play(heroscript: heroscript)! + + mut client := get()! + + res := client.create_embeddings( + input: ['The food was delicious and the waiter..'] + model: embeddings.EmbeddingModel.text_embedding_ada + )! + + assert res.data.len == 1 + assert res.data[0].embedding.len == 1536 +} diff --git a/lib/clients/openai/files/README.md b/lib/clients/openai/files/README.md new file mode 100644 index 00000000..f752610d --- /dev/null +++ b/lib/clients/openai/files/README.md @@ -0,0 +1,25 @@ + +# Example: Uploading a File + +```v +import freeflowuniverse.herolib.clients.openai + +mut client:= openai.get()! //will be the default client, key is in `AIKEY` on environment variable or `OPENROUTER_API_KEY` + +// Assuming you have a file named 'mydata.jsonl' in the same directory +// For a real application, handle file paths dynamically +file_path := 'mydata.jsonl' + +resp := client.files.upload_file( + file: file_path, + purpose: 'fine-tune' // or 'assistants', 'batch', 'vision' +) + +if resp.id.len > 0 { + println('File uploaded successfully with ID: ${resp.id}') +} else { + eprintln('Failed to upload file.') +} + + +``` \ No newline at end of file diff --git a/lib/clients/openai/files.v b/lib/clients/openai/files/files.v similarity index 82% rename from lib/clients/openai/files.v rename to lib/clients/openai/files/files.v index 2913ca0b..e4473bd2 100644 --- a/lib/clients/openai/files.v +++ b/lib/clients/openai/files/files.v @@ -1,9 +1,12 @@ -module openai +module files import json import freeflowuniverse.herolib.core.httpconnection import os import net.http +import freeflowuniverse.herolib.clients.openai { OpenAI } + +type OpenAIAlias = OpenAI const jsonl_mime_type = 'text/jsonl' @@ -44,7 +47,7 @@ pub mut: } // upload file to client org, usually used for fine tuning -pub fn (mut f OpenAI) upload_file(args FileUploadArgs) !File { +pub fn (mut f OpenAIAlias) upload_file(args FileUploadArgs) !File { file_content := os.read_file(args.filepath)! file_data := http.FileData{ @@ -74,28 +77,28 @@ pub fn (mut f OpenAI) upload_file(args FileUploadArgs) !File { } // list all files in client org -pub fn (mut f OpenAI) list_files() !Files { +pub fn (mut f OpenAIAlias) list_files() !Files { mut conn := f.connection()! r := conn.get(prefix: 'files')! return json.decode(Files, r)! } // deletes a file -pub fn (mut f OpenAI) delete_file(file_id string) !DeleteResp { +pub fn (mut f OpenAIAlias) delete_file(file_id string) !DeleteResp { mut conn := f.connection()! r := conn.delete(prefix: 'files/' + file_id)! return json.decode(DeleteResp, r)! } // returns a single file metadata -pub fn (mut f OpenAI) get_file(file_id string) !File { +pub fn (mut f OpenAIAlias) get_file(file_id string) !File { mut conn := f.connection()! r := conn.get(prefix: 'files/' + file_id)! return json.decode(File, r)! } // returns the content of a specific file -pub fn (mut f OpenAI) get_file_content(file_id string) !string { +pub fn (mut f OpenAIAlias) get_file_content(file_id string) !string { mut conn := f.connection()! r := conn.get(prefix: 'files/' + file_id + '/content')! return r diff --git a/lib/clients/openai/files/files_test.v b/lib/clients/openai/files/files_test.v new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/lib/clients/openai/files/files_test.v @@ -0,0 +1 @@ + diff --git a/lib/clients/openai/finetune/README.md b/lib/clients/openai/finetune/README.md new file mode 100644 index 00000000..31f2943f --- /dev/null +++ b/lib/clients/openai/finetune/README.md @@ -0,0 +1,22 @@ +# OpenAI Fine-tuning Client + + +```v +import freeflowuniverse.herolib.clients.openai + +mut client:= openai.get()! //will be the default client, key is in `AIKEY` on environment variable or `OPENROUTER_API_KEY` + +// Assuming you have a training file ID from the Files API +training_file_id := 'file-xxxxxxxxxxxxxxxxxxxxxxxxx' + +resp := client.finetune.create_fine_tune( + training_file: training_file_id, + model: 'gpt-3.5-turbo' +) + +if resp.id.len > 0 { + println('Fine-tuning job created with ID: ${resp.id}') +} else { + eprintln('Failed to create fine-tuning job.') +} +``` \ No newline at end of file diff --git a/lib/clients/openai/fine_tunes.v b/lib/clients/openai/finetune/fine_tunes.v similarity index 79% rename from lib/clients/openai/fine_tunes.v rename to lib/clients/openai/finetune/fine_tunes.v index 78a867f2..b71b8492 100644 --- a/lib/clients/openai/fine_tunes.v +++ b/lib/clients/openai/finetune/fine_tunes.v @@ -1,7 +1,11 @@ -module openai +module finetune +import freeflowuniverse.herolib.clients.openai { OpenAI } +import freeflowuniverse.herolib.clients.openai.files { File } import json +type OpenAIAlias = OpenAI + pub struct FineTune { pub: id string @@ -61,7 +65,7 @@ pub mut: } // creates a new fine-tune based on an already uploaded file -pub fn (mut f OpenAI) create_fine_tune(args FineTuneCreateArgs) !FineTune { +pub fn (mut f OpenAIAlias) create_fine_tune(args FineTuneCreateArgs) !FineTune { data := json.encode(args) mut conn := f.connection()! r := conn.post_json_str(prefix: 'fine-tunes', data: data)! @@ -70,28 +74,28 @@ pub fn (mut f OpenAI) create_fine_tune(args FineTuneCreateArgs) !FineTune { } // returns all fine-tunes in this account -pub fn (mut f OpenAI) list_fine_tunes() !FineTuneList { +pub fn (mut f OpenAIAlias) list_fine_tunes() !FineTuneList { mut conn := f.connection()! r := conn.get(prefix: 'fine-tunes')! return json.decode(FineTuneList, r)! } // get a single fine-tune information -pub fn (mut f OpenAI) get_fine_tune(fine_tune string) !FineTune { +pub fn (mut f OpenAIAlias) get_fine_tune(fine_tune string) !FineTune { mut conn := f.connection()! r := conn.get(prefix: 'fine-tunes/' + fine_tune)! return json.decode(FineTune, r)! } // cancel a fine-tune that didn't finish yet -pub fn (mut f OpenAI) cancel_fine_tune(fine_tune string) !FineTune { +pub fn (mut f OpenAIAlias) cancel_fine_tune(fine_tune string) !FineTune { mut conn := f.connection()! r := conn.post_json_str(prefix: 'fine-tunes/' + fine_tune + '/cancel')! return json.decode(FineTune, r)! } // returns all events for a fine tune in this account -pub fn (mut f OpenAI) list_fine_tune_events(fine_tune string) !FineTuneEventList { +pub fn (mut f OpenAIAlias) list_fine_tune_events(fine_tune string) !FineTuneEventList { mut conn := f.connection()! r := conn.get(prefix: 'fine-tunes/' + fine_tune + '/events')! return json.decode(FineTuneEventList, r)! diff --git a/lib/clients/openai/images/README.md b/lib/clients/openai/images/README.md new file mode 100644 index 00000000..a6d8890f --- /dev/null +++ b/lib/clients/openai/images/README.md @@ -0,0 +1,21 @@ + +# Example: Creating an Image + +```v + +import freeflowuniverse.herolib.clients.openai + +mut client:= openai.get()! //will be the default client, key is in `AIKEY` on environment variable or `OPENROUTER_API_KEY` + +resp := client.images.create_image( + prompt: 'A futuristic city at sunset', + n: 1, + size: '1024x1024' +) + +if resp.data.len > 0 { + println('Image created. URL: ${resp.data[0].url}') +} else { + eprintln('Failed to create image.') +} +``` diff --git a/lib/clients/openai/images.v b/lib/clients/openai/images/images.v similarity index 92% rename from lib/clients/openai/images.v rename to lib/clients/openai/images/images.v index 79994de5..bea8b001 100644 --- a/lib/clients/openai/images.v +++ b/lib/clients/openai/images/images.v @@ -4,6 +4,9 @@ import json import net.http import os import freeflowuniverse.herolib.core.httpconnection +import freeflowuniverse.herolib.clients.openai { OpenAI } + +type OpenAIAlias = OpenAI const image_mine_type = 'image/png' @@ -43,6 +46,7 @@ fn image_resp_type_str(i ImageRespType) string { } } +@[params] pub struct ImageCreateArgs { pub mut: prompt string @@ -95,7 +99,7 @@ pub mut: // Create new images generation given a prompt // the amount of images returned is specified by `num_images` -pub fn (mut f OpenAI) create_image(args ImageCreateArgs) !Images { +pub fn (mut f OpenAIAlias) create_image(args ImageCreateArgs) !Images { image_size := image_size_str(args.size) response_format := image_resp_type_str(args.format) request := ImageRequest{ @@ -115,7 +119,7 @@ pub fn (mut f OpenAI) create_image(args ImageCreateArgs) !Images { // image needs to be in PNG format and transparent or else a mask of the same size needs // to be specified to indicate where the image should be in the generated image // the amount of images returned is specified by `num_images` -pub fn (mut f OpenAI) create_edit_image(args ImageEditArgs) !Images { +pub fn (mut f OpenAIAlias) create_edit_image(args ImageEditArgs) !Images { image_content := os.read_file(args.image_path)! image_file := http.FileData{ filename: os.base(args.image_path) @@ -160,7 +164,7 @@ pub fn (mut f OpenAI) create_edit_image(args ImageEditArgs) !Images { // create variations of the given image // image needs to be in PNG format // the amount of images returned is specified by `num_images` -pub fn (mut f OpenAI) create_variation_image(args ImageVariationArgs) !Images { +pub fn (mut f OpenAIAlias) create_variation_image(args ImageVariationArgs) !Images { image_content := os.read_file(args.image_path)! image_file := http.FileData{ filename: os.base(args.image_path) diff --git a/lib/clients/openai/model_enums.v b/lib/clients/openai/model_enums.v deleted file mode 100644 index 7b3be146..00000000 --- a/lib/clients/openai/model_enums.v +++ /dev/null @@ -1,101 +0,0 @@ -module openai - -pub enum RoleType { - system - user - assistant - function -} - -fn roletype_str(x RoleType) string { - return match x { - .system { - 'system' - } - .user { - 'user' - } - .assistant { - 'assistant' - } - .function { - 'function' - } - } -} - -pub enum Voice { - alloy - ash - coral - echo - fable - onyx - nova - sage - shimmer -} - -fn voice_str(x Voice) string { - return match x { - .alloy { - 'alloy' - } - .ash { - 'ash' - } - .coral { - 'coral' - } - .echo { - 'echo' - } - .fable { - 'fable' - } - .onyx { - 'onyx' - } - .nova { - 'nova' - } - .sage { - 'sage' - } - .shimmer { - 'shimmer' - } - } -} - -pub enum AudioFormat { - mp3 - opus - aac - flac - wav - pcm -} - -fn audio_format_str(x AudioFormat) string { - return match x { - .mp3 { - 'mp3' - } - .opus { - 'opus' - } - .aac { - 'aac' - } - .flac { - 'flac' - } - .wav { - 'wav' - } - .pcm { - 'pcm' - } - } -} diff --git a/lib/clients/openai/model_models_completion.v b/lib/clients/openai/model_models_completion.v new file mode 100644 index 00000000..c71b0d0c --- /dev/null +++ b/lib/clients/openai/model_models_completion.v @@ -0,0 +1,31 @@ +module openai + +struct ChatCompletionRaw { +mut: + id string + object string + created u32 + choices []ChoiceRaw + usage Usage +} + +struct ChoiceRaw { +mut: + index int + message MessageRaw + finish_reason string +} + +struct MessageRaw { +mut: + role string + content string +} + +struct ChatMessagesRaw { +mut: + model string + messages []MessageRaw + temperature f64 = 0.5 + max_completion_tokens int = 32000 +} diff --git a/lib/clients/openai/models.v b/lib/clients/openai/model_models_raw.v similarity index 100% rename from lib/clients/openai/models.v rename to lib/clients/openai/model_models_raw.v diff --git a/lib/clients/openai/moderation/README.md b/lib/clients/openai/moderation/README.md new file mode 100644 index 00000000..a8e428e0 --- /dev/null +++ b/lib/clients/openai/moderation/README.md @@ -0,0 +1,30 @@ +# OpenAI Moderation Client + +This directory contains the V client for OpenAI's Moderation API. + + +```v + +import freeflowuniverse.herolib.clients.openai + +mut client:= openai.get()! //will be the default client, key is in `AIKEY` on environment variable or `OPENROUTER_API_KEY` + +text_to_moderate := 'I want to kill them all.' + +resp := client.moderation.create_moderation( + input: text_to_moderate +) + +if resp.results.len > 0 { + if resp.results[0].flagged { + println('Text was flagged for moderation.') + println('Categories: ${resp.results[0].categories}') + } else { + println('Text passed moderation.') + } +} else { + eprintln('Failed to get moderation result.') +} + +``` + diff --git a/lib/clients/openai/moderation.v b/lib/clients/openai/moderation/moderation.v similarity index 90% rename from lib/clients/openai/moderation.v rename to lib/clients/openai/moderation/moderation.v index 19015b72..be2ab46b 100644 --- a/lib/clients/openai/moderation.v +++ b/lib/clients/openai/moderation/moderation.v @@ -1,6 +1,9 @@ -module openai +module moderation import json +import freeflowuniverse.herolib.clients.openai { OpenAI } + +type OpenAIAlias = OpenAI pub enum ModerationModel { text_moderation_latest @@ -69,7 +72,7 @@ pub mut: results []ModerationResult } -pub fn (mut f OpenAI) create_moderation(input string, model ModerationModel) !ModerationResponse { +pub fn (mut f OpenAIAlias) create_moderation(input string, model ModerationModel) !ModerationResponse { req := ModerationRequest{ input: input model: moderation_model_str(model) diff --git a/lib/clients/openai/openai_model.v b/lib/clients/openai/openai_model.v index a8d67cf3..4b383735 100644 --- a/lib/clients/openai/openai_model.v +++ b/lib/clients/openai/openai_model.v @@ -8,57 +8,42 @@ pub const version = '0.0.0' const singleton = false 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 OpenAIBase { -// pub mut: -// name string = 'default' -// api_key string -// url string -// model_default string -// } - @[heap] pub struct OpenAI { pub mut: name string = 'default' api_key string - url string - model_default string + url string = 'https://openrouter.ai/api/v1' + model_default string = 'gpt-oss-120b' conn ?&httpconnection.HTTPConnection @[skip; str: skip] } // your checking & initialization code if needed fn obj_init(mycfg_ OpenAI) !OpenAI { mut mycfg := mycfg_ + if mycfg.model_default == '' { + k := os.getenv('AIMODEL') + if k != '' { + mycfg.model_default = k + } + } + + if mycfg.url == '' { + k := os.getenv('AIURL') + if k != '' { + mycfg.url = k + } + } if mycfg.api_key == '' { - mut k := os.getenv('AIKEY') + k := os.getenv('AIKEY') if k != '' { mycfg.api_key = k - k = os.getenv('AIURL') - if k != '' { - mycfg.url = k - } else { - return error('found AIKEY in env, but not AIURL') - } - k = os.getenv('AIMODEL') - if k != '' { - mycfg.model_default = k - } - return mycfg } - mycfg.url = 'https://api.openai.com/v1/models' - k = os.getenv('OPENAI_API_KEY') - if k != '' { - mycfg.api_key = k - return mycfg - } - k = os.getenv('OPENROUTER_API_KEY') - if k != '' { - mycfg.api_key = k - mycfg.url = 'https://openrouter.ai/api/v1' - return mycfg + if k == '' { + k2 := os.getenv('OPENROUTER_API_KEY') + if k2 != '' { + mycfg.api_key = k2 + } } } return mycfg diff --git a/lib/clients/openai/readme.md b/lib/clients/openai/readme.md index 64825b53..4342562c 100644 --- a/lib/clients/openai/readme.md +++ b/lib/clients/openai/readme.md @@ -4,21 +4,27 @@ To get started ```vlang - - import freeflowuniverse.herolib.clients.openai +import freeflowuniverse.herolib.core.playcmds +playcmds.run( + heroscript:' + !!openai.configure name: "default" key: "" url: "https://openrouter.ai/api/v1" model_default: "gpt-oss-120b" + ' + heroscript_path:'' + reset: false +)! + +//name:'default' is the default, you can change it to whatever you want mut client:= openai.get()! -client... - - - +mut r:=client.chat_completion( + model: "gpt-3.5-turbo", + message: 'Hello, world!' + temperature: 0.5 + max_completion_tokens: 1024 +)! ``` -## example heroscript - -```hero -!!openai.configure key -``` +if key empty then will try to get it from environment variable `AIKEY` or `OPENROUTER_API_KEY` diff --git a/lib/clients/openai/testdata/testfile.txt b/lib/clients/openai/testdata/testfile.txt deleted file mode 100644 index 95d09f2b..00000000 --- a/lib/clients/openai/testdata/testfile.txt +++ /dev/null @@ -1 +0,0 @@ -hello world \ No newline at end of file diff --git a/lib/clients/openai/testdata/testfile2.txt b/lib/clients/openai/testdata/testfile2.txt deleted file mode 100644 index dda5b160..00000000 --- a/lib/clients/openai/testdata/testfile2.txt +++ /dev/null @@ -1 +0,0 @@ -testfile2 content \ No newline at end of file diff --git a/lib/core/herocmds/docusaurus.v b/lib/core/herocmds/docusaurus.v index dc5e4930..0ee23736 100644 --- a/lib/core/herocmds/docusaurus.v +++ b/lib/core/herocmds/docusaurus.v @@ -163,68 +163,47 @@ fn cmd_docusaurus_execute(cmd Command) ! { mut builddevpublish := cmd.flags.get_bool('builddevpublish') or { false } mut dev := cmd.flags.get_bool('dev') or { false } - // Process heroscript files from the ebook directory to get proper site configuration - mut plbook := playbook.new(path: heroscript_config_dir)! - // Dynamically add ebook configuration files - config_file := '${heroscript_config_dir}/config.heroscript' - if os.exists(config_file) { - plbook.add(path: config_file)! - } + // // Get the site configuration that was processed from the heroscript files + // // The site.play() function processes the heroscript and creates sites in the global websites map + // // We need to get the site by name from the processed configuration + // config_actions := plbook.find(filter: 'site.config')! + // if config_actions.len == 0 { + // return error('No site.config found in heroscript files. Make sure config.heroscript contains !!site.config.') + // } - menus_file := '${heroscript_config_dir}/menus.heroscript' - if os.exists(menus_file) { - plbook.add(path: menus_file)! - } + // // Get the site name from the first site.config action + // site_name := config_actions[0].params.get('name') or { + // return error('site.config must specify a name parameter') + // } - pages_dir := '${heroscript_config_dir}/pages' - if os.exists(pages_dir) && os.is_dir(pages_dir) { - plbook.add(path: pages_dir)! - } + // // Get the processed site configuration + // mut generic_site := site.get(name: site_name)! - playcmds.run(plbook: plbook)! + // // Add docusaurus site + // mut dsite := docusaurus.dsite_add( + // site: generic_site + // path_src: url // Use URL as source path for now + // path_build: build_path + // path_publish: publish_path + // reset: false + // template_update: update + // install: init + // )! - // Process site pages to generate the actual documentation files - site.play(mut plbook)! + // // Conditional site actions based on flags + // if buildpublish { + // dsite.build_publish()! + // } else if builddevpublish { + // dsite.build_dev_publish()! + // } else if dev { + // dsite.dev(host: 'localhost', port: 3000, open: open)! + // } else if open { + // dsite.open('localhost', 3000)! + // } else { + // // If no specific action (build/dev/open) is requested, just generate the site + // dsite.generate()! + // } - // Get the site configuration that was processed from the heroscript files - // The site.play() function processes the heroscript and creates sites in the global websites map - // We need to get the site by name from the processed configuration - config_actions := plbook.find(filter: 'site.config')! - if config_actions.len == 0 { - return error('No site.config found in heroscript files. Make sure config.heroscript contains !!site.config.') - } - - // Get the site name from the first site.config action - site_name := config_actions[0].params.get('name') or { - return error('site.config must specify a name parameter') - } - - // Get the processed site configuration - mut generic_site := site.get(name: site_name)! - - // Add docusaurus site - mut dsite := docusaurus.add( - site: generic_site - path_src: url // Use URL as source path for now - path_build: build_path - path_publish: publish_path - reset: false - template_update: update - install: init - )! - - // Conditional site actions based on flags - if buildpublish { - dsite.build_publish()! - } else if builddevpublish { - dsite.build_dev_publish()! - } else if dev { - dsite.dev(host: 'localhost', port: 3000, open: open)! - } else if open { - dsite.open('localhost', 3000)! - } else { - // If no specific action (build/dev/open) is requested, just generate the site - dsite.generate()! - } + panic("implement") } diff --git a/lib/core/herocmds/starlight.v b/lib/core/herocmds/starlight.v index 2bf42ac4..80a0569d 100644 --- a/lib/core/herocmds/starlight.v +++ b/lib/core/herocmds/starlight.v @@ -2,7 +2,7 @@ module herocmds // import freeflowuniverse.herolib.web.starlight import os -import cli { Command, Flag } +import cli // pub fn cmd_starlight(mut cmdroot Command) { // mut cmd_run := Command{ diff --git a/lib/core/playbook/find.v b/lib/core/playbook/find.v index 15a780aa..893c6e27 100644 --- a/lib/core/playbook/find.v +++ b/lib/core/playbook/find.v @@ -92,7 +92,6 @@ pub fn (mut plbook PlayBook) find(args FindArgs) ![]&Action { return res } - pub fn (mut plbook PlayBook) exists_once(args FindArgs) bool { mut res := plbook.find(args) or { [] } return res.len == 1 diff --git a/lib/core/playbook/playbook.v b/lib/core/playbook/playbook.v index 9786ce12..9d6873b4 100644 --- a/lib/core/playbook/playbook.v +++ b/lib/core/playbook/playbook.v @@ -73,7 +73,7 @@ pub fn (mut plbook PlayBook) actions_sorted(args SortArgs) ![]&Action { } action_ids := plbook.priorities[nr] or { panic('bug') } for id in action_ids { - mut a := plbook.actions[id-1] or { panic("bug in actions sorted") } + mut a := plbook.actions[id - 1] or { panic('bug in actions sorted') } res << a } } @@ -99,7 +99,7 @@ pub fn (mut plbook PlayBook) heroscript(args HeroScriptArgs) !string { // out += '${plbook.othertext}' // } out = texttools.remove_empty_js_blocks(out) - out +="\n\n" + out += '\n\n' return out } diff --git a/lib/core/playbook/playbook_add.v b/lib/core/playbook/playbook_add.v index 8d930a44..649f249f 100644 --- a/lib/core/playbook/playbook_add.v +++ b/lib/core/playbook/playbook_add.v @@ -17,8 +17,8 @@ pub fn (mut plbook PlayBook) add(args_ PlayBookNewArgs) ! { if args.git_url.len > 0 { mut git_path_args := gittools.GitPathGetArgs{ - git_url: args.git_url - git_pull: args.git_pull + git_url: args.git_url + git_pull: args.git_pull git_reset: args.git_reset } args.path = gittools.path(git_path_args)!.path diff --git a/lib/core/playbook/readme.md b/lib/core/playbook/readme.md index 7c9b6a4e..122056ce 100644 --- a/lib/core/playbook/readme.md +++ b/lib/core/playbook/readme.md @@ -1,7 +1,6 @@ # heroscript -is our small language which allows us to run parser - +Our heroscript is a simple way to execute commands in a playbook. It allows you to define a series of actions that can be executed in sequence, making it easy to automate tasks and workflows. ## execute a playbook @@ -25,7 +24,6 @@ playcmds.run(mut plbook)! ``` - ## execute a heroscript and make executable ```bash @@ -39,54 +37,8 @@ playcmds.run(mut plbook)! you can now just execute this script and hero will interprete the content -## parser -are text based representatsions of parser which need to be executed - -example - -```js -!!tflibrary.circlesmanager.circle_add - gitsource:'books' - path:'technology/src' - name:technology -``` - -the first one is the action, the rest are the params - -```v -import freeflowuniverse.herolib.core.playbook - -mut plbook := playbook.new(text: "....")! - -``` -## way how to use for a module - - -```v -import freeflowuniverse.herolib.core.playbook - -// !!hr.employee_define -// descr:'Junior Engineer' -// growth:'1:5,60:30' cost:'4000USD' indexation:'5%' -// department:'engineering' - - -// populate the params for hr -fn (mut m BizModel) hr_actions(actions playbook.PlayBook) ! { - mut actions2 := actions.find('hr.*,vm.start')! - for action in actions2 { - if action.name == 'employee_define' { - mut name := action.params.get_default('name', '')! - mut descr := action.params.get_default('descr', '')! - //... - } - } -} -``` - - -## we can also use the filtersort +## filtersort ```v diff --git a/lib/core/playcmds/factory.v b/lib/core/playcmds/factory.v index 2f326b1d..44894efd 100644 --- a/lib/core/playcmds/factory.v +++ b/lib/core/playcmds/factory.v @@ -5,6 +5,9 @@ import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.data.doctree import freeflowuniverse.herolib.biz.bizmodel import freeflowuniverse.herolib.web.docusaurus +import freeflowuniverse.herolib.clients.openai + + // import freeflowuniverse.herolib.hero.publishing // import freeflowuniverse.herolib.threefold.grid4.gridsimulator // import freeflowuniverse.herolib.installers.sysadmintools.daguserver @@ -48,6 +51,7 @@ pub fn run(args_ PlayArgs) ! { bizmodel.play(mut plbook)! doctree.play(mut plbook)! docusaurus.play(mut plbook)! + openai.play(mut plbook)! // slides.play(mut plbook)! // base_install(play(mut plbook)! diff --git a/lib/core/playcmds/play_git.v b/lib/core/playcmds/play_git.v index 02ce2dbe..ed3b8ac1 100644 --- a/lib/core/playcmds/play_git.v +++ b/lib/core/playcmds/play_git.v @@ -1,11 +1,10 @@ module playcmds import freeflowuniverse.herolib.develop.gittools -import freeflowuniverse.herolib.core.playbook {PlayBook} +import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console fn play_git(mut plbook PlayBook) ! { - // Handle !!git.define action first to configure GitStructure define_actions := plbook.find(filter: 'git.define')! mut gs := if define_actions.len > 0 { @@ -150,6 +149,4 @@ fn play_git(mut plbook PlayBook) ! { gs = gittools.new(coderoot: coderoot)! gs.load(true)! // Force reload } - - } diff --git a/lib/core/playcmds/play_luadns.v b/lib/core/playcmds/play_luadns.v index 20da42ba..eb574b83 100644 --- a/lib/core/playcmds/play_luadns.v +++ b/lib/core/playcmds/play_luadns.v @@ -1,11 +1,10 @@ module playcmds import freeflowuniverse.herolib.develop.luadns -import freeflowuniverse.herolib.core.playbook {PlayBook} +import freeflowuniverse.herolib.core.playbook { PlayBook } // import os fn play_luadns(mut plbook PlayBook) ! { - // Variables below are not used, commenting them out // mut buildroot := '${os.home_dir()}/hero/var/mdbuild' // mut publishroot := '${os.home_dir()}/hero/www/info' @@ -34,6 +33,4 @@ fn play_luadns(mut plbook PlayBook) ! { dns.set_domain(domain, ip)! action.done = true } - - } diff --git a/lib/core/playcmds/play_ssh.v b/lib/core/playcmds/play_ssh.v index ea621275..cafafc52 100644 --- a/lib/core/playcmds/play_ssh.v +++ b/lib/core/playcmds/play_ssh.v @@ -1,10 +1,9 @@ module playcmds import freeflowuniverse.herolib.osal.sshagent -import freeflowuniverse.herolib.core.playbook {PlayBook} +import freeflowuniverse.herolib.core.playbook { PlayBook } fn play_ssh(mut plbook PlayBook) ! { - mut agent := sshagent.new()! for mut action in plbook.find(filter: 'sshagent.*')! { mut p := action.params @@ -20,5 +19,4 @@ fn play_ssh(mut plbook PlayBook) ! { } action.done = true } - } diff --git a/lib/core/playcmds/readme.md b/lib/core/playcmds/readme.md index f6fdedff..4dc12068 100644 --- a/lib/core/playcmds/readme.md +++ b/lib/core/playcmds/readme.md @@ -1,49 +1,13 @@ # how to use the playcmds ```v -import freeflowuniverse.herolib.core.playbook -import freeflowuniverse.herolib.core.base +import freeflowuniverse.herolib.core.playcmds -mut s:=base.session_new( - coderoot:'/tmp/code' - interactive:true +playcmds.run( + heroscript:'' + heroscript_path:'' + reset: false + //plbook ?PlayBook )! - -// Path to the code execution directory -path string - -// Command text to execute (e.g., "ls -la") -text string - -// Git repository URL for version control -git_url string - -// Pull latest changes from git -git_pull bool - -// Git branch to use -git_branch string - -// Reset repository before pull -git_reset bool - -// Execute command after setup -execute bool = true - -// Optional session object for state management -session ?&base.Session - -mut plbook := playbook.new(text: "....",session:s) or { panic(err) } - - - - - ``` - -## some core play commands - -```heroscript - -``` \ No newline at end of file diff --git a/lib/core/playmacros/playmacros.v b/lib/core/playmacros/playmacros.v index b9b85661..f00784f8 100644 --- a/lib/core/playmacros/playmacros.v +++ b/lib/core/playmacros/playmacros.v @@ -1,7 +1,7 @@ module playmacros import freeflowuniverse.herolib.ui.console -import freeflowuniverse.herolib.core.playbook {PlayBook, Action} +import freeflowuniverse.herolib.core.playbook { Action, PlayBook } import freeflowuniverse.herolib.threefold.grid4.gridsimulator import freeflowuniverse.herolib.threefold.grid4.farmingsimulator import freeflowuniverse.herolib.biz.bizmodel diff --git a/lib/data/encoderhero/decoder.v b/lib/data/encoderhero/decoder.v index 57a92ca3..66e164b7 100644 --- a/lib/data/encoderhero/decoder.v +++ b/lib/data/encoderhero/decoder.v @@ -19,7 +19,7 @@ fn decode_struct[T](_ T, data string) !T { mut typ := T{} // println(data) $if T is $struct { - obj_name := texttools.snake_case(T.name.all_after_last('.')) + obj_name := T.name.all_after_last('.').to_lower() mut action_name := '${obj_name}.define' if !data.contains(action_name) { action_name = '${obj_name}.configure' diff --git a/lib/data/encoderhero/encoder.v b/lib/data/encoderhero/encoder.v index 1a999cc4..00463b22 100644 --- a/lib/data/encoderhero/encoder.v +++ b/lib/data/encoderhero/encoder.v @@ -118,7 +118,7 @@ pub fn (mut e Encoder) encode_struct[T](t T) ! { mut mytype := reflection.type_of[T](t) struct_attrs := attrs_get_reflection(mytype) - mut action_name := texttools.snake_case(T.name.all_after_last('.')) + mut action_name := T.name.all_after_last('.').to_lower() // println('action_name: ${action_name} ${T.name}') if 'alias' in struct_attrs { action_name = struct_attrs['alias'].to_lower() diff --git a/lib/data/encoderhero/postgres_client_decoder_test.v b/lib/data/encoderhero/postgres_client_decoder_test.v index b38b8ecb..eddd8792 100644 --- a/lib/data/encoderhero/postgres_client_decoder_test.v +++ b/lib/data/encoderhero/postgres_client_decoder_test.v @@ -1,6 +1,5 @@ module encoderhero - pub struct PostgresqlClient { pub mut: name string = 'default' @@ -11,14 +10,13 @@ pub mut: dbname string = 'postgres' } - const postgres_client_blank = '!!postgresql_client.configure' const postgres_client_full = '!!postgresql_client.configure name:production user:app_user port:5433 host:db.example.com password:secret123 dbname:myapp' const postgres_client_partial = '!!postgresql_client.configure name:dev host:localhost password:devpass' -const postgres_client_complex = " +const postgres_client_complex = ' !!postgresql_client.configure name:staging user:stage_user port:5434 host:staging.db.com password:stagepass dbname:stagingdb -" +' fn test_postgres_client_decode_blank() ! { mut client := decode[PostgresqlClient](postgres_client_blank)! @@ -63,14 +61,14 @@ fn test_postgres_client_decode_complex() ! { fn test_postgres_client_encode_decode_roundtrip() ! { // Test encoding and decoding roundtrip original := PostgresqlClient{ - name: 'testdb' - user: 'testuser' - port: 5435 - host: 'test.host.com' + name: 'testdb' + user: 'testuser' + port: 5435 + host: 'test.host.com' password: 'testpass123' - dbname: 'testdb' + dbname: 'testdb' } - + // Encode to heroscript encoded := encode[PostgresqlClient](original)! @@ -78,10 +76,10 @@ fn test_postgres_client_encode_decode_roundtrip() ! { // if true { // panic("sss") // } - + // Decode back from heroscript decoded := decode[PostgresqlClient](encoded)! - + // Verify roundtrip assert decoded.name == original.name assert decoded.user == original.user @@ -95,35 +93,35 @@ fn test_postgres_client_encode() ! { // Test encoding with different configurations test_cases := [ PostgresqlClient{ - name: 'minimal' - user: 'root' - port: 5432 - host: 'localhost' + name: 'minimal' + user: 'root' + port: 5432 + host: 'localhost' password: '' - dbname: 'postgres' + dbname: 'postgres' }, PostgresqlClient{ - name: 'full_config' - user: 'admin' - port: 5433 - host: 'remote.server.com' + name: 'full_config' + user: 'admin' + port: 5433 + host: 'remote.server.com' password: 'securepass' - dbname: 'production' + dbname: 'production' }, PostgresqlClient{ - name: 'localhost_dev' - user: 'dev' - port: 5432 - host: '127.0.0.1' + name: 'localhost_dev' + user: 'dev' + port: 5432 + host: '127.0.0.1' password: 'devpassword' - dbname: 'devdb' - } + dbname: 'devdb' + }, ] - + for client in test_cases { encoded := encode[PostgresqlClient](client)! decoded := decode[PostgresqlClient](encoded)! - + assert decoded.name == client.name assert decoded.user == client.user assert decoded.port == client.port @@ -134,7 +132,7 @@ fn test_postgres_client_encode() ! { } // Play script for interactive testing -const play_script = " +const play_script = ' # PostgresqlClient Encode/Decode Play Script # This script demonstrates encoding and decoding PostgresqlClient configurations @@ -149,35 +147,35 @@ const play_script = " # Default configuration (all defaults) !!postgresql_client.configure -" +' fn test_play_script() ! { // Test the play script with multiple configurations lines := play_script.split_into_lines().filter(fn (line string) bool { return line.trim(' ') != '' && !line.starts_with('#') }) - + mut clients := []PostgresqlClient{} - + for line in lines { if line.starts_with('!!postgresql_client.configure') { client := decode[PostgresqlClient](line)! clients << client } } - + assert clients.len == 3 - + // First client: full configuration assert clients[0].name == 'playground' assert clients[0].user == 'play_user' assert clients[0].port == 5432 - + // Second client: partial configuration assert clients[1].name == 'quick_test' assert clients[1].host == '127.0.0.1' assert clients[1].user == 'root' // default - + // Third client: defaults only assert clients[2].name == 'default' assert clients[2].host == 'localhost' @@ -188,48 +186,48 @@ fn test_play_script() ! { pub fn run_play_script() ! { println('=== PostgresqlClient Encode/Decode Play Script ===') println('Testing encoding and decoding of PostgresqlClient configurations...') - + // Test 1: Basic encoding println('\n1. Testing basic encoding...') client := PostgresqlClient{ - name: 'example' - user: 'example_user' - port: 5432 - host: 'example.com' + name: 'example' + user: 'example_user' + port: 5432 + host: 'example.com' password: 'example_pass' - dbname: 'example_db' + dbname: 'example_db' } - + encoded := encode[PostgresqlClient](client)! println('Encoded: ${encoded}') - + decoded := decode[PostgresqlClient](encoded)! println('Decoded name: ${decoded.name}') println('Decoded host: ${decoded.host}') - + // Test 2: Play script println('\n2. Testing play script...') test_play_script()! println('Play script test passed!') - + // Test 3: Edge cases println('\n3. Testing edge cases...') edge_client := PostgresqlClient{ - name: 'edge' - user: '' - port: 0 - host: '' + name: 'edge' + user: '' + port: 0 + host: '' password: '' - dbname: '' + dbname: '' } - + edge_encoded := encode[PostgresqlClient](edge_client)! edge_decoded := decode[PostgresqlClient](edge_encoded)! - + assert edge_decoded.name == 'edge' assert edge_decoded.user == '' assert edge_decoded.port == 0 println('Edge cases test passed!') - + println('\n=== All tests completed successfully! ===') -} \ No newline at end of file +} diff --git a/lib/data/markdown/elements/element_def.v b/lib/data/markdown/elements/element_def.v index 5a4581e2..3858aab3 100644 --- a/lib/data/markdown/elements/element_def.v +++ b/lib/data/markdown/elements/element_def.v @@ -1,7 +1,6 @@ module elements - -//TODO: def is broken, way how we do it is bad +// TODO: def is broken, way how we do it is bad @[heap] pub struct Def { diff --git a/lib/data/markdown/elements/element_paragraph.v b/lib/data/markdown/elements/element_paragraph.v index 9705cc45..699f2daf 100644 --- a/lib/data/markdown/elements/element_paragraph.v +++ b/lib/data/markdown/elements/element_paragraph.v @@ -39,7 +39,7 @@ fn (self Paragraph) html() !string { mut out := self.DocBase.html()! // the children should have all the content if self.children.len == 1 { if out.trim_space() != '' { - if self.children[0] or { panic("bug") } is Link { + if self.children[0] or { panic('bug') } is Link { return out } else { return out diff --git a/lib/data/paramsparser/params_reflection_test.v b/lib/data/paramsparser/params_reflection_test.v index 21fe7e16..d837fca6 100644 --- a/lib/data/paramsparser/params_reflection_test.v +++ b/lib/data/paramsparser/params_reflection_test.v @@ -26,13 +26,13 @@ struct TestChild { } const test_child = TestChild{ - child_name: 'test_child' - child_number: 3 - child_yesno: false - child_liststr: ['three', 'four'] - child_listint: [3, 4] + child_name: 'test_child' + child_number: 3 + child_yesno: false + child_liststr: ['three', 'four'] + child_listint: [3, 4] child_listbool: [true, false] - child_listu32: [u32(5), u32(6)] + child_listu32: [u32(5), u32(6)] } const test_struct = TestStruct{ @@ -51,7 +51,6 @@ const test_struct = TestStruct{ child: test_child } - const test_child_params = Params{ params: [ Param{ @@ -116,7 +115,6 @@ const test_params = Params{ }] } - fn test_encode_struct() { encoded_struct := encode[TestStruct](test_struct)! assert encoded_struct == test_params diff --git a/lib/develop/gittools/factory.v b/lib/develop/gittools/factory.v index 0fbe2978..638d4983 100644 --- a/lib/develop/gittools/factory.v +++ b/lib/develop/gittools/factory.v @@ -8,24 +8,23 @@ __global ( gsinstances map[string]&GitStructure ) - @[params] -pub struct GetRepoArgs{ +pub struct GetRepoArgs { pub mut: - path string //if used will check if path exists if yes, just return - git_url string - git_pull bool + path string // if used will check if path exists if yes, just return + git_url string + git_pull bool git_reset bool } // get_repo_path implements the GitUrlResolver interface -pub fn get_repo_path(args GetRepoArgs) !string { - if os.exists(args.path){ +pub fn get_repo_path(args GetRepoArgs) !string { + if os.exists(args.path) { return args.path } mut gs := get()! mut repo := gs.get_repo( - url: args.git_url + url: args.git_url pull: args.git_pull reset: args.git_reset )! diff --git a/lib/develop/gittools/gittools_do.v b/lib/develop/gittools/gittools_do.v index 5d9423ca..ac957a6d 100644 --- a/lib/develop/gittools/gittools_do.v +++ b/lib/develop/gittools/gittools_do.v @@ -45,9 +45,9 @@ pub fn (mut gs GitStructure) do(args_ ReposActionsArgs) !string { mut args := args_ // console.print_debug('git do ${args.cmd}') - if args.path == '' && args.url == '' && args.repo == '' && args.account == '' && args.provider == '' && args.filter == ''{ + if args.path == '' && args.url == '' && args.repo == '' && args.account == '' + && args.provider == '' && args.filter == '' { args.path = os.getwd() - } if args.path != '' { @@ -84,7 +84,7 @@ pub fn (mut gs GitStructure) do(args_ ReposActionsArgs) !string { )! // reset the status for the repo - if args.reload || args.cmd == 'reload'{ + if args.reload || args.cmd == 'reload' { for mut repo in repos { repo.cache_last_load_clear()! } diff --git a/lib/develop/gittools/repository.v b/lib/develop/gittools/repository.v index 832ac6be..8a0ff23b 100644 --- a/lib/develop/gittools/repository.v +++ b/lib/develop/gittools/repository.v @@ -85,9 +85,7 @@ pub fn (mut repo GitRepo) commit(msg string) ! { return error('Commit message is empty.') } repo_path := repo.path() - repo.exec('git add . -A') or { - return error('Cannot add to repo: ${repo_path}. Error: ${err}') - } + repo.exec('git add . -A') or { return error('Cannot add to repo: ${repo_path}. Error: ${err}') } repo.exec('git commit -m "${msg}"') or { return error('Cannot commit repo: ${repo_path}. Error: ${err}') } diff --git a/lib/develop/gittools/repository_info.v b/lib/develop/gittools/repository_info.v index 21e0aad3..84c2a47b 100644 --- a/lib/develop/gittools/repository_info.v +++ b/lib/develop/gittools/repository_info.v @@ -136,5 +136,5 @@ pub fn (self GitRepo) get_last_local_commit() !string { if self.status_local.branch in self.status_local.branches { return self.status_local.branches[self.status_local.branch] } - return "" + return '' } diff --git a/lib/hero/db/hero_db/hero_db.v b/lib/hero/db/hero_db/hero_db.v index c71cc282..15fe21fc 100644 --- a/lib/hero/db/hero_db/hero_db.v +++ b/lib/hero/db/hero_db/hero_db.v @@ -8,7 +8,7 @@ import freeflowuniverse.herolib.core.texttools // Generic database interface for Hero root objects pub struct HeroDB[T] { pub mut: - db pg.DB + db pg.DB table_name string } @@ -22,14 +22,12 @@ pub fn new[T]() !HeroDB[T] { table_name = '${dirname}_${texttools.snake_case(T.name)}' } - mut dbclient:=postgresql_client.get()! + mut dbclient := postgresql_client.get()! + + mut dbcl := dbclient.db() or { return error('Failed to connect to database') } - mut dbcl:=dbclient.db() or { - return error('Failed to connect to database') - } - return HeroDB[T]{ - db: dbcl + db: dbcl table_name: table_name } } @@ -38,13 +36,13 @@ pub fn new[T]() !HeroDB[T] { pub fn (mut self HeroDB[T]) ensure_table() ! { // Get index fields from struct reflection index_fields := self.get_index_fields() - + // Build index column definitions mut index_cols := []string{} for field in index_fields { index_cols << '${field} varchar(255)' } - + // Create table with JSON storage create_sql := ' CREATE TABLE IF NOT EXISTS ${self.table_name} ( @@ -54,11 +52,9 @@ pub fn (mut self HeroDB[T]) ensure_table() ! { created_at timestamp DEFAULT CURRENT_TIMESTAMP, updated_at timestamp DEFAULT CURRENT_TIMESTAMP ) - ' - - // self.db.exec(create_sql)! - + ' // self.db.exec(create_sql)! // Create indexes on index fields + for field in index_fields { index_sql := 'CREATE INDEX IF NOT EXISTS idx_${self.table_name}_${field} ON ${self.table_name}(${field})' // self.db.exec(index_sql)! @@ -80,22 +76,22 @@ fn (self HeroDB[T]) get_index_fields() []string { pub fn (mut self HeroDB[T]) save(obj T) ! { // Get index values from object index_data := self.extract_index_values(obj) - + // Serialize to JSON json_data := json.encode_pretty(obj) - + // Check if object already exists mut query := 'SELECT id FROM ${self.table_name} WHERE ' mut params := []string{} - + // Build WHERE clause for unique lookup for key, value in index_data { params << '${key} = \'${value}\'' } query += params.join(' AND ') - - existing :=self.db.exec(query)! - + + existing := self.db.exec(query)! + if existing.len > 0 { // Update existing record id_val := existing[0].vals[0] or { return error('no id') } @@ -114,22 +110,21 @@ pub fn (mut self HeroDB[T]) save(obj T) ! { // Insert new record mut columns := []string{} mut values := []string{} - + // Add index columns for key, value in index_data { columns << key values << "'${value}'" } - + // Add JSON data columns << 'data' values << "'${json_data}'" - + insert_sql := ' INSERT INTO ${self.table_name} (${columns.join(', ')}) VALUES (${values.join(', ')}) - ' - // self.db.exec(insert_sql)! + ' // self.db.exec(insert_sql)! } } @@ -137,7 +132,7 @@ pub fn (mut self HeroDB[T]) save(obj T) ! { pub fn (mut self HeroDB[T]) get_by_index(index_values map[string]string) !T { mut query := 'SELECT data FROM ${self.table_name} WHERE ' mut params := []string{} - + for key, value in index_values { params << '${key} = \'${value}\'' } @@ -147,16 +142,16 @@ pub fn (mut self HeroDB[T]) get_by_index(index_values map[string]string) !T { if rows.len == 0 { return error('${T.name} not found with index values: ${index_values}') } - + json_data_val := rows[0].vals[0] or { return error('no data') } println('json_data_val: ${json_data_val}') - if true{ + if true { panic('sd2221') } // mut obj := json.decode(T, json_data_val) or { // return error('Failed to decode JSON: ${err}') // } - + // return &obj return T{} } @@ -165,7 +160,7 @@ pub fn (mut self HeroDB[T]) get_by_index(index_values map[string]string) !T { // pub fn (mut self HeroDB[T]) get_all() ![]T { // query := 'SELECT data FROM ${self.table_name} ORDER BY id DESC' // rows := self.db_client.db()!.exec(query)! - + // mut results := []T{} // for row in rows { // json_data_val := row.vals[0] or { continue } @@ -176,7 +171,7 @@ pub fn (mut self HeroDB[T]) get_by_index(index_values map[string]string) !T { // } // results << &obj // } - + // return results // } @@ -184,7 +179,7 @@ pub fn (mut self HeroDB[T]) get_by_index(index_values map[string]string) !T { // pub fn (mut self HeroDB[T]) search_by_index(field_name string, value string) ![]T { // query := 'SELECT data FROM ${self.table_name} WHERE ${field_name} = \'${value}\' ORDER BY id DESC' // rows := self.db_client.db()!.exec(query)! - + // mut results := []T{} // for row in rows { // json_data_val := row.vals[0] or { continue } @@ -194,7 +189,7 @@ pub fn (mut self HeroDB[T]) get_by_index(index_values map[string]string) !T { // } // results << &obj // } - + // return results // } @@ -202,12 +197,12 @@ pub fn (mut self HeroDB[T]) get_by_index(index_values map[string]string) !T { // pub fn (mut self HeroDB[T]) delete_by_index(index_values map[string]string) ! { // mut query := 'DELETE FROM ${self.table_name} WHERE ' // mut params := []string{} - + // for key, value in index_values { // params << '${key} = \'${value}\'' // } // query += params.join(' AND ') - + // self.db_client.db()!.exec(query)! // } diff --git a/lib/installers/infra/zinit_installer/zinit_installer_actions.v b/lib/installers/infra/zinit_installer/zinit_installer_actions.v index 214487fa..e6e5a28b 100644 --- a/lib/installers/infra/zinit_installer/zinit_installer_actions.v +++ b/lib/installers/infra/zinit_installer/zinit_installer_actions.v @@ -74,7 +74,7 @@ fn install() ! { return error('only support linux for now') } - release_url := 'https://github.com/threefoldtech/zinit/releases/download/v0.2.14/zinit' + release_url := 'https://github.com/threefoldtech/zinit/releases/download/v0.2.25/zinit' mut dest := osal.download( url: release_url diff --git a/lib/mcp/transport/interface.v b/lib/mcp/transport/interface.v index b952dcf9..df995203 100644 --- a/lib/mcp/transport/interface.v +++ b/lib/mcp/transport/interface.v @@ -41,9 +41,9 @@ pub: // HttpConfig holds HTTP-specific configuration pub struct HttpConfig { pub: - port int = 8080 // Port to listen on - host string = 'localhost' // Host to bind to - protocol HttpMode = .both // Which HTTP protocols to support + port int = 8080 // Port to listen on + host string = 'localhost' // Host to bind to + protocol HttpMode = .both // Which HTTP protocols to support } // HttpMode defines which HTTP protocols the server should support diff --git a/lib/osal/core/done_test.v b/lib/osal/core/done_test.v index 4d116579..bb1ffa3a 100644 --- a/lib/osal/core/done_test.v +++ b/lib/osal/core/done_test.v @@ -1,4 +1,4 @@ -module core +module core fn test_done_set() ! { done_set('mykey', 'myvalue')! diff --git a/lib/web/docusaurus/dsite.v b/lib/web/docusaurus/dsite.v index 1e2dc605..52843546 100644 --- a/lib/web/docusaurus/dsite.v +++ b/lib/web/docusaurus/dsite.v @@ -18,7 +18,7 @@ pub mut: path_build pathlib.Path errors []SiteError config Configuration - website sitemodule.Site + website sitemodule.Site } pub fn (mut s DocSite) build() ! { diff --git a/lib/web/docusaurus/dsite_add.v b/lib/web/docusaurus/dsite_add.v index 1287d992..957dc9a2 100644 --- a/lib/web/docusaurus/dsite_add.v +++ b/lib/web/docusaurus/dsite_add.v @@ -13,14 +13,14 @@ import freeflowuniverse.herolib.core.playbook @[params] pub struct AddArgs { pub mut: - sitename string //needs to exist in web.site module - path string //site of the docusaurus site with the config as is needed to populate the docusaurus site - git_url string - git_reset bool - git_root string - git_pull bool + sitename string // needs to exist in web.site module + path string // site of the docusaurus site with the config as is needed to populate the docusaurus site + git_url string + git_reset bool + git_root string + git_pull bool path_publish string - play bool = true + play bool = true } pub fn dsite_add(args_ AddArgs) !&DocSite { @@ -29,10 +29,9 @@ pub fn dsite_add(args_ AddArgs) !&DocSite { console.print_header('Add Docusaurus Site: ${args.sitename}') - if args.sitename in docusaurus_sites { - return error('Docusaurus site ${args.sitename} already exists, returning existing.') - - } + if args.sitename in docusaurus_sites { + return error('Docusaurus site ${args.sitename} already exists, returning existing.') + } mut path := gittools.path( path: args.path @@ -47,7 +46,7 @@ pub fn dsite_add(args_ AddArgs) !&DocSite { return error('path is not a directory') } - if ! os.exists('${args.path}/cfg') { + if !os.exists('${args.path}/cfg') { return error('config directory for docusaurus does not exist in ${args.path}/cfg.\n${args}') } @@ -66,13 +65,13 @@ pub fn dsite_add(args_ AddArgs) !&DocSite { mut website := site.get(name: args.sitename)! - mut myconfig := new_configuration(website.siteconfig)! //go from site.SiteConfig to docusaurus.Configuration + mut myconfig := new_configuration(website.siteconfig)! // go from site.SiteConfig to docusaurus.Configuration if myconfig.main.name.len == 0 { return error('main.name is not set in the site configuration') } - mut f:=factory_get()! + mut f := factory_get()! if args.path_publish == '' { args.path_publish = '${f.path_publish.path}/${args.sitename}' @@ -80,37 +79,33 @@ pub fn dsite_add(args_ AddArgs) !&DocSite { path_build_ := '${f.path_build.path}/${args.sitename}' - //get our website + // get our website mut mysite := site.new(name: args.sitename)! if site.exists(name: args.sitename) { console.print_debug('Docusaurus site ${args.sitename} already exists, using existing site.') mysite = site.get(name: args.sitename)! } else { - if !args.play{ + if !args.play { return error('Docusaurus site ${args.sitename} does not exist, please set play to true to create it.') } console.print_debug('Creating new Docusaurus site ${args.sitename}.') - mut plbook := playbook.new(path: "${args.path}/cfg")! + mut plbook := playbook.new(path: '${args.path}/cfg')! site.play(mut plbook)! mysite = site.get(name: args.sitename) or { return error('Failed to get site after playing playbook: ${args.sitename}') - } + } } - // Create the DocSite instance - mut dsite := &DocSite{ - name: args.sitename - path_src: pathlib.get_dir(path: args.path, create: false)! - path_publish: pathlib.get_dir(path: args.path_publish, create: true)! - path_build: pathlib.get_dir(path: path_build_, create: true)! - config: new_configuration(website.siteconfig)! + // Create the DocSite instance + mut dsite := &DocSite{ + name: args.sitename + path_src: pathlib.get_dir(path: args.path, create: false)! + path_publish: pathlib.get_dir(path: args.path_publish, create: true)! + path_build: pathlib.get_dir(path: path_build_, create: true)! + config: new_configuration(website.siteconfig)! website: mysite - } + } - docusaurus_sites[args.sitename] = dsite - return dsite + docusaurus_sites[args.sitename] = dsite + return dsite } - - - - diff --git a/lib/web/docusaurus/dsite_configuration.v b/lib/web/docusaurus/dsite_configuration.v index de60cdca..dbffc6cb 100644 --- a/lib/web/docusaurus/dsite_configuration.v +++ b/lib/web/docusaurus/dsite_configuration.v @@ -2,7 +2,7 @@ module docusaurus import freeflowuniverse.herolib.web.site -//IS THE ONE AS USED BY DOCUSAURUS +// IS THE ONE AS USED BY DOCUSAURUS pub struct Configuration { pub mut: diff --git a/lib/web/docusaurus/dsite_generate.v b/lib/web/docusaurus/dsite_generate.v index 37f4f6f4..9d720cc9 100644 --- a/lib/web/docusaurus/dsite_generate.v +++ b/lib/web/docusaurus/dsite_generate.v @@ -12,13 +12,11 @@ import freeflowuniverse.herolib.core.texttools.regext import freeflowuniverse.herolib.web.site as sitegen pub fn (mut site DocSite) generate() ! { - - mut f:=factory_get()! + mut f := factory_get()! console.print_header(' site generate: ${site.name} on ${f.path_build.path}') console.print_header(' site source on ${site.path_src.path}') - // lets make sure we remove the cfg dir so we rebuild cfg_path := os.join_path(f.path_build.path, 'cfg') osal.rm(cfg_path)! diff --git a/lib/web/docusaurus/factory.v b/lib/web/docusaurus/factory.v index ade794e2..35b9523c 100644 --- a/lib/web/docusaurus/factory.v +++ b/lib/web/docusaurus/factory.v @@ -8,71 +8,72 @@ import freeflowuniverse.herolib.osal.core as osal import freeflowuniverse.herolib.installers.web.bun __global ( - docusaurus_sites map[string]&DocSite - docusaurus_factory ?DocSiteFactory + docusaurus_sites map[string]&DocSite + docusaurus_factory []DocSiteFactory ) -pub struct DocSiteFactory{ +pub struct DocSiteFactory { pub mut: path_publish pathlib.Path - path_build pathlib.Path + path_build pathlib.Path } @[params] pub struct DocSiteFactoryArgs { pub mut: - path_build string - path_publish string - install bool - reset bool - template_update bool + path_build string + path_publish string + install bool + reset bool + template_update bool } pub fn factory_get(args_ DocSiteFactoryArgs) !DocSiteFactory { - mut args:= args_ - mut f:= docusaurus_factory or { - mut factory:=factory_set(args)! - factory - } - return f + mut args := args_ + if docusaurus_factory.len > 1 { + panic("multiple docusaurus factories found, please specify which one to use") + } + if docusaurus_factory.len > 0 { + return docusaurus_factory[0] + } + return factory_set(args)! } pub fn factory_set(args_ DocSiteFactoryArgs) !DocSiteFactory { - mut args:= args_ - if args.path_build == '' { - args.path_build = '${os.home_dir()}/hero/var/docusaurus/build' - } - if args.path_publish == '' { - args.path_publish = '${os.home_dir()}/hero/var/docusaurus/publish' - } - mut factory := DocSiteFactory{ - path_publish: pathlib.get_dir(path:args.path_publish,create:true)! - path_build: pathlib.get_dir(path:args.path_build,create:true)! - } + mut args := args_ + if args.path_build == '' { + args.path_build = '${os.home_dir()}/hero/var/docusaurus/build' + } + if args.path_publish == '' { + args.path_publish = '${os.home_dir()}/hero/var/docusaurus/publish' + } + mut factory := DocSiteFactory{ + path_publish: pathlib.get_dir(path: args.path_publish, create: true)! + path_build: pathlib.get_dir(path: args.path_build, create: true)! + } - if !os.exists('${args.path_build}/node_modules') { - args.install = true - } + if !os.exists('${args.path_build}/node_modules') { + args.install = true + } + + if args.install { + factory.install(args.reset, args.template_update)! + } + + docusaurus_factory << factory - if args.install { - factory.install(args.reset, args.template_update)! - } return factory } - pub fn dsite_get(name_ string) !&DocSite { - name := texttools.name_fix(name_) - return docusaurus_sites[name] or { - return error('docusaurus site with name "${name}" does not exist') - } + name := texttools.name_fix(name_) + return docusaurus_sites[name] or { + return error('docusaurus site with name "${name}" does not exist') + } } pub fn dsite_exists(name_ string) !bool { - name := texttools.name_fix(name_) - d := docusaurus_sites[name] or { - return false - } - return true + name := texttools.name_fix(name_) + d := docusaurus_sites[name] or { return false } + return true } - diff --git a/lib/web/docusaurus/generator.v b/lib/web/docusaurus/generator.v index 7d486449..dcce2db4 100644 --- a/lib/web/docusaurus/generator.v +++ b/lib/web/docusaurus/generator.v @@ -2,12 +2,11 @@ module docusaurus import freeflowuniverse.herolib.core.pathlib import freeflowuniverse.herolib.web.doctreeclient -import freeflowuniverse.herolib.web.site {Site, Page, Section} +import freeflowuniverse.herolib.web.site { Page, Section, Site } import freeflowuniverse.herolib.data.markdown.tools as markdowntools // import freeflowuniverse.herolib.ui.console -//THIS CODE GENERATES A DOCUSAURUS SITE FROM A DOCTREECLIENT AND SITE DEFINITION - +// THIS CODE GENERATES A DOCUSAURUS SITE FROM A DOCTREECLIENT AND SITE DEFINITION struct SiteGenerator { mut: @@ -15,7 +14,7 @@ mut: path pathlib.Path client &doctreeclient.DocTreeClient flat bool // if flat then won't use sitenames as subdir's - site Site + site Site } @[params] @@ -36,7 +35,7 @@ fn generate(args SiteGeneratorArgs) ! { path: pathlib.get_dir(path: path, create: true)! client: doctreeclient.new()! flat: args.flat - site: args.site + site: args.site } for section in gen.site.sections { diff --git a/lib/web/docusaurus/install.v b/lib/web/docusaurus/install.v index 59c3c3fb..0bc74e30 100644 --- a/lib/web/docusaurus/install.v +++ b/lib/web/docusaurus/install.v @@ -6,7 +6,6 @@ import freeflowuniverse.herolib.develop.gittools import freeflowuniverse.herolib.osal.core as osal import freeflowuniverse.herolib.installers.web.bun - fn (mut f DocSiteFactory) install(reset bool, template_update bool) ! { mut gs := gittools.new()! @@ -23,19 +22,18 @@ fn (mut f DocSiteFactory) install(reset bool, template_update bool) ! { mut template_path0 := pathlib.get_dir(path: template_path, create: false)! - template_path0.copy(dest: f.path_build.path, delete:reset)! // Changed args.delete to args.reset + template_path0.copy(dest: f.path_build.path, delete: reset)! // Changed args.delete to args.reset - // install bun - mut installer := bun.get()! - installer.install()! - osal.exec( - // always stay in the context of the build directory - cmd: ' + // install bun + mut installer := bun.get()! + installer.install()! + osal.exec( + // always stay in the context of the build directory + cmd: ' ${osal.profile_path_source_and()!} export PATH=${f.path_build.path}/node_modules/.bin::${os.home_dir()}/.bun/bin/:\$PATH cd ${f.path_build.path} bun install ' - )! - -} \ No newline at end of file + )! +} diff --git a/lib/web/docusaurus/play.v b/lib/web/docusaurus/play.v index 325d3fd6..eac75a6a 100644 --- a/lib/web/docusaurus/play.v +++ b/lib/web/docusaurus/play.v @@ -4,48 +4,55 @@ import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.web.site pub fn play(mut plbook PlayBook) ! { + + if !plbook.exists_once(filter: 'docusaurus.') { + return + } + // 1. Process generic site configuration first. // This populates the global `site.websites` map. site.play(mut plbook)! // check if docusaurus.define exists, if not, we create a default factory - mut f:=DocSiteFactory{} - if plbook.exists_once(filter: 'docusaurus.define'){ - mut a:=plbook.get(filter: 'docusaurus.define') or { + mut f := DocSiteFactory{} + if plbook.exists_once(filter: 'docusaurus.define') { + mut a := plbook.get(filter: 'docusaurus.define') or { panic('docusaurus.define action not found, this should not happen.') } - mut p:=a.params - f=factory_set( - path_build: p.get_default('path_build', '')! - path_publish: p.get_default('path_publish', '')! - reset: p.get_default_false('reset') - template_update: p.get_default_false('template_update') - install: p.get_default_false('install') - )! - a.done = true - }else{ - f=factory_get()! + mut p := a.params + f = factory_set( + path_build: p.get_default('path_build', '')! + path_publish: p.get_default('path_publish', '')! + reset: p.get_default_false('reset') + template_update: p.get_default_false('template_update') + install: p.get_default_false('install') + )! + a.done = true + } else { + f = factory_get()! } // 3. Process `docusaurus.add` actions to create sites. for mut action in plbook.find(filter: 'docusaurus.add')! { mut p := action.params - site_name := p.get('sitename') or { return error('In docusaurus.add, param "sitename" is required.') } + site_name := p.get('sitename') or { + return error('In docusaurus.add, param "sitename" is required.') + } dsite_add( - sitename: site_name - path: p.get('path')! - git_url: p.get('git_url')! - git_reset: p.get_default_false('git_reset') - git_root: p.get('git_root')! - git_pull: p.get_default_false('git_pull') + sitename: site_name + path: p.get('path')! + git_url: p.get('git_url')! + git_reset: p.get_default_false('git_reset') + git_root: p.get('git_root')! + git_pull: p.get_default_false('git_pull') path_publish: p.get_default('path_publish', f.path_publish.path)! - play:false //need to make sure we don't play again + play: false // need to make sure we don't play again )! action.done = true } - mut actions_dev:=plbook.find(filter: 'docusaurus.dev')! + mut actions_dev := plbook.find(filter: 'docusaurus.dev')! if actions_dev.len > 1 { return error('Multiple "docusaurus.dev" actions found. Only one is allowed.') } @@ -54,15 +61,15 @@ pub fn play(mut plbook PlayBook) ! { site_name := p.get('site')! mut dsite := dsite_get(site_name)! dsite.dev( - host: p.get_default('host', 'localhost')! - port: p.get_int_default('port', 3000)! - open: p.get_default_false('open') + host: p.get_default('host', 'localhost')! + port: p.get_int_default('port', 3000)! + open: p.get_default_false('open') watch_changes: p.get_default_false('watch_changes') )! action.done = true } - mut actions_build:=plbook.find(filter: 'docusaurus.build')! + mut actions_build := plbook.find(filter: 'docusaurus.build')! if actions_build.len > 1 { return error('Multiple "docusaurus.build" actions found. Only one is allowed.') } @@ -73,5 +80,4 @@ pub fn play(mut plbook PlayBook) ! { dsite.build()! action.done = true } - -} \ No newline at end of file +} diff --git a/lib/web/site/factory.v b/lib/web/site/factory.v index 8834847e..e4b3bfdd 100644 --- a/lib/web/site/factory.v +++ b/lib/web/site/factory.v @@ -3,13 +3,13 @@ module site import freeflowuniverse.herolib.core.texttools __global ( - websites map[string]&Site + websites map[string]&Site ) @[params] pub struct FactoryArgs { pub mut: - name string = "default" + name string = 'default' } pub fn new(args FactoryArgs) !&Site { @@ -19,29 +19,24 @@ pub fn new(args FactoryArgs) !&Site { name: name } } - return get(name:name)! + return get(name: name)! } pub fn get(args FactoryArgs) !&Site { name := texttools.name_fix(args.name) - mut sc := websites[name] or { - return error('siteconfig with name "${name}" does not exist') - } + mut sc := websites[name] or { return error('siteconfig with name "${name}" does not exist') } return sc } - pub fn exists(args FactoryArgs) bool { name := texttools.name_fix(args.name) - mut sc := websites[name] or { - return false - } + mut sc := websites[name] or { return false } return true } pub fn default() !&Site { if websites.len == 0 { - return new(name:'default')! + return new(name: 'default')! } return get()! } diff --git a/lib/web/site/model_site.v b/lib/web/site/model_site.v index 8a0aa468..21f44134 100644 --- a/lib/web/site/model_site.v +++ b/lib/web/site/model_site.v @@ -1,13 +1,11 @@ module site - @[heap] pub struct Site { pub mut: - pages []Page - sections []Section + pages []Page + sections []Section siteconfig SiteConfig - } pub struct Page { @@ -17,10 +15,10 @@ pub mut: draft bool position int hide_title bool - src string @[required] //always in format collection:page_name + src string @[required] // always in format collection:page_name path string @[required] title_nr int - slug string + slug string } pub struct Section { diff --git a/lib/web/site/model_siteconfig.v b/lib/web/site/model_siteconfig.v index 1179a2aa..a466fe9f 100644 --- a/lib/web/site/model_siteconfig.v +++ b/lib/web/site/model_siteconfig.v @@ -1,6 +1,8 @@ module site + import os // Combined config structure + @[heap] pub struct SiteConfig { pub mut: diff --git a/lib/web/site/play.v b/lib/web/site/play.v index 76a47fe9..7bf6f321 100644 --- a/lib/web/site/play.v +++ b/lib/web/site/play.v @@ -15,7 +15,7 @@ pub fn play(mut plbook PlayBook) ! { // Process each site configuration separately for mut config_action in config_actions { mut website := play_config_single(mut config_action)! - + mut config := &website.siteconfig play_import(mut plbook, mut config)! @@ -30,9 +30,7 @@ pub fn play(mut plbook PlayBook) ! { fn play_config_single(mut action Action) !&Site { mut p := action.params - name := p.get('name') or { - return error('need to specify name in site.config.\n${action}') - } + name := p.get('name') or { return error('need to specify name in site.config.\n${action}') } mut website := new(name: name)! mut config := &website.siteconfig diff --git a/lib/web/site/play_page.v b/lib/web/site/play_page.v index f0f2408e..a98461fc 100644 --- a/lib/web/site/play_page.v +++ b/lib/web/site/play_page.v @@ -2,12 +2,11 @@ module site import freeflowuniverse.herolib.core.playbook { PlayBook } -//plays the sections & pages +// plays the sections & pages fn play_pages(mut plbook PlayBook, mut site Site) ! { - // mut siteconfig := &site.siteconfig - //if only 1 doctree is specified, then we use that as the default doctree name + // if only 1 doctree is specified, then we use that as the default doctree name mut doctreename := 'main' if plbook.exists(filter: 'site.doctree') { if plbook.exists_once(filter: 'site.doctree') {