Group
Extension

Business-LetterWriter/lib/Business/LetterWriter.pm

package Business::LetterWriter;

use strict;
use warnings;
use JSON;
use OpenAPI::Client::OpenAI;
use Archive::Zip;
use XML::Twig;

our $VERSION = '0.09';

sub get_one_answer_from_new_llm {
    my ($request, $body_builder) = @_;
    
    $body_builder //= sub {
        return {
            model    => 'gpt-4o',
            messages => [ { role => 'user', content => $request } ],
            temperature => 0,
            max_tokens  => 1000,
        };
    };

    my $client = OpenAPI::Client::OpenAI->new();
    my $body = $body_builder->($request);
    my $tx = $client->createChatCompletion({ body => $body });

    my $response_data = $tx->res->json;
    my $raw_response = $response_data->{choices}[0]{message}{content};
    if ($raw_response =~ /({.*})/s) {
        return $1;
    }
    die "Unexpected response format in get_one_answer_from_new_llm.\n";
}

sub get_hashref_from_template_file{
	my $template_fname = shift;
	open my $template_fh, "<:encoding(UTF-8)", $template_fname  or die $!;
	my $template = do{local $/; <$template_fh>};
	my @arr = split /\n{2}/, $template;
	my $counter = 1;
	my %return_hash = ();
	foreach my $para (@arr){
		my $key = "paragraph_" . $counter++;
		$return_hash{$key} = $para;
	}
	return \%return_hash;
}

sub out_parts{
	my $text_hashref = shift;
	my $parms = shift;
	my $fn = $parms->{outfn};
	my $preserved_parts = $parms->{preserved_parts};
	open my $outfh, ">:encoding(UTF-8)", $fn  or die $!;
	foreach my $key (qw(paragraph_1 paragraph_2 paragraph_3 paragraph_4 paragraph_5 paragraph_6)){
		print $outfh $text_hashref->{$key}, "\n\n";
		# print $text_hashref->{$key}, "\n\n";
	}
	close $outfh;
}

sub signature{
    return "John Doe";
}


sub replace_in_docx{
	my $input_docx  = shift;
	my $output_docx = shift;
	my $replacement_hashref = shift;
	my $temp_dir    = "docx_contents_simple";

	# Step 1: Extract .docx contents
	my $zip = Archive::Zip->new();
	$zip->read($input_docx) == Archive::Zip::AZ_OK or die "Cannot read DOCX file";

	mkdir $temp_dir unless -d $temp_dir;
	$zip->extractTree('', "$temp_dir/");



	# Step 2: Load and edit word/document.xml
	my $xml_file = "$temp_dir/word/document.xml";
	my $doc=XML::Twig->new();    # create the twig
	$doc->parsefile( $xml_file); # build it

	# my %replacement_hash = ('{Address1}' => 'Egasse 66', '{Address2}' => 'Bern 3044');
	my %replacement_hash = %{$replacement_hashref};

	# Step 3: Merge text and replace content
	foreach my $p ($doc->findnodes('//w:p')) {  # Process each paragraph
		my @runs = $p->findnodes('.//w:t');     # Collect all text elements
		my $full_text = join('', map { $_->text } @runs);

		foreach my $placeholder (keys %replacement_hash) {
			# my $replacement = decode("UTF-8", $full_replacement_hash{$placeholder});
			# my $replacement = decode("UTF-8", $replacement_hash{$placeholder});
			my $replacement = $replacement_hash{$placeholder};
			$full_text =~ s/\{\Q$placeholder\E\}/$replacement/g;
		}
		
		# Update the XML: Replace text while keeping structure
		if (@runs) {
			$runs[0]->set_text($full_text);

			# Remove extra <w:t> elements
			for (my $i = 1; $i < @runs; $i++) {
				$runs[$i]->delete();
			}
		}
	}

	# Save changes
	open my $fh2, '>:utf8', $xml_file or die "Cannot write to XML file. $!" ;
	print $fh2 $doc->toString(1);
	close $fh2;

	# Step 4: Repackage back into a .docx file
	unlink $output_docx if -e $output_docx;
	my $new_zip = Archive::Zip->new();
	$new_zip->addTree($temp_dir, '');
	$new_zip->writeToFileNamed($output_docx) == Archive::Zip::AZ_OK or die "Failed to create new DOCX file";

	print "Editing complete. Output saved to $output_docx\n";
	
}

sub generate_meta_hashref_from_file{
	my $input_fn = shift;
	open my $fh, '<:utf8', $input_fn or die "Cannot open file: $!";
	my %data;
	while (<$fh>) {
		chomp;
		my ($key, $value) = split /:/, $_, 2;
		next unless defined $key && defined $value;
		$data{"$key"} = $value;
	}
	close $fh;
	return \%data;
}

=pod
    USAGE:
        use strict;
        use JSON;
        use lib '../Business-LetterWriter/lib/';
        use Business::LetterWriter;

        my $template_href = Business::LetterWriter::get_hashref_from_template_file("./CL_TEMPLATE_DE.txt");

        my $json = JSON->new->utf8(0)->pretty(1);
        my $letter_template_json = $json->encode($template_href);

        my $request 
            = "Here is a template letter in the JSON format: " . $letter_template_json .
            " Tailor each part to make the whole letter friendier less formal and very short. Return the tailored version of the letter strictly in JSON format. Do not explain anything." ;
        my $llm_answer_raw = Business::LetterWriter::get_one_answer_from_new_llm($request);
        my $template_href = $json->decode($llm_answer_raw);
        my %parms = (outfn => "outtest.txt");
        Business::LetterWriter::out_parts($template_href, \%parms);

    USAGE:
        use strict;
        use JSON;
        use lib '../Business-LetterWriter/lib/';
        use Business::LetterWriter;

        my $template_href = Business::LetterWriter::get_hashref_from_template_file("./customer_req/LETTER_TEMPLATE_DE.txt");
        my $json = JSON->new->utf8(0)->pretty(1);
        my $letter_template_json = $json->encode($template_href);

        my $customer_req_fn = "./customer_req/requirement_1.txt";
        open my $customer_req_fh, "<:encoding(UTF-8)", $customer_req_fn;
        my $customer_req_text = do{local $/; <$customer_req_fh>};

        # binmode(STDOUT, ":encoding(UTF-8)");  # stdout is UTF-8
        # print $customer_req;
        # exit;

        my $request 
            = "Here is a template letter in the JSON format: " . $letter_template_json .
            "Here is requirement of the company: " . $customer_req_text .
            " Tailor each part of the template letter to make the whole letter fit to the customer requirement. Return the tailored version of the letter strictly in JSON format. Do not explain anything." ;

        my $llm_answer_raw = Business::LetterWriter::get_one_answer_from_new_llm($request);

        my $template_href = $json->decode($llm_answer_raw);

        # Business::LetterWriter::out_parts($template_href, {outfn => "TAILORED_VERSION.txt"});
        my $metha_file_path_name = "./Customers/MyCustomer/META.txt";
        my $address_hashref = Business::LetterWriter::generate_meta_hashref_from_file($metha_file_path_name);
        my %combined_hash = ( %{$address_hashref} , %{$template_href});

        Business::LetterWriter::replace_in_docx("template.docx", "./my_results.docx", \%combined_hash);
=cut

1;


Powered by Groonga
Maintained by Kenichi Ishigaki <ishigaki@cpan.org>. If you find anything, submit it on GitHub.