38 private static readonly Dictionary<AIClassificationType, ClassificationConfig> ClassificationConfigs =
new Dictionary<AIClassificationType, ClassificationConfig>
40 { AIClassificationType.Sentiment,
new ClassificationConfig { Labels =
new[] {
"Strong Positive",
"Weak Positive",
"Neutral",
"Weak Negative",
"Strong Negative" }, PromptTemplate =
"Classify {0} as {1}", ConfidenceThreshold = 0.5, MaxWords = 100 } },
41 { AIClassificationType.Spam,
new ClassificationConfig { Labels =
new[] {
"Spam",
"Not Spam" }, PromptTemplate =
"Classify {0} as {1}", ConfidenceThreshold = 0.5, MaxWords = 100 } },
42 { AIClassificationType.Topic,
new ClassificationConfig { Labels =
new[] {
"Technology",
"Politics",
"Sports",
"Other" }, PromptTemplate =
"Classify {0} into {1}", ConfidenceThreshold = 0.5, MaxWords = 100 } },
43 { AIClassificationType.Intent,
new ClassificationConfig { Labels =
new[] {
"Request",
"Complaint",
"Inquiry",
"Moderate",
"Other" }, PromptTemplate =
"Classify {0} as {1}", ConfidenceThreshold = 0.5, MaxWords = 100 } },
44 { AIClassificationType.Emotion,
new ClassificationConfig { Labels =
new[] {
"Happy",
"Sad",
"Angry",
"Neutral" }, PromptTemplate =
"Classify {0} as {1}", ConfidenceThreshold = 0.5, MaxWords = 100 } },
45 { AIClassificationType.Toxicity,
new ClassificationConfig { Labels =
new[] {
"Toxic",
"Non-Toxic" }, PromptTemplate =
"Classify {0} as {1}", ConfidenceThreshold = 0.5, MaxWords = 100 } },
46 { AIClassificationType.Language,
new ClassificationConfig { Labels =
new[] {
"English",
"Spanish",
"French",
"Other" }, PromptTemplate =
"Identify the language of {0} as {1}", ConfidenceThreshold = 0.5, MaxWords = 100 } },
47 { AIClassificationType.Category,
new ClassificationConfig { Labels =
new[] {
"General",
"Specific",
"Miscellaneous" }, PromptTemplate =
"Classify {0} into {1}", ConfidenceThreshold = 0.5, MaxWords = 100 } },
48 { AIClassificationType.Priority,
new ClassificationConfig { Labels =
new[] {
"High",
"Medium",
"Low",
"Urgent" }, PromptTemplate =
"Classify {0} as {1}", ConfidenceThreshold = 0.5, MaxWords = 100 } },
49 { AIClassificationType.Relevance,
new ClassificationConfig { Labels =
new[] {
"Relevant",
"Irrelevant" }, PromptTemplate =
"Classify {0} as {1}", ConfidenceThreshold = 0.5, MaxWords = 100 } },
50 { AIClassificationType.Tone,
new ClassificationConfig { Labels =
new[] {
"Formal",
"Informal",
"Sarcastic",
"Humorous" }, PromptTemplate =
"Classify {0} as {1}", ConfidenceThreshold = 0.5, MaxWords = 100 } },
51 { AIClassificationType.Urgency,
new ClassificationConfig { Labels =
new[] {
"Urgent",
"Non-Urgent" }, PromptTemplate =
"Classify {0} as {1}", ConfidenceThreshold = 0.5, MaxWords = 100 } },
52 { AIClassificationType.IntentComplexity,
new ClassificationConfig { Labels =
new[] {
"Simple",
"Complex" }, PromptTemplate =
"Classify {0} as {1}", ConfidenceThreshold = 0.5, MaxWords = 100 } },
53 { AIClassificationType.Risk,
new ClassificationConfig { Labels =
new[] {
"High Risk",
"Low Risk",
"Acceptable" }, PromptTemplate =
"Classify {0} as {1}", ConfidenceThreshold = 0.5, MaxWords = 100 } },
54 { AIClassificationType.ContentType,
new ClassificationConfig { Labels =
new[] {
"News",
"Opinion",
"Advertisement",
"Technical" }, PromptTemplate =
"Classify {0} as {1}", ConfidenceThreshold = 0.5, MaxWords = 100 } },
55 { AIClassificationType.Engagement,
new ClassificationConfig { Labels =
new[] {
"High Engagement",
"Low Engagement" }, PromptTemplate =
"Classify {0} as {1}", ConfidenceThreshold = 0.5, MaxWords = 100 } },
56 { AIClassificationType.Authenticity,
new ClassificationConfig { Labels =
new[] {
"Authentic",
"Fake" }, PromptTemplate =
"Classify {0} as {1}", ConfidenceThreshold = 0.5, MaxWords = 100 } },
57 { AIClassificationType.LanguageProficiency,
new ClassificationConfig { Labels =
new[] {
"Beginner",
"Intermediate",
"Advanced" }, PromptTemplate =
"Classify {0} as {1}", ConfidenceThreshold = 0.5, MaxWords = 100 } },
58 { AIClassificationType.SentimentConfidence,
new ClassificationConfig { Labels =
new[] {
"High Confidence",
"Low Confidence" }, PromptTemplate =
"Classify {0} as {1}", ConfidenceThreshold = 0.5, MaxWords = 100 } },
59 { AIClassificationType.Custom,
new ClassificationConfig { PromptTemplate =
"Classify {0} as {1}", ConfidenceThreshold = 0.5, MaxWords = 100 } }
62 private AIProviderType? _provider;
63 private AIModel? _model;
66 private AIClassificationType? _classificationType;
67 private List<ClassificationConfig> _configs =
new List<ClassificationConfig>();
68 private List<object> _inputSources =
new List<object>();
69 private List<object> _outputTargets =
new List<object>();
70 private AIResponseDelegate _callback;
73 private bool _isExecuted;
77 public IGPALAI ToGPALObject() =>
this;
79 public IAllowAITask WithProvider(AIProviderType provider)
81 if (_provider.HasValue)
83 GPAL.
PublishSimpleEvent(GPALEventType.ERROR,
"Provider already set and cannot be changed.",
this, GPALObjectType.GPALAI);
98 if (credentials ==
null)
103 _credentials = credentials;
108 public IAllowAITask WithCredentials(
string staticApiKey) =>
109 WithCredentials(
GPAL.CredentialsFor(CredentialServiceType.StaticKey).WithServiceKey(staticApiKey).ToGPALObject());
134 _configs.Add(config);
138 public IAllowAIConfig WithClassificationType(AIClassificationType type)
142 _classificationType =
null;
145 if (_task != AITask.Classification)
147 GPAL.
PublishSimpleEvent(GPALEventType.ERROR,
"WithClassificationType is only valid for Classification task.",
this, GPALObjectType.GPALAI);
150 _classificationType = type;
161 if (_task != AITask.Classification || _classificationType != AIClassificationType.Custom)
163 GPAL.
PublishSimpleEvent(GPALEventType.ERROR,
"WithCustomClassification requires Classification task and Custom type.",
this, GPALObjectType.GPALAI);
166 if (config ==
null || config.Labels ==
null || config.PromptTemplate ==
null)
168 GPAL.
PublishSimpleEvent(GPALEventType.ERROR,
"Custom classification requires Labels and PromptTemplate.",
this, GPALObjectType.GPALAI);
171 _configs.Add(config);
177 WithInputFrom(input);
178 WireLiveCallback(input, debounceMilliseconds);
184 WithInputFrom(textArea);
185 WireLiveCallback(textArea, debounceMilliseconds);
191 var timer =
new System.Windows.Forms.Timer { Interval = Math.Max(1, debounceMilliseconds) };
192 timer.Tick += (s, e) =>
197 control.WithCallback(
new EventHandler((s, e) =>
204 public IAllowAIOutput WithInputFrom(
object source)
208 _inputSources.Clear();
213 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"Input source cannot be null.",
this, GPALObjectType.GPALAI);
216 if (!(source is GPALForm.GPALInput || source is GPALForm.GPALTextArea || source is IGPALGrid<string> ||
217 source is GPALFile || source is GPALDatabase || source is
string || source is Uri))
219 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"Unsupported input source type.",
this, GPALObjectType.GPALAI);
222 _inputSources.Add(source);
226 public IAllowAIExecution WithOutputTo(out IGPALGrid<string> target)
230 _outputTargets.Clear();
233 target =
new GPALGrid<string>();
234 _outputTargets.Add(target);
238 public IAllowAIExecution WithOutputTo(
object target)
242 _outputTargets.Clear();
247 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"Output target cannot be null.",
this, GPALObjectType.GPALAI);
250 if (!(target is GPALForm.GPALTextArea || target is GPALForm.GPALRichTextBox || target is GPALDatabase || target is IGPALGrid<string> || target is GPALFile))
252 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"Unsupported output target type.",
this, GPALObjectType.GPALAI);
255 _outputTargets.Add(target);
259 public IAllowAIConfig CallAfterAIResponse(AIResponseDelegate callback)
261 if (callback ==
null)
263 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
"Callback is null.",
this, GPALObjectType.GPALAI);
266 _callback = callback;
270 public IAllowAIExecution WithStatusTo(GPALForm.GPALStatusStrip statusStrip)
272 _statusStrip = statusStrip;
280 private void SetStatus(
string text)
282 if (
null == _statusStrip)
return;
283 _statusStrip.Text = text;
284 System.Windows.Forms.Application.DoEvents();
287 public IAllowAIConfig Execute()
289 if (!_provider.HasValue)
291 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"Provider must be set before execution.",
this, GPALObjectType.GPALAI);
294 ExecuteInternal(
false);
299 private void ExecuteInternal(
bool isInteractive)
301 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Starting AI task for [{_provider}][{_model}][{_task}]",
this, GPALObjectType.GPALAI);
302 IGPALGrid<string> results =
new GPALGrid<string>();
305 _task = _task ==
default ? AITask.Classification : _task;
306 _classificationType = _classificationType ?? AIClassificationType.Custom;
307 if (!_inputSources.Any())
309 _inputSources.Add(
"");
310 GPAL.PublishSimpleEvent(GPALEventType.INFO,
"Using default empty input.",
this, GPALObjectType.GPALAI);
312 if (!_outputTargets.Any())
314 _outputTargets.Add(results);
315 GPAL.PublishSimpleEvent(GPALEventType.INFO,
"Using default grid output.",
this, GPALObjectType.GPALAI);
318 var mergedConfig = MergeConfigs();
320 mergedConfig.AdditionalParameters[
"Model"] = _model.Value.ToString();
322 if (_aiProvider ==
null || _credentials !=
null)
324 _aiProvider =
new AIProvider(_provider.Value,
new RESTClient(), _credentials, mergedConfig);
327 string promptTemplate = GeneratePromptTemplate(mergedConfig);
328 if (
string.IsNullOrEmpty(promptTemplate))
330 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"Failed to generate prompt template.",
this, GPALObjectType.GPALAI);
334 List<string> inputs = ProcessInputs();
337 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
"No valid inputs provided.",
this, GPALObjectType.GPALAI);
341 foreach (var input
in inputs)
343 string prompt =
string.Format(promptTemplate, input, mergedConfig.Labels?.Length > 0 ?
string.Join(
", ", mergedConfig.Labels) :
"");
345 SetStatus(
"Sending request...");
346 SetStatus(
"Waiting for response...");
347 string result = _aiProvider.Call(prompt);
349 if (!
string.IsNullOrEmpty(result))
351 results.AddRow(
new List<string> { result });
352 SetStatus(
"Response received");
356 SetStatus(
"Error: no response from AI provider - see log for details");
360 foreach (var target
in _outputTargets)
362 if (target is IGPALGrid<string> grid)
364 foreach (var row
in results)
367 else if (target is GPALFile file)
369 if (file.Filenames.Count == 0)
371 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
"No filenames specified for GPALFile output. Skipping.",
this, GPALObjectType.GPALAI);
374 GPAL.Converter.WithInput(results).AppendTo(file);
376 else if (target is GPALDatabase db)
378 if (
string.IsNullOrEmpty(db.DatabaseSettings.TableName))
380 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"TableName must be set for GPALDatabase output.",
this, GPALObjectType.GPALAI);
383 GPAL.Converter.WithInput(results).SaveTo(db);
385 else if (target is GPALForm.GPALTextArea textArea)
387 textArea.Text =
string.Join(
"\n", results.Select(row =>
string.Join(
" ", row)));
389 else if (target is GPALForm.GPALRichTextBox richTextBox)
391 richTextBox.Text =
string.Join(
"\n", results.Select(row =>
string.Join(
" ", row)));
395 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"Unsupported output target.",
this, GPALObjectType.GPALAI);
399 if (_callback !=
null)
401 int callbackResult = _callback(
this, results);
402 if (callbackResult == -1)
404 string str =
"AI Callback requested application exit.";
405 GPAL.PublishSimpleEvent(GPALEventType.INFO, str,
this, GPALObjectType.GPALAI);
406 throw new GPALException(str);
412 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION,
"AI execution failed.",
this, GPALObjectType.GPALAI, ex);
413 SetStatus($
"Error: {ex.Message}");
417 private ClassificationConfig MergeConfigs()
419 var merged =
new ClassificationConfig();
420 if (_task == AITask.Classification && _classificationType.HasValue && ClassificationConfigs.TryGetValue(_classificationType.Value, out var defaultConfig))
422 merged.Labels = defaultConfig.Labels;
423 merged.PromptTemplate = defaultConfig.PromptTemplate;
424 merged.ConfidenceThreshold = defaultConfig.ConfidenceThreshold;
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.ConfidenceThreshold.HasValue) merged.ConfidenceThreshold = config.ConfidenceThreshold;
435 if (config.AdditionalParameters !=
null)
437 merged.AdditionalParameters =
new Dictionary<string, object>(config.AdditionalParameters);
443 private string GeneratePromptTemplate(ClassificationConfig config)
447 case AITask.Summarization:
448 return $
"Summarize {{0}} in {config.MaxWords ?? 100} words{(config.IncludeKeywords == true ? ", including keywords
" : "")}";
449 case AITask.Classification:
450 if (_classificationType == AIClassificationType.Custom &&
string.IsNullOrEmpty(config.PromptTemplate))
452 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"Custom classification requires a PromptTemplate.",
this, GPALObjectType.GPALAI);
455 return config.PromptTemplate ?? (ClassificationConfigs.TryGetValue(_classificationType ?? AIClassificationType.Custom, out var defaultConfig) ? defaultConfig.PromptTemplate :
string.Empty);
456 case AITask.TextGeneration:
457 return config.PromptTemplate ??
"Generate text based on {0}";
458 case AITask.DataAugmentation:
459 return config.PromptTemplate ??
"Augment data for {0}";
461 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"Unsupported AI task.",
this, GPALObjectType.GPALAI);
466 private List<string> ProcessInputs()
468 var inputs =
new List<string>();
471 foreach (var source
in _inputSources)
475 case GPALForm.GPALInput input:
476 inputs.Add(input.Text);
478 case GPALForm.GPALTextArea textArea:
479 inputs.Add(textArea.Text);
481 case IGPALGrid<string> grid:
482 foreach (var row
in grid) inputs.Add(
string.Join(
" ", row));
485 if (!FileHelper.TokenizeFile(file,
null))
487 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"Failed to tokenize file.",
this, GPALObjectType.GPALAI);
490 foreach (var row
in ((IGPALFileInternal)file).TokenList)
491 inputs.Add(
string.Join(
" ", row));
493 case GPALDatabase database:
494 DatabaseHelper.TokenizeDatabase(
null, database);
495 foreach (var row
in database.Tokens)
496 inputs.Add(
string.Join(
" ", row));
502 inputs.Add(FetchWebContent(uri));
505 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"Unsupported input source.",
this, GPALObjectType.GPALAI);
512 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION,
"Failed to process inputs.",
this, GPALObjectType.GPALAI, ex);
517 private string FetchWebContent(Uri uri)
521 using (var client =
new HttpClient())
523 client.DefaultRequestHeaders.Add(
"User-Agent", BrowserHelper.GetUserAgent(GPAL.Browsers[0]));
525 string content = client.GetStringAsync(uri).GetAwaiter().GetResult();
526 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Fetched web content from [{uri}]",
this, GPALObjectType.GPALAI);
532 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Failed to fetch web content",
this, GPALObjectType.GPALAI, ex);