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 { Label } from "@/components/ui/label";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import { Switch } from "@/components/ui/switch"; import { Switch } from "@/components/ui/switch";
import { workspaceServiceClient } from "@/grpcweb";
import { workspaceStore } from "@/store"; import { workspaceStore } from "@/store";
import { workspaceSettingNamePrefix } from "@/store/common"; import { workspaceSettingNamePrefix } from "@/store/common";
import { WorkspaceSetting_AiSetting, WorkspaceSetting_Key } from "@/types/proto/api/v1/workspace_service"; 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 [originalSetting, setOriginalSetting] = useState<WorkspaceSetting_AiSetting>(workspaceStore.state.aiSetting);
const [aiSetting, setAiSetting] = useState<WorkspaceSetting_AiSetting>(originalSetting); const [aiSetting, setAiSetting] = useState<WorkspaceSetting_AiSetting>(originalSetting);
const [showApiKey, setShowApiKey] = useState(false); const [showApiKey, setShowApiKey] = useState(false);
const [isTestingConnection, setIsTestingConnection] = useState(false);
const updatePartialSetting = (partial: Partial<WorkspaceSetting_AiSetting>) => { const updatePartialSetting = (partial: Partial<WorkspaceSetting_AiSetting>) => {
setAiSetting(WorkspaceSetting_AiSetting.fromPartial({ ...aiSetting, ...partial })); setAiSetting(WorkspaceSetting_AiSetting.fromPartial({ ...aiSetting, ...partial }));
@ -62,6 +64,34 @@ const AISettings = observer(() => {
const resetSetting = () => setAiSetting(originalSetting); 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配置的变化不包括子功能配置 // 只比较全局AI配置的变化不包括子功能配置
const globalSettingChanged = !isEqual( const globalSettingChanged = !isEqual(
{ {
@ -188,13 +218,22 @@ const AISettings = observer(() => {
{/* Action Buttons */} {/* Action Buttons */}
{aiSetting.enableAi && ( {aiSetting.enableAi && (
<div className="w-full flex flex-row justify-end items-center gap-2 mt-4"> <div className="w-full flex flex-row justify-between items-center gap-2 mt-4">
<Button variant="outline" onClick={resetSetting} disabled={!globalSettingChanged}> <Button
{t("common.cancel")} variant="outline"
</Button> onClick={testConnection}
<Button onClick={updateSetting} disabled={!globalSettingChanged}> disabled={isTestingConnection || !aiSetting.baseUrl || !aiSetting.apiKey || !aiSetting.model}
{t("common.save")} >
{isTestingConnection ? t("setting.ai-section.testing-connection") : t("setting.ai-section.test-connection")}
</Button> </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>
)} )}
</div> </div>

View File

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