feat: implement AI connection test in frontend

- Add test connection button with loading states in AI settings
- Integrate connection test API with proper error handling
- Update tag recommendation settings with improved UI

Signed-off-by: ChaoLiu <chaoliu719@gmail.com>
This commit is contained in:
ChaoLiu 2025-08-18 17:14:51 +08:00
parent 69f946844b
commit 30202db710
2 changed files with 47 additions and 8 deletions

View File

@ -9,6 +9,7 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Separator } from "@/components/ui/separator";
import { Switch } from "@/components/ui/switch";
import { workspaceServiceClient } from "@/grpcweb";
import { workspaceStore } from "@/store";
import { workspaceSettingNamePrefix } from "@/store/common";
import { WorkspaceSetting_AiSetting, WorkspaceSetting_Key } from "@/types/proto/api/v1/workspace_service";
@ -20,6 +21,7 @@ const AISettings = observer(() => {
const [originalSetting, setOriginalSetting] = useState<WorkspaceSetting_AiSetting>(workspaceStore.state.aiSetting);
const [aiSetting, setAiSetting] = useState<WorkspaceSetting_AiSetting>(originalSetting);
const [showApiKey, setShowApiKey] = useState(false);
const [isTestingConnection, setIsTestingConnection] = useState(false);
const updatePartialSetting = (partial: Partial<WorkspaceSetting_AiSetting>) => {
setAiSetting(WorkspaceSetting_AiSetting.fromPartial({ ...aiSetting, ...partial }));
@ -62,6 +64,34 @@ const AISettings = observer(() => {
const resetSetting = () => setAiSetting(originalSetting);
const testConnection = async () => {
if (!aiSetting.baseUrl || !aiSetting.apiKey || !aiSetting.model) {
toast.error(t("setting.ai-section.test-connection-incomplete"));
return;
}
setIsTestingConnection(true);
try {
const response = await workspaceServiceClient.testAiConnection({
baseUrl: aiSetting.baseUrl || "https://api.openai.com/v1",
apiKey: aiSetting.apiKey,
model: aiSetting.model,
timeoutSeconds: aiSetting.timeoutSeconds || 10,
});
if (response.success) {
toast.success(t("setting.ai-section.test-connection-success"));
} else {
toast.error(`${t("setting.ai-section.test-connection-failed")}: ${response.message}`);
}
} catch (error: any) {
console.error("AI connection test failed:", error);
toast.error(`${t("setting.ai-section.test-connection-failed")}: ${error.message || error}`);
} finally {
setIsTestingConnection(false);
}
};
// 只比较全局AI配置的变化不包括子功能配置
const globalSettingChanged = !isEqual(
{
@ -188,13 +218,22 @@ const AISettings = observer(() => {
{/* Action Buttons */}
{aiSetting.enableAi && (
<div className="w-full flex flex-row justify-end items-center gap-2 mt-4">
<Button variant="outline" onClick={resetSetting} disabled={!globalSettingChanged}>
{t("common.cancel")}
</Button>
<Button onClick={updateSetting} disabled={!globalSettingChanged}>
{t("common.save")}
<div className="w-full flex flex-row justify-between items-center gap-2 mt-4">
<Button
variant="outline"
onClick={testConnection}
disabled={isTestingConnection || !aiSetting.baseUrl || !aiSetting.apiKey || !aiSetting.model}
>
{isTestingConnection ? t("setting.ai-section.testing-connection") : t("setting.ai-section.test-connection")}
</Button>
<div className="flex flex-row items-center gap-2">
<Button variant="outline" onClick={resetSetting} disabled={!globalSettingChanged}>
{t("common.cancel")}
</Button>
<Button onClick={updateSetting} disabled={!globalSettingChanged}>
{t("common.save")}
</Button>
</div>
</div>
)}
</div>

View File

@ -46,7 +46,7 @@ const TagRecommendationSection = observer(({ aiSetting, onSettingChange, disable
systemPrompt: "",
requestsPerMinute: 10,
});
setOriginalTagConfig(newTagConfig);
setTagConfig(newTagConfig);
}, [aiSetting]);
@ -80,7 +80,7 @@ const TagRecommendationSection = observer(({ aiSetting, onSettingChange, disable
name: `${workspaceSettingNamePrefix}${WorkspaceSetting_Key.AI}`,
aiSetting: newAiSetting,
});
setOriginalTagConfig(newTagConfig);
setTagConfig(newTagConfig);
onSettingChange(newAiSetting);