001package org.nasdanika.ai;
002
003import java.awt.image.BufferedImage;
004import java.io.ByteArrayOutputStream;
005import java.io.File;
006import java.io.IOException;
007import java.io.InputStream;
008import java.lang.module.ModuleDescriptor.Version;
009import java.net.URL;
010import java.time.Duration;
011import java.util.ArrayList;
012import java.util.Arrays;
013import java.util.Collections;
014import java.util.Date;
015import java.util.List;
016import java.util.Optional;
017
018import javax.imageio.ImageIO;
019
020import reactor.core.publisher.Mono;
021
022public interface Chat extends Model {
023        
024        public static final Chat ECHO = new Chat() {
025
026                @Override
027                public int getMaxInputTokens() {
028                        return Integer.MAX_VALUE;
029                }
030
031                @Override
032                public String getProvider() {
033                        return "Nasdanika";
034                }
035
036                @Override
037                public String getName() {
038                        return "Echo";
039                }
040
041                @Override
042                public String getVersion() {
043                        Optional<Version> moduleVersion = getClass().getModule().getDescriptor().version();
044                        return moduleVersion == null ? new Date().toString() : moduleVersion.toString();
045                }
046
047                @Override
048                public Mono<List<? extends ResponseMessage>> chatAsync(List<Message> messages) {
049                        List<? extends ResponseMessage> responseMessages = messages.stream().map(m ->  new ResponseMessage() {
050                                
051                                @Override
052                                public String getRole() {
053                                        return Role.assistant.name();
054                                }
055                                
056                                @Override
057                                public String getContent() {
058                                        return m.getContent();
059                                }
060                                
061                                @Override
062                                public String getRefusal() {
063                                        return null;
064                                }
065                                
066                                @Override
067                                public String getFinishReason() {
068                                        return "stop";
069                                }
070                        })
071                        .toList();
072                        Mono<List<? extends ResponseMessage>> mono = Mono.just(responseMessages);
073                        return mono.delayElement(Duration.ofSeconds(1));
074                }
075
076                @Override
077                public int getMaxOutputTokens() {
078                        return Integer.MAX_VALUE;
079                }
080                
081        };
082        
083        public static final String LOREM_IPSUM_TEXT = 
084                        """
085                        Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec tempus ac nibh a convallis. 
086                        Phasellus tristique, ex ac maximus iaculis, nulla magna aliquam turpis, a vestibulum ligula metus varius lectus. 
087                        Duis rhoncus suscipit odio, vel porttitor metus egestas quis. Nunc quis tristique orci. 
088                        Phasellus tellus mi, pellentesque id aliquam a, eleifend nec nunc. Curabitur volutpat feugiat vestibulum. Maecenas sit amet dapibus velit.
089                        Fusce scelerisque, nisl a fermentum vulputate, nisi enim hendrerit orci, a vestibulum sem leo at lacus. Duis vel rhoncus odio.
090                        Nam facilisis est in ullamcorper consectetur. Donec aliquet velit quis dolor accumsan maximus. Aenean sodales mattis sem sed tincidunt.
091                        Aliquam tristique augue nec tristique lobortis. Vivamus id metus in justo dignissim viverra. 
092                        Pellentesque ipsum lectus, ultricies in vehicula efficitur, aliquam vel purus.
093                        Pellentesque lacus metus, vestibulum at convallis non, sodales sed ante.
094                        
095                        Donec tincidunt elit eros, sit amet blandit ex posuere sed. 
096                        Pellentesque pharetra magna lacus, rhoncus placerat risus faucibus id. Duis non euismod turpis. 
097                        Vestibulum interdum dictum velit sit amet sagittis. Proin velit eros, interdum a nibh sed, viverra luctus ex.
098                        Integer commodo diam id arcu varius pharetra. Proin porta justo lorem, quis sodales ex fermentum a. 
099                        Sed at consequat dolor. In quis consectetur leo, non aliquet mauris.
100                        
101                        Aenean laoreet dui a facilisis efficitur. Maecenas consectetur ligula non magna porta congue. Etiam a ornare lectus. 
102                        Nulla at ligula et tortor mattis bibendum id quis sapien. Aenean eget condimentum enim. 
103                        Duis ullamcorper malesuada sapien et egestas. In hac habitasse platea dictumst. Quisque sed fermentum tortor. In vehicula auctor felis. 
104                        Donec fringilla turpis eget tortor lobortis posuere quis ac erat. Morbi mattis elementum felis, sit amet scelerisque lectus finibus a. 
105                        Donec ac est odio. Sed sollicitudin, arcu sit amet pulvinar pulvinar, lorem nisl malesuada odio, nec dapibus metus dui porta urna. 
106                        In viverra rhoncus est, sed interdum elit faucibus placerat. Proin ut ligula venenatis, bibendum massa in, fermentum sapien. 
107                        Mauris tempor eros ligula, id placerat eros vulputate quis.
108                        
109                        Vestibulum quis dignissim urna. Pellentesque eros turpis, laoreet vel justo vel, tincidunt suscipit elit. Nullam eget cursus nulla. 
110                        Nulla pellentesque molestie sem, sed dictum lacus efficitur eu. Quisque orci lectus, egestas eget nunc quis, placerat blandit ligula. 
111                        Cras quis dictum sapien. Duis varius metus sapien, quis venenatis lectus dapibus ac. Sed eget sem ac leo elementum rhoncus. 
112                        Nunc dolor eros, dapibus a porta eget, vestibulum vel nunc. Aliquam erat volutpat. Nam imperdiet libero velit, vel ullamcorper nisi lacinia nec. 
113                        Suspendisse blandit dolor ut odio aliquam sodales.
114                        
115                        In nec augue pulvinar, semper felis fringilla, vehicula dui. 
116                        Praesent vulputate tellus ac ante varius, sit amet ultricies felis ultrices. Pellentesque egestas lectus non nibh viverra, vitae tristique orci ultrices. 
117                        Donec non sem imperdiet neque varius sodales ut ut odio. Sed vehicula pellentesque nulla, ut dignissim turpis blandit non. Donec fermentum pellentesque nisi in facilisis. 
118                        Aenean mollis purus vel ex porttitor, sed bibendum ante mollis. Fusce molestie justo turpis, luctus sollicitudin augue pulvinar quis. 
119                        Nunc id tincidunt erat. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. 
120                        Cras accumsan laoreet velit. Vestibulum sit amet nisl mauris.                   
121                        """;
122        
123        
124        public static final Chat LOREM_IPSUM = new Chat() {
125
126                @Override
127                public int getMaxInputTokens() {
128                        return Integer.MAX_VALUE;
129                }
130
131                @Override
132                public String getProvider() {
133                        return "Nasdanika";
134                }
135
136                @Override
137                public String getName() {
138                        return "Lorem Impsum";
139                }
140
141                @Override
142                public String getVersion() {
143                        Optional<Version> moduleVersion = getClass().getModule().getDescriptor().version();
144                        return moduleVersion == null ? new Date().toString() : moduleVersion.toString();
145                }
146
147                @Override
148                public Mono<List<? extends ResponseMessage>> chatAsync(List<Message> messages) {
149                        Mono<List<? extends ResponseMessage>> ret = Mono.just(List.of(new ResponseMessage() {
150                                
151                                @Override
152                                public String getRole() {
153                                        return Role.assistant.name();
154                                }
155                                
156                                @Override
157                                public String getContent() {
158                                        return LOREM_IPSUM_TEXT;
159                                }
160                                
161                                @Override
162                                public String getRefusal() {
163                                        return null;
164                                }
165                                
166                                @Override
167                                public String getFinishReason() {
168                                        return "stop";
169                                }
170                        }));
171                        
172                        return ret.delayElement(Duration.ofSeconds(1));
173                }
174
175                @Override
176                public int getMaxOutputTokens() {
177                        return Integer.MAX_VALUE;
178                }
179                
180        };
181        
182        
183        /**
184         * Chat requirement.
185         * String attributes match any value if null.
186         */
187        record Requirement(
188                String provider,
189                String model,
190                String version) {}      
191        
192        interface Message {
193                
194                String getRole();
195                
196                String getContent();
197                
198                /**
199                 * Images encoded as base64 url
200                 * @return
201                 */
202                List<String> getImages();
203                
204                /**
205                 * Adds an image encoded as base64 data URL
206                 * @param dataUrl
207                 * @return this message
208                 */
209                Message addImage(String dataUrl);
210                
211                default Message addImage(File file) {
212                        try {
213                                return addImage(ImageIO.read(file));
214                        } catch (IOException e) {
215                                throw new IllegalArgumentException("Cannot read image from file '" + file.getAbsolutePath() + "': " + e, e);
216                        }
217                }
218
219                default Message addImage(InputStream inputStream) {
220                        try {
221                                return addImage(ImageIO.read(inputStream));
222                        } catch (IOException e) {
223                                throw new IllegalArgumentException("Cannot read image from input stream: " + e, e);
224                        }
225                }
226
227                default Message addImage(URL url) {
228                        try {
229                                return addImage(ImageIO.read(url));
230                        } catch (IOException e) {
231                                throw new IllegalArgumentException("Cannot read image from URL '" + url + "': " + e, e);
232                        }
233                }
234
235                default Message addImage(BufferedImage image) {
236                        ByteArrayOutputStream baos = new ByteArrayOutputStream();
237                        try {
238                                try (baos) {
239                                        ImageIO.write(image, "PNG", baos);                                                                                              
240                                }
241                        String base64Image = java.util.Base64.getEncoder().encodeToString(baos.toByteArray());
242                            return addImage("data:image/png;base64," + base64Image);
243                        } catch (Exception e) {
244                                throw new IllegalArgumentException("Cannot write image: " + e, e);
245                        }
246                        
247                }
248                
249                /**
250                 * Creates a message
251                 * @param role
252                 * @param content Message content. Can be null.
253                 * @return
254                 */
255                static Message create(String role, String content) {
256                                                                        
257                        return new Message() {
258                                
259                                @Override
260                                public String getRole() {
261                                        return role;
262                                }
263                                
264                                @Override
265                                public String getContent() {
266                                        return content;
267                                }
268                                
269                                private List<String> images = new ArrayList<>();
270
271                                @Override
272                                public List<String> getImages() {
273                                        return Collections.unmodifiableList(images);
274                                }
275
276                                @Override
277                                public Message addImage(String dataUrl) {
278                                        images.add(dataUrl);
279                                        return this;
280                                }
281                                
282                        };
283                        
284                }
285                
286        }
287        
288        interface ResponseMessage extends Message {
289                
290                String getRefusal();
291                
292                String getFinishReason();
293                
294                @Override
295                default Message addImage(String dataUrl) {
296                        throw new UnsupportedOperationException();                      
297                }
298                
299                @Override
300                default List<String> getImages() {
301                        return Collections.emptyList();
302                }
303                
304        }
305                
306        enum Role {
307                
308                system,
309                assistant,
310                user,
311                function,
312                tool,
313                developer;              
314                
315                public Message createMessage(String content) {
316                        return Message.create(name(), content);
317                }
318                
319        }
320        
321        Mono<List<? extends ResponseMessage>> chatAsync(List<Message> messages);
322        
323        default Mono<List<? extends ResponseMessage>> chatAsync(Message... messages) {
324                return chatAsync(Arrays.asList(messages));
325        }               
326        
327        default List<? extends ResponseMessage> chat(List<Message> messages) {
328                return chatAsync(messages).block();
329        }
330                
331        default List<? extends ResponseMessage> chat(Message... messages) {
332                return chat(Arrays.asList(messages));
333        }       
334        
335        int getMaxOutputTokens();
336
337}