|
|
@@ -143,11 +143,13 @@ async function callAiSuggestions<T>(
|
|
|
): Promise<T[]> {
|
|
|
if (!apiSettings) throw new Error('API 未配置');
|
|
|
|
|
|
+ const isOllama = apiSettings.apiUrl.includes('11434') || apiSettings.apiUrl.includes('ollama');
|
|
|
+
|
|
|
const res = await fetch('/api/proxy', {
|
|
|
method: 'POST',
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
body: JSON.stringify({
|
|
|
- targetUrl: `${apiSettings.apiUrl.replace(/\/$/, '')}/chat/completions`,
|
|
|
+ targetUrl: apiSettings.apiUrl,
|
|
|
headers: {
|
|
|
Authorization: `Bearer ${apiSettings.apiKey}`,
|
|
|
'Content-Type': 'application/json',
|
|
|
@@ -161,6 +163,7 @@ async function callAiSuggestions<T>(
|
|
|
temperature: 0.8,
|
|
|
stream: false,
|
|
|
},
|
|
|
+ isOllama,
|
|
|
}),
|
|
|
});
|
|
|
|
|
|
@@ -170,14 +173,18 @@ async function callAiSuggestions<T>(
|
|
|
}
|
|
|
|
|
|
const data = await res.json();
|
|
|
- if (data.error) throw new Error(data.error.message || 'AI 请求失败');
|
|
|
+ if (data.error) throw new Error(data.error.message || data.error.detail || 'AI 请求失败');
|
|
|
|
|
|
- const content = data.choices?.[0]?.message?.content;
|
|
|
+ const content = data.choices?.[0]?.message?.content || data.response || data.content;
|
|
|
if (!content) throw new Error('AI 返回内容为空');
|
|
|
|
|
|
// Strip possible markdown code fence around JSON
|
|
|
const cleaned = content.replace(/```(?:json)?\n?/g, '').trim();
|
|
|
- return JSON.parse(cleaned) as T[];
|
|
|
+ try {
|
|
|
+ return JSON.parse(cleaned) as T[];
|
|
|
+ } catch {
|
|
|
+ throw new Error('AI 返回了无效的 JSON,请重试');
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
// ─── Step 1: Novel Type ──────────────────────────────────────────────────────
|