File Coverage

blib/lib/App/Greple/xlate/gpt4.pm
Criterion Covered Total %
statement 32 88 36.3
branch 0 18 0.0
condition 0 12 0.0
subroutine 11 17 64.7
pod 0 4 0.0
total 43 139 30.9


line stmt bran cond sub pod time code
1             package App::Greple::xlate::gpt4;
2              
3             our $VERSION = "1.01";
4              
5 1     1   1468 use v5.14;
  1         5  
6 1     1   6 use warnings;
  1         4  
  1         74  
7 1     1   7 use utf8;
  1         2  
  1         12  
8 1     1   50 use Encode;
  1         2  
  1         124  
9 1     1   8 use Data::Dumper;
  1         2  
  1         87  
10             {
11 1     1   7 no warnings 'redefine';
  1         2  
  1         156  
12 0     0     *Data::Dumper::qquote = sub { qq["${\(shift)}"] };
  0            
13             $Data::Dumper::Useperl = 1;
14             }
15              
16 1     1   8 use List::Util qw(sum);
  1         2  
  1         68  
17 1     1   6 use Command::Run;
  1         1  
  1         45  
18              
19 1     1   6 use App::Greple::xlate qw(%opt &opt);
  1         2  
  1         149  
20 1     1   7 use App::Greple::xlate::Lang qw(%LANGNAME);
  1         42  
  1         850  
21              
22             our $lang_from //= 'ORIGINAL';
23             our $lang_to //= 'JA';
24             our $auth_key;
25             our $method = __PACKAGE__ =~ s/.*://r;
26              
27             my %param = (
28             gpt4 => { engine => 'gpt-4.1', temp => '0.0', max => 3000, sub => \&gpty,
29             prompt => <
30             Translate the following JSON array into %s.
31             For each input array element, output only the corresponding translated element at the same array index.
32             If an element is a blank string or an XML-style marker tag (e.g., ""), leave it unchanged and do not translate it.
33             Do not output the original (pre-translation) text under any circumstances.
34             The number and order of output elements must always match the input exactly: output element n must correspond to input element n.
35             Output only the translated elements or unchanged tags/blank strings as a JSON array.
36             Do not leave any unnecessary spaces or tabs at the end of any array element in your output.
37             Before finishing, carefully check that there are absolutely no omissions, duplicate content, or trailing spaces of any kind in your output.
38              
39             Return the result as a JSON array and nothing else.
40             Your entire output must be valid JSON.
41             Do not include any explanations, code blocks, or text outside of the JSON array.
42             If you cannot produce a valid JSON array, return an empty JSON array ([]).
43             END
44             },
45             );
46              
47             sub initialize {
48 0     0 0   my($mod, $argv) = @_;
49 0           $mod->setopt(default => "-Mxlate --xlate-engine=$method");
50             }
51              
52             sub gpty {
53 0     0 0   state $gpty = Command::Run->new;
54 0           my $text = shift;
55 0           my $param = $param{$method};
56 0   0       my $prompt = opt('prompt') || $param->{prompt};
57 0           my @vars = do {
58 0 0         if ($prompt =~ /%s/) {
59 0   0       $LANGNAME{$lang_to} // die "$lang_to: unknown lang.\n"
60             } else {
61 0           ();
62             }
63             };
64 0           my $system = sprintf($prompt, @vars);
65             my @command = (
66             'gpty',
67             -e => $param->{engine},
68             -t => $param->{temp},
69 0           -s => $system,
70             );
71 0 0         if (my @contexts = @{$opt{contexts}}) {
  0            
72 0           push @command, map { (-s => "Translation context: $_") } @contexts;
  0            
73             }
74 0           push @command, '-';
75 0 0         warn Dumper \@command if opt('debug');
76 0           $gpty->command(@command)->with(stdin => $text)->update->data;
77             }
78              
79             sub _progress {
80 0 0   0     print STDERR @_ if opt('progress');
81             }
82              
83 1     1   2514 use JSON;
  1         16218  
  1         7  
84             my $json = JSON->new->canonical->pretty;
85              
86             sub xlate_each {
87 0   0 0 0   my $call = $param{$method}->{sub} // die;
88 0           my @count = map { int tr/\n/\n/ } @_;
  0            
89 0           _progress("From:\n", map s/^/\t< /mgr, @_);
90 0           my($in, $out);
91 0           my @in = map { m/.*\n/mg } @_;
  0            
92 0           my $obj = $json->decode($out = $call->($in = $json->encode(\@in)));
93 0           my @out = map { s/(?
  0            
94 0           _progress("To:\n", map s/^/\t> /mgr, @out);
95 0 0         if (@out < @in) {
96 0           my $to = join '', @out;
97 0           die sprintf("Unexpected response (%d < %d):\n\n%s\n",
98             int(@out), int(@in), $to);
99             }
100 0           map { join '', splice @out, 0, $_ } @count;
  0            
101             }
102              
103             sub xlate {
104 0 0   0 0   my @from = map { /\n\z/ ? $_ : "$_\n" } @_;
  0            
105 0           my @to;
106 0   0       my $max = $App::Greple::xlate::max_length || $param{$method}->{max} // die;
      0        
107 0 0         if (my @len = grep { $_ > $max } map length, @from) {
  0            
108 0           die "Contain lines longer than max length (@len > $max).\n";
109             }
110 0           while (@from) {
111 0           my @tmp;
112 0           my $len = 0;
113 0           while (@from) {
114 0           my $next = length $from[0];
115 0 0         last if $len + $next > $max;
116 0           $len += $next;
117 0           push @tmp, shift @from;
118             }
119 0 0         @tmp > 0 or die "Probably text is longer than max length ($max).\n";
120 0           push @to, xlate_each @tmp;
121             }
122 0           @to;
123             }
124              
125             1;
126              
127             __DATA__