11module App
2+ using Base64
23using ProToPortal
34using PromptingTools
45const PT = PromptingTools
@@ -69,9 +70,19 @@ const HISTORY_SAVE = get(ENV, "PROTO_HISTORY_SAVE", true)
6970 @in chat_auto_reply_count = 0
7071 # chat
7172 @in conv_displayed = Dict{Symbol, Any}[]
73+ @in chat_edit_show = false
74+ @in chat_edit_content = " "
75+ @in chat_edit_save = false
76+ @in chat_edit_index = 0
77+ @in is_recording = false
78+ @in audio_chunks = []
79+ @in mediaRecorder = nothing
80+ @in channel_ = nothing
7281 # Enter text
7382 @in chat_question = " "
7483 @out chat_disabled = false
84+ @out chat_question_tokens = " "
85+ @out chat_convo_tokens = " "
7586 # Select template
7687 @in chat_advanced_expanded = false
7788 @in chat_template_expanded = false
@@ -92,6 +103,16 @@ const HISTORY_SAVE = get(ENV, "PROTO_HISTORY_SAVE", true)
92103 @in meta_rounds_current = 0
93104 @in meta_displayed = Dict{Symbol, Any}[]
94105 @in meta_rm_last_msg = false
106+ # # Prompt Builder
107+ @in builder_apply = false
108+ @in builder_submit = false
109+ @in builder_reset = false
110+ @in builder_disabled = false
111+ @in builder_question = " "
112+ @in builder_tabs = Dict{Symbol, Any}[]
113+ @in builder_tab = " tab1"
114+ @in builder_model = isempty (PT. GROQ_API_KEY) ? " gpt4t" : " gllama370"
115+ @in builder_samples = 3
95116 # Template browser
96117 @in template_filter = " "
97118 @in template_submit = false
@@ -231,6 +252,34 @@ const HISTORY_SAVE = get(ENV, "PROTO_HISTORY_SAVE", true)
231252 chat_reset = true
232253 conv_displayed = conv_displayed_temp
233254 end
255+ @onchange conv_displayed begin
256+ chat_convo_tokens = if isempty (conv_displayed)
257+ " "
258+ elseif PT. isaimessage (conv_displayed[end ][:message ])
259+ msg = conv_displayed[end ][:message ]
260+ " Tokens: $(sum (msg. tokens)) , Cost: \$ $(round (msg. cost;digits= 2 )) "
261+ else
262+ " "
263+ end
264+ end
265+ # # Chat Speech-to-text
266+ @onchange fileuploads begin
267+ if ! isempty (fileuploads)
268+ @info " File was uploaded: " fileuploads[" path" ]
269+ filename = base64encode (fileuploads[" name" ])
270+ try
271+ fn_new = fileuploads[" path" ] * " .wav"
272+ mv (fileuploads[" path" ], fn_new; force = true )
273+ chat_question = openai_whisper (fn_new)
274+ rm (fn_new; force = true )
275+ Base. run (__model__, " this.copyToClipboardText(this.chat_question);" )
276+ catch e
277+ @error " Error processing file: $e "
278+ notify (__model__, " Error processing file: $(fileuploads[" name" ]) " )
279+ end
280+ fileuploads = Dict {AbstractString, AbstractString} ()
281+ end
282+ end
234283 # ## Meta-prompting
235284 @onbutton meta_submit begin
236285 meta_disabled = true
@@ -272,6 +321,71 @@ const HISTORY_SAVE = get(ENV, "PROTO_HISTORY_SAVE", true)
272321 pop! (meta_displayed)
273322 meta_displayed = meta_displayed
274323 end
324+ # ## Prompt Builder
325+ @onbutton builder_submit begin
326+ builder_disabled = true
327+ @info " > Prompt Builder Triggered - generating $(builder_samples) samples"
328+ first_run = isempty (builder_tabs)
329+ for i in 1 : builder_samples
330+ if first_run
331+ # # Generate the first version
332+ conv_current = send_to_model (
333+ :PromptGeneratorBasic ; task = builder_question, model = builder_model)
334+ new_sample = Dict (:name => " tab$(i) " ,
335+ :label => " Sample $(i) " ,
336+ :display => [msg2display (msg; id)
337+ for (id, msg) in enumerate (conv_current)])
338+ # # add new sample
339+ builder_tabs = push! (builder_tabs, new_sample)
340+ else
341+ # # Generate the future iterations
342+ current_tab = builder_tabs[i]
343+ conv = prepare_conversation (
344+ current_tab[:display ]; question = builder_question)
345+ conv_current = send_to_model (
346+ conv; model = builder_model)
347+ # # update the tab
348+ current_tab[:display ] = [msg2display (msg; id)
349+ for (id, msg) in enumerate (conv_current)]
350+ builder_tabs[i] = current_tab
351+ builder_tabs = builder_tabs
352+ end
353+ end
354+
355+ builder_disabled, builder_question = false , " "
356+ end
357+ @onbutton builder_reset begin
358+ @info " > Prompt Builder Reset!"
359+ builder_tabs = empty! (builder_tabs)
360+ builder_disabled, builder_question = false , " "
361+ end
362+ @onbutton builder_apply begin
363+ @info " > Applying Prompt Builder!"
364+ builder_msg = filter (x -> x[:name ] == builder_tab, builder_tabs) |> only
365+ aimsg = builder_msg[:display ][end ][:message ]
366+ instructions, inputs = parse_builder (aimsg)
367+ if isempty (instructions) && isempty (inputs)
368+ notify (__model__, " Parsing failed! Retry..." )
369+ else
370+ conv_current = if isempty (inputs)
371+ notify (__model__, " Parsing failed! Expect bad results / edit as needed!" )
372+ # # slap all instructions into user message
373+ [PT. SystemMessage (system_prompt), PT. UserMessage (instructions)]
374+ else
375+ # # turn into sytem and user message
376+ [PT. SystemMessage (instructions), PT. UserMessage (inputs)]
377+ end
378+ conv_displayed = [msg2display (msg; id)
379+ for (id, msg) in enumerate (conv_current)]
380+ # # show the variables to fill in by the user -- use the last message / UserMessage
381+ chat_template_expanded = true
382+ chat_template_variables = [Dict (:id => id, :variable => String (sym),
383+ :content => " " )
384+ for (id, sym) in enumerate (conv_current[end ]. variables)]
385+ # # change page to chat
386+ selected_page = " chat"
387+ end
388+ end
275389 # ## Template browsing behavior
276390 @onbutton template_submit begin
277391 @info " > Template filter: $template_filter "
@@ -306,10 +420,7 @@ const HISTORY_SAVE = get(ENV, "PROTO_HISTORY_SAVE", true)
306420 end
307421 end
308422end
309- # # TODO : add cost tracking on configuration pages + token tracking
310- # # TODO : add RAG/knowledge loading from folder or URL
311- # Required for the JS events
312-
423+ # ## JAVASCRIPT SECTION ###
313424# set focus to the first variable when it changes
314425@watch begin
315426 raw """
330441 console.log("woowza");
331442 }
332443 },
444+ // saves edits made in the chat dialog
445+ saveEdits(index) {
446+ this.chat_edit_show = false;
447+ this.conv_displayed[this.chat_edit_index].content = this.chat_edit_content;
448+ this.chat_edit_content = "";
449+ },
450+ updateLengthChat() {
451+ const tokens = Math.round(this.chat_question.length / 3.5);
452+ this.chat_question_tokens = `Approx. tokens: ${tokens}`;
453+ },
333454 focusTemplateSelect() {
334455 this.$nextTick(() => {
335456 this.$refs.tpl_select.focus();
387508 el.select(); // Select the <textarea> content
388509 document.execCommand('copy'); // Copy - only works as a result of a user action (e.g. click events)
389510 document.body.removeChild(el); // Remove the <textarea> element
511+ },
512+ copyToClipboardText(str) {
513+ const el = document.createElement('textarea'); // Create a <textarea> element
514+ el.value = str; // Set its value to the string that you want copied
515+ el.setAttribute('readonly', ''); // Make it readonly to be tamper-proof
516+ el.style.position = 'absolute';
517+ el.style.left = '-9999px'; // Move outside the screen to make it invisible
518+ document.body.appendChild(el); // Append the <textarea> element to the HTML document
519+ el.select(); // Select the <textarea> content
520+ document.execCommand('copy'); // Copy - only works as a result of a user action (e.g. click events)
521+ document.body.removeChild(el); // Remove the <textarea> element
522+ },
523+ async toggleRecording() {
524+ if (!this.is_recording) {
525+ this.startRecording()
526+ } else {
527+ this.stopRecording()
528+ }
529+ },
530+ async startRecording() {
531+ navigator.mediaDevices.getUserMedia({ audio: true })
532+ .then(stream => {
533+ this.is_recording = true
534+ this.mediaRecorder = new MediaRecorder(stream);
535+ this.mediaRecorder.start();
536+ this.mediaRecorder.onstop = () => {
537+ const audioBlob = new Blob(this.audio_chunks, { type: 'audio/wav' });
538+ this.is_recording = false;
539+
540+ // upload via uploader
541+ const file = new File([audioBlob], 'test.wav');
542+ this.$refs.uploader.addFiles([file], 'test.wav');
543+ this.$refs.uploader.upload(); // Trigger the upload
544+ console.log("Uploaded WAV");
545+ this.$refs.uploader.reset();
546+ this.audio_chunks=[];
547+
548+ };
549+ this.mediaRecorder.ondataavailable = event => {
550+ this.audio_chunks.push(event.data);
551+ };
552+ })
553+ .catch(error => console.error('Error accessing microphone:', error));
554+ },
555+ stopRecording() {
556+ if (this.mediaRecorder) {
557+ this.mediaRecorder.stop();
558+ } else {
559+ console.error('MediaRecorder is not initialized');
560+ }
390561 }
391562 """
392563end
0 commit comments