39 private static readonly Dictionary<AIClassificationType, ClassificationConfig> ClassificationConfigs =
new Dictionary<AIClassificationType, ClassificationConfig>
41 { AIClassificationType.Sentiment,
new ClassificationConfig { Labels =
new[] {
"Strong Positive",
"Weak Positive",
"Neutral",
"Weak Negative",
"Strong Negative" }, PromptTemplate =
"Classify {0} as {1}", MaxWords = 100 } },
42 { AIClassificationType.Spam,
new ClassificationConfig { Labels =
new[] {
"Spam",
"Not Spam" }, PromptTemplate =
"Classify {0} as {1}", MaxWords = 100 } },
43 { AIClassificationType.Topic,
new ClassificationConfig { Labels =
new[] {
"Technology",
"Politics",
"Sports",
"Other" }, PromptTemplate =
"Classify {0} into {1}", MaxWords = 100 } },
44 { AIClassificationType.Intent,
new ClassificationConfig { Labels =
new[] {
"Request",
"Complaint",
"Inquiry",
"Moderate",
"Other" }, PromptTemplate =
"Classify {0} as {1}", MaxWords = 100 } },
45 { AIClassificationType.Emotion,
new ClassificationConfig { Labels =
new[] {
"Happy",
"Sad",
"Angry",
"Neutral" }, PromptTemplate =
"Classify {0} as {1}", MaxWords = 100 } },
46 { AIClassificationType.Toxicity,
new ClassificationConfig { Labels =
new[] {
"Toxic",
"Non-Toxic" }, PromptTemplate =
"Classify {0} as {1}", MaxWords = 100 } },
47 { AIClassificationType.Language,
new ClassificationConfig { Labels =
new[] {
"English",
"Spanish",
"French",
"Other" }, PromptTemplate =
"Identify the language of {0} as {1}", MaxWords = 100 } },
48 { AIClassificationType.Category,
new ClassificationConfig { Labels =
new[] {
"General",
"Specific",
"Miscellaneous" }, PromptTemplate =
"Classify {0} into {1}", MaxWords = 100 } },
49 { AIClassificationType.Priority,
new ClassificationConfig { Labels =
new[] {
"High",
"Medium",
"Low",
"Urgent" }, PromptTemplate =
"Classify {0} as {1}", MaxWords = 100 } },
50 { AIClassificationType.Relevance,
new ClassificationConfig { Labels =
new[] {
"Relevant",
"Irrelevant" }, PromptTemplate =
"Classify {0} as {1}", MaxWords = 100 } },
51 { AIClassificationType.Tone,
new ClassificationConfig { Labels =
new[] {
"Formal",
"Informal",
"Sarcastic",
"Humorous" }, PromptTemplate =
"Classify {0} as {1}", MaxWords = 100 } },
52 { AIClassificationType.Urgency,
new ClassificationConfig { Labels =
new[] {
"Urgent",
"Non-Urgent" }, PromptTemplate =
"Classify {0} as {1}", MaxWords = 100 } },
53 { AIClassificationType.IntentComplexity,
new ClassificationConfig { Labels =
new[] {
"Simple",
"Complex" }, PromptTemplate =
"Classify {0} as {1}", MaxWords = 100 } },
54 { AIClassificationType.Risk,
new ClassificationConfig { Labels =
new[] {
"High Risk",
"Low Risk",
"Acceptable" }, PromptTemplate =
"Classify {0} as {1}", MaxWords = 100 } },
55 { AIClassificationType.ContentType,
new ClassificationConfig { Labels =
new[] {
"News",
"Opinion",
"Advertisement",
"Technical" }, PromptTemplate =
"Classify {0} as {1}", MaxWords = 100 } },
56 { AIClassificationType.Engagement,
new ClassificationConfig { Labels =
new[] {
"High Engagement",
"Low Engagement" }, PromptTemplate =
"Classify {0} as {1}", MaxWords = 100 } },
57 { AIClassificationType.Authenticity,
new ClassificationConfig { Labels =
new[] {
"Authentic",
"Fake" }, PromptTemplate =
"Classify {0} as {1}", MaxWords = 100 } },
58 { AIClassificationType.LanguageProficiency,
new ClassificationConfig { Labels =
new[] {
"Beginner",
"Intermediate",
"Advanced" }, PromptTemplate =
"Classify {0} as {1}", MaxWords = 100 } },
59 { AIClassificationType.SentimentConfidence,
new ClassificationConfig { Labels =
new[] {
"High Confidence",
"Low Confidence" }, PromptTemplate =
"Classify {0} as {1}", MaxWords = 100 } },
60 { AIClassificationType.Custom,
new ClassificationConfig { PromptTemplate =
"Classify {0} as {1}", MaxWords = 100 } }
63 private AIProviderType? _provider;
64 private AIModel? _model;
67 private AIClassificationType? _classificationType;
68 private List<ClassificationConfig> _configs =
new List<ClassificationConfig>();
69 private List<object> _inputSources =
new List<object>();
70 private List<object> _outputTargets =
new List<object>();
71 private AIResponseDelegate _callback;
74 private bool _isExecuted;
78 public IGPALAI ToGPALObject() =>
this;
80 public IAllowAITask WithProvider(AIProviderType provider)
82 if (_provider.HasValue)
84 GPAL.
PublishSimpleEvent(GPALEventType.ERROR,
"Provider already set and cannot be changed.",
this, GPALObjectType.GPALAI);
99 if (credentials ==
null)
104 _credentials = credentials;
109 public IAllowAITask WithCredentials(
string staticApiKey) =>
110 WithCredentials(
GPAL.CredentialsFor(CredentialServiceType.StaticKey).WithServiceKey(staticApiKey).ToGPALObject());
135 _configs.Add(config);
139 public IAllowAIConfig WithClassificationType(AIClassificationType type)
143 _classificationType =
null;
146 if (_task != AITask.Classification)
148 GPAL.
PublishSimpleEvent(GPALEventType.ERROR,
"WithClassificationType is only valid for Classification task.",
this, GPALObjectType.GPALAI);
151 _classificationType = type;
162 if (_task != AITask.Classification || _classificationType != AIClassificationType.Custom)
164 GPAL.
PublishSimpleEvent(GPALEventType.ERROR,
"WithCustomClassification requires Classification task and Custom type.",
this, GPALObjectType.GPALAI);
167 if (config ==
null || config.Labels ==
null || config.PromptTemplate ==
null)
169 GPAL.
PublishSimpleEvent(GPALEventType.ERROR,
"Custom classification requires Labels and PromptTemplate.",
this, GPALObjectType.GPALAI);
172 _configs.Add(config);
178 WithInputFrom(input);
179 WireLiveCallback(input, debounceMilliseconds);
185 WithInputFrom(textArea);
186 WireLiveCallback(textArea, debounceMilliseconds);
192 var timer =
new System.Windows.Forms.Timer { Interval = Math.Max(1, debounceMilliseconds) };
193 timer.Tick += (s, e) =>
198 control.WithCallback(
new EventHandler((s, e) =>
205 public IAllowAIOutput WithInputFrom(
object source)
209 _inputSources.Clear();
214 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"Input source cannot be null.",
this, GPALObjectType.GPALAI);
217 if (!(source is GPALForm.GPALInput || source is GPALForm.GPALTextArea || source is IGPALGrid<string> ||
218 source is GPALFile || source is GPALDatabase || source is
string || source is Uri))
220 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"Unsupported input source type.",
this, GPALObjectType.GPALAI);
223 _inputSources.Add(source);
227 public IAllowAIExecution WithOutputTo(out IGPALGrid<string> target)
231 _outputTargets.Clear();
234 target =
new GPALGrid<string>();
235 _outputTargets.Add(target);
239 public IAllowAIExecution WithOutputTo(
object target)
243 _outputTargets.Clear();
248 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"Output target cannot be null.",
this, GPALObjectType.GPALAI);
251 if (!(target is GPALForm.GPALTextArea || target is GPALForm.GPALRichTextBox || target is GPALDatabase || target is IGPALGrid<string> || target is GPALFile))
253 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"Unsupported output target type.",
this, GPALObjectType.GPALAI);
256 _outputTargets.Add(target);
260 public IAllowAIConfig CallAfterAIResponse(AIResponseDelegate callback)
262 if (callback ==
null)
264 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
"Callback is null.",
this, GPALObjectType.GPALAI);
267 _callback = callback;
271 public IAllowAIExecution WithStatusTo(GPALForm.GPALStatusStrip statusStrip)
273 _statusStrip = statusStrip;
281 private void SetStatus(
string text)
283 if (
null == _statusStrip)
return;
284 _statusStrip.Text = text;
285 System.Windows.Forms.Application.DoEvents();
288 public IAllowAIConfig Execute()
290 if (!_provider.HasValue)
292 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"Provider must be set before execution.",
this, GPALObjectType.GPALAI);
295 ExecuteInternal(
false);
300 private void ExecuteInternal(
bool isInteractive)
302 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Starting AI task for [{_provider}][{_model}][{_task}]",
this, GPALObjectType.GPALAI);
303 IGPALGrid<string> results =
new GPALGrid<string>();
306 _task = _task ==
default ? AITask.Classification : _task;
307 _classificationType = _classificationType ?? AIClassificationType.Custom;
308 if (!_inputSources.Any())
310 _inputSources.Add(
"");
311 GPAL.PublishSimpleEvent(GPALEventType.INFO,
"Using default empty input.",
this, GPALObjectType.GPALAI);
313 if (!_outputTargets.Any())
315 _outputTargets.Add(results);
316 GPAL.PublishSimpleEvent(GPALEventType.INFO,
"Using default grid output.",
this, GPALObjectType.GPALAI);
319 var mergedConfig = MergeConfigs();
321 mergedConfig.AdditionalParameters[
"Model"] = _model.Value.ToString();
323 if (_aiProvider ==
null || _credentials !=
null)
325 _aiProvider =
new AIProvider(_provider.Value,
new RESTClient(), _credentials, mergedConfig);
328 string promptTemplate = GeneratePromptTemplate(mergedConfig);
329 if (
string.IsNullOrEmpty(promptTemplate))
331 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"Failed to generate prompt template.",
this, GPALObjectType.GPALAI);
335 List<string> inputs = ProcessInputs();
338 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
"No valid inputs provided.",
this, GPALObjectType.GPALAI);
342 foreach (var input
in inputs)
344 string prompt =
string.Format(promptTemplate, input, mergedConfig.Labels?.Length > 0 ?
string.Join(
", ", mergedConfig.Labels) :
"");
346 SetStatus(
"Sending request...");
347 SetStatus(
"Waiting for response...");
348 string result = _aiProvider.Call(prompt);
350 if (!
string.IsNullOrEmpty(result))
352 results.AddRow(
new List<string> { result });
353 SetStatus(
"Response received");
357 SetStatus(
"Error: no response from AI provider - see log for details");
361 foreach (var target
in _outputTargets)
363 if (target is IGPALGrid<string> grid)
365 foreach (var row
in results)
368 else if (target is GPALFile file)
370 if (file.Filenames.Count == 0)
372 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
"No filenames specified for GPALFile output. Skipping.",
this, GPALObjectType.GPALAI);
375 GPAL.Converter.WithInput(results).AppendTo(file);
377 else if (target is GPALDatabase db)
379 if (
string.IsNullOrEmpty(db.DatabaseSettings.TableName))
381 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"TableName must be set for GPALDatabase output.",
this, GPALObjectType.GPALAI);
384 GPAL.Converter.WithInput(results).SaveTo(db);
386 else if (target is GPALForm.GPALTextArea textArea)
388 textArea.Text =
string.Join(
"\n", results.Select(row =>
string.Join(
" ", row)));
390 else if (target is GPALForm.GPALRichTextBox richTextBox)
392 richTextBox.Text =
string.Join(
"\n", results.Select(row =>
string.Join(
" ", row)));
396 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"Unsupported output target.",
this, GPALObjectType.GPALAI);
400 if (_callback !=
null)
402 int callbackResult = _callback(
this, results);
403 if (callbackResult == -1)
405 string str =
"AI Callback requested application exit.";
406 GPAL.PublishSimpleEvent(GPALEventType.INFO, str,
this, GPALObjectType.GPALAI);
407 throw new GPALException(str);
413 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION,
"AI execution failed.",
this, GPALObjectType.GPALAI, ex);
414 SetStatus($
"Error: {ex.Message}");
418 private ClassificationConfig MergeConfigs()
420 var merged =
new ClassificationConfig();
421 if (_task == AITask.Classification && _classificationType.HasValue && ClassificationConfigs.TryGetValue(_classificationType.Value, out var defaultConfig))
423 merged.Labels = defaultConfig.Labels;
424 merged.PromptTemplate = defaultConfig.PromptTemplate;
425 merged.MaxWords = defaultConfig.MaxWords;
428 foreach (var config
in _configs)
430 if (config.MaxWords.HasValue) merged.MaxWords = config.MaxWords;
431 if (config.IncludeKeywords.HasValue) merged.IncludeKeywords = config.IncludeKeywords;
432 if (config.Labels !=
null) merged.Labels = config.Labels;
433 if (config.PromptTemplate !=
null) merged.PromptTemplate = config.PromptTemplate;
434 if (config.AdditionalParameters !=
null)
436 merged.AdditionalParameters =
new Dictionary<string, object>(config.AdditionalParameters);
442 private string GeneratePromptTemplate(ClassificationConfig config)
446 case AITask.Summarization:
447 return $
"Summarize {{0}} in {config.MaxWords ?? 100} words{(config.IncludeKeywords == true ? ", including keywords
" : "")}";
448 case AITask.Classification:
449 if (_classificationType == AIClassificationType.Custom &&
string.IsNullOrEmpty(config.PromptTemplate))
451 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"Custom classification requires a PromptTemplate.",
this, GPALObjectType.GPALAI);
454 return config.PromptTemplate ?? (ClassificationConfigs.TryGetValue(_classificationType ?? AIClassificationType.Custom, out var defaultConfig) ? defaultConfig.PromptTemplate :
string.Empty);
455 case AITask.TextGeneration:
456 return config.PromptTemplate ??
"Generate text based on {0}";
457 case AITask.DataAugmentation:
458 return config.PromptTemplate ??
"Augment data for {0}";
460 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"Unsupported AI task.",
this, GPALObjectType.GPALAI);
465 private List<string> ProcessInputs()
467 var inputs =
new List<string>();
470 foreach (var source
in _inputSources)
474 case GPALForm.GPALInput input:
475 inputs.Add(input.Text);
477 case GPALForm.GPALTextArea textArea:
478 inputs.Add(textArea.Text);
480 case IGPALGrid<string> grid:
481 foreach (var row
in grid) inputs.Add(
string.Join(
" ", row));
484 if (!FileHelper.TokenizeFile(file,
null))
486 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"Failed to tokenize file.",
this, GPALObjectType.GPALAI);
489 foreach (var row
in ((IGPALFileInternal)file).TokenList)
490 inputs.Add(
string.Join(
" ", row));
492 case GPALDatabase database:
493 DatabaseHelper.TokenizeDatabase(
null, database);
494 foreach (var row
in database.Tokens)
495 inputs.Add(
string.Join(
" ", row));
501 inputs.Add(FetchWebContent(uri));
504 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"Unsupported input source.",
this, GPALObjectType.GPALAI);
511 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION,
"Failed to process inputs.",
this, GPALObjectType.GPALAI, ex);
516 private string FetchWebContent(Uri uri)
520 using (var client =
new HttpClient())
522 client.DefaultRequestHeaders.Add(
"User-Agent", BrowserHelper.GetUserAgent(GPAL.Browsers[0]));
524 string content = client.GetStringAsync(uri).GetAwaiter().GetResult();
525 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Fetched web content from [{uri}]",
this, GPALObjectType.GPALAI);
531 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Failed to fetch web content",
this, GPALObjectType.GPALAI, ex);