Skip to content

Commit 271933c

Browse files
committed
feat: Add IO Intelligence as translation provider
- New LLM-based provider using OpenAI-compatible API - Base URL: https://api.intelligence.io.solutions/api/v1/chat/completions - Dynamic model selection via dropdown with Refresh button - Models fetched from /api/v1/models endpoint (no auth required)
1 parent d823b0c commit 271933c

6 files changed

Lines changed: 311 additions & 1 deletion

File tree

src/apis/LLMRequestDataFactory.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public static class LLMRequestDataFactory
1414
["Ollama"] = typeof(OllamaRequestData),
1515
["OpenRouter"] = typeof(OpenRouterRequestData),
1616
["OpenAI"] = typeof(OpenAIRequestData),
17+
["IOIntelligence"] = typeof(OpenAIRequestData),
1718
["XAI"] = typeof(XAIRequestData),
1819
["base"] = typeof(BaseLLMRequestData)
1920
};

src/apis/TranslateAPI.cs

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,15 @@ public static readonly Dictionary<string, Func<string, CancellationToken, Task<s
2525
{ "OpenAI", OpenAI },
2626
{ "DeepL", DeepL },
2727
{ "OpenRouter", OpenRouter },
28+
{ "IOIntelligence", IOIntelligence },
2829
{ "Youdao", Youdao },
2930
{ "MTranServer", MTranServer },
3031
{ "Baidu", Baidu },
3132
{ "LibreTranslate", LibreTranslate },
3233
};
3334
public static readonly List<string> LLM_BASED_APIS = new()
3435
{
35-
"Ollama", "OpenAI", "OpenRouter"
36+
"Ollama", "OpenAI", "OpenRouter", "IOIntelligence"
3637
};
3738
public static readonly List<string> NO_CONFIG_APIS = new()
3839
{
@@ -258,6 +259,73 @@ public static async Task<string> OpenRouter(string text, CancellationToken token
258259
return $"[ERROR] Translation Failed: HTTP Error - {response.StatusCode}";
259260
}
260261

262+
public static async Task<string> IOIntelligence(string text, CancellationToken token = default)
263+
{
264+
var config = Translator.Setting["IOIntelligence"] as IOIntelligenceConfig;
265+
string language = IOIntelligenceConfig.SupportedLanguages.TryGetValue(
266+
Translator.Setting.TargetLanguage, out var langValue) ? langValue : Translator.Setting.TargetLanguage;
267+
string apiUrl = "https://api.intelligence.io.solutions/api/v1/chat/completions";
268+
269+
var messages = new List<BaseLLMConfig.Message>
270+
{
271+
new BaseLLMConfig.Message { role = "system", content = string.Format(Prompt, language) },
272+
new BaseLLMConfig.Message { role = "user", content = $"🔤 {text} 🔤" }
273+
};
274+
275+
if (Translator.Setting.ContextAware)
276+
{
277+
foreach (var entry in Translator.Caption.AwareContexts)
278+
{
279+
string translatedText = entry.TranslatedText;
280+
if (translatedText.Contains("[ERROR]") || translatedText.Contains("[WARNING]"))
281+
continue;
282+
translatedText = RegexPatterns.NoticePrefix().Replace(translatedText, "");
283+
284+
messages.InsertRange(1, [
285+
new BaseLLMConfig.Message { role = "user", content = $"🔤 {entry.SourceText} 🔤" },
286+
new BaseLLMConfig.Message { role = "assistant", content = $"{translatedText}" }
287+
]);
288+
}
289+
}
290+
291+
var requestData = LLMRequestDataFactory.Create("IOIntelligence", config.ModelName, messages, config.Temperature);
292+
293+
string jsonContent = JsonSerializer.Serialize(requestData, requestData.GetType());
294+
var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
295+
client.DefaultRequestHeaders.Clear();
296+
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {config?.ApiKey}");
297+
298+
HttpResponseMessage response;
299+
try
300+
{
301+
response = await client.PostAsync(apiUrl, content, token);
302+
}
303+
catch (OperationCanceledException ex)
304+
{
305+
if (ex.Message.StartsWith("The request"))
306+
return $"[ERROR] Translation Failed: The request was canceled due to timeout (> 8 seconds), " +
307+
$"please use a faster API or check network connection.";
308+
throw;
309+
}
310+
catch (Exception ex)
311+
{
312+
return $"[ERROR] Translation Failed: {ex.Message}";
313+
}
314+
315+
if (response.IsSuccessStatusCode)
316+
{
317+
var responseContent = await response.Content.ReadAsStringAsync();
318+
var jsonResponse = JsonSerializer.Deserialize<JsonElement>(responseContent);
319+
var output = jsonResponse.GetProperty("choices")[0]
320+
.GetProperty("message")
321+
.GetProperty("content")
322+
.GetString() ?? string.Empty;
323+
return RegexPatterns.ModelThinking().Replace(output, "");
324+
}
325+
else
326+
return $"[ERROR] Translation Failed: HTTP Error - {response.StatusCode}";
327+
}
328+
261329
public static async Task<string> Google(string text, CancellationToken token = default)
262330
{
263331
var language = Translator.Setting?.TargetLanguage;

src/models/Setting.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ public Setting()
197197
{ "Ollama", [new OllamaConfig()] },
198198
{ "OpenAI", [new OpenAIConfig()] },
199199
{ "OpenRouter", [new OpenRouterConfig()] },
200+
{ "IOIntelligence", [new IOIntelligenceConfig()] },
200201
{ "DeepL", [new DeepLConfig()] },
201202
{ "Youdao", [new YoudaoConfig()] },
202203
{ "Baidu", [new BaiduConfig()] },
@@ -210,6 +211,7 @@ public Setting()
210211
{ "Ollama", 0 },
211212
{ "OpenAI", 0 },
212213
{ "OpenRouter", 0 },
214+
{ "IOIntelligence", 0 },
213215
{ "DeepL", 0 },
214216
{ "Youdao", 0 },
215217
{ "Baidu", 0 },

src/models/TranslateAPIConfig.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.ComponentModel;
2+
using System.Net.Http;
23
using System.Runtime.CompilerServices;
34
using System.Text.Json.Serialization;
45

@@ -164,6 +165,51 @@ public string ApiKey
164165
}
165166
}
166167

168+
public class IOIntelligenceConfig : BaseLLMConfig
169+
{
170+
private static readonly HttpClient httpClient = new HttpClient()
171+
{
172+
Timeout = TimeSpan.FromSeconds(10)
173+
};
174+
175+
public class ModelInfo
176+
{
177+
public string id { get; set; }
178+
}
179+
180+
public class ModelsResponse
181+
{
182+
public List<ModelInfo> data { get; set; }
183+
}
184+
185+
private string apiKey = "";
186+
public string ApiKey
187+
{
188+
get => apiKey;
189+
set
190+
{
191+
apiKey = value;
192+
OnPropertyChanged();
193+
}
194+
}
195+
196+
public static async Task<List<string>> FetchModelsAsync()
197+
{
198+
try
199+
{
200+
var response = await httpClient.GetAsync("https://api.intelligence.io.solutions/api/v1/models");
201+
if (response.IsSuccessStatusCode)
202+
{
203+
string json = await response.Content.ReadAsStringAsync();
204+
var modelsResponse = System.Text.Json.JsonSerializer.Deserialize<ModelsResponse>(json);
205+
return modelsResponse?.data?.Select(m => m.id).ToList() ?? new List<string>();
206+
}
207+
}
208+
catch { }
209+
return new List<string>();
210+
}
211+
}
212+
167213
public class DeepLConfig : TranslateAPIConfig
168214
{
169215
[JsonIgnore]

src/windows/SettingWindow.xaml

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,20 @@
9797
</StackPanel>
9898
</ui:Button>
9999

100+
<ui:Button
101+
x:Name="IOIntelligenceButton"
102+
Margin="10,5,5,0"
103+
Padding="10"
104+
HorizontalAlignment="Stretch"
105+
HorizontalContentAlignment="Left"
106+
Background="Transparent"
107+
Click="NavigationButton_Click"
108+
Tag="IOIntelligence">
109+
<StackPanel Orientation="Horizontal">
110+
<ui:TextBlock VerticalAlignment="Center" Text="IO Intelligence" />
111+
</StackPanel>
112+
</ui:Button>
113+
100114
<ui:Button
101115
x:Name="DeepLButton"
102116
Margin="10,5,5,0"
@@ -601,6 +615,143 @@
601615
</ui:Card>
602616
</StackPanel>
603617

618+
<StackPanel x:Name="IOIntelligenceSection" Margin="20">
619+
<ui:TextBlock
620+
Margin="10"
621+
FontSize="20"
622+
FontWeight="Medium"
623+
Text="IO Intelligence" />
624+
<ui:Card Padding="15">
625+
<StackPanel>
626+
<Grid Margin="15,0,15,10">
627+
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal">
628+
<ui:TextBlock
629+
Margin="0,0,10,0"
630+
VerticalAlignment="Center"
631+
FontWeight="Bold"
632+
Text="Current Config: " />
633+
<ui:Button
634+
Margin="0,0,10,0"
635+
Padding="8,4,8,4"
636+
Click="PriorButton_Click"
637+
Icon="{ui:SymbolIcon chevronLeft12}"
638+
Tag="IOIntelligence" />
639+
<ui:TextBlock
640+
x:Name="IOIntelligenceIndex"
641+
VerticalAlignment="Center"
642+
Text="1/1" />
643+
<ui:Button
644+
Margin="10,0,0,0"
645+
Padding="8,4,8,4"
646+
Click="NextButton_Click"
647+
Icon="{ui:SymbolIcon chevronRight12}"
648+
Tag="IOIntelligence" />
649+
</StackPanel>
650+
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
651+
<ui:Button
652+
Margin="0,0,10,0"
653+
Click="NewButton_Click"
654+
Content="New"
655+
Tag="IOIntelligence" />
656+
<ui:Button
657+
Margin="0,0,-10,0"
658+
Click="DeleteButton_Click"
659+
Content="Delete"
660+
Tag="IOIntelligence" />
661+
<ui:Flyout x:Name="IOIntelligenceDeleteFlyout">
662+
<TextBlock
663+
Width="120"
664+
Text="You must keep at least one config."
665+
TextWrapping="Wrap" />
666+
</ui:Flyout>
667+
</StackPanel>
668+
</Grid>
669+
<Separator Margin="0,0,0,10" />
670+
<Grid x:Name="IOIntelligenceGrid">
671+
<Grid.RowDefinitions>
672+
<RowDefinition Height="Auto" />
673+
<RowDefinition Height="Auto" />
674+
<RowDefinition Height="Auto" />
675+
</Grid.RowDefinitions>
676+
<Grid.ColumnDefinitions>
677+
<ColumnDefinition Width="Auto" />
678+
<ColumnDefinition Width="Auto" />
679+
</Grid.ColumnDefinitions>
680+
681+
<StackPanel
682+
Grid.Row="0"
683+
Grid.Column="0"
684+
Margin="15,0,0,0"
685+
Orientation="Vertical">
686+
<ui:TextBlock Margin="2.5,0,0,5" Text="Model Name" />
687+
<ComboBox
688+
x:Name="IOIntelligenceModelComboBox"
689+
Width="130"
690+
Height="30"
691+
Padding="10,4,10,7"
692+
FontSize="13.3"
693+
IsEditable="True"
694+
DropDownOpened="IOIntelligenceModelComboBox_DropDownOpened"
695+
Text="{Binding [IOIntelligence].ModelName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
696+
</StackPanel>
697+
<StackPanel
698+
Grid.Row="1"
699+
Grid.Column="0"
700+
Margin="15,10,0,0"
701+
Orientation="Vertical">
702+
<ui:TextBlock Margin="2.5,0,0,5" Text="Temperature" />
703+
<ui:NumberBox
704+
Width="130"
705+
Height="30"
706+
Padding="10,4,10,7"
707+
ClearButtonEnabled="False"
708+
FontSize="13.3"
709+
LargeChange="1"
710+
Maximum="2"
711+
Minimum="0"
712+
SmallChange="0.1"
713+
Value="{Binding [IOIntelligence].Temperature, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
714+
</StackPanel>
715+
<StackPanel
716+
Grid.Row="0"
717+
Grid.Column="1"
718+
Margin="15,0,0,0"
719+
Orientation="Vertical">
720+
<ui:TextBlock Margin="2.5,0,0,5" Text="API Key" />
721+
<StackPanel Orientation="Horizontal">
722+
<ui:TextBox
723+
Width="200"
724+
Height="30"
725+
Padding="10,4,10,7"
726+
FontSize="13.3"
727+
Text="{Binding [IOIntelligence].ApiKey, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
728+
<ui:Button
729+
Width="75"
730+
Height="30"
731+
Margin="5,0,0,0"
732+
Click="IOIntelligenceGetKey_Click"
733+
Content="Get Key"
734+
ToolTip="Get API Key from IO Intelligence" />
735+
</StackPanel>
736+
</StackPanel>
737+
<StackPanel
738+
Grid.Row="1"
739+
Grid.Column="1"
740+
Margin="15,10,0,0"
741+
Orientation="Vertical">
742+
<ui:TextBlock Margin="2.5,0,0,5" Text="Fetch Models" />
743+
<ui:Button
744+
Width="200"
745+
Height="30"
746+
Click="IOIntelligenceRefresh_Click"
747+
Content="Refresh Model List"
748+
Icon="{ui:SymbolIcon ArrowSync16}" />
749+
</StackPanel>
750+
</Grid>
751+
</StackPanel>
752+
</ui:Card>
753+
</StackPanel>
754+
604755
<StackPanel x:Name="DeepLSection" Margin="20">
605756
<ui:TextBlock
606757
Margin="10"

src/windows/SettingWindow.xaml.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Diagnostics;
12
using System.Windows;
23
using System.Windows.Controls;
34
using System.Windows.Input;
@@ -135,6 +136,47 @@ private void OllamaAPIUrlInfo_MouseLeave(object sender, MouseEventArgs e)
135136
OllamaAPIUrlInfoFlyout.Hide();
136137
}
137138

139+
private void IOIntelligenceGetKey_Click(object sender, RoutedEventArgs e)
140+
{
141+
Process.Start(new ProcessStartInfo
142+
{
143+
FileName = "https://ai.io.net/ai/api-keys",
144+
UseShellExecute = true
145+
});
146+
}
147+
148+
private async void IOIntelligenceRefresh_Click(object sender, RoutedEventArgs e)
149+
{
150+
var button = sender as Button;
151+
button.IsEnabled = false;
152+
153+
try
154+
{
155+
var models = await IOIntelligenceConfig.FetchModelsAsync();
156+
IOIntelligenceModelComboBox.Items.Clear();
157+
foreach (var model in models)
158+
{
159+
IOIntelligenceModelComboBox.Items.Add(model);
160+
}
161+
}
162+
finally
163+
{
164+
button.IsEnabled = true;
165+
}
166+
}
167+
168+
private async void IOIntelligenceModelComboBox_DropDownOpened(object sender, EventArgs e)
169+
{
170+
if (IOIntelligenceModelComboBox.Items.Count == 0)
171+
{
172+
var models = await IOIntelligenceConfig.FetchModelsAsync();
173+
foreach (var model in models)
174+
{
175+
IOIntelligenceModelComboBox.Items.Add(model);
176+
}
177+
}
178+
}
179+
138180
private void SwitchConfig(string apiName, int index)
139181
{
140182
if (index < 0 || index >= Translator.Setting.Configs[apiName].Count)

0 commit comments

Comments
 (0)