@mirodan/ai-insight 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -7,6 +7,19 @@ import { messages } from '../i18n/messages'
|
|
|
7
7
|
|
|
8
8
|
const { t, locale: i18nLocale } = useI18n({ messages, useScope: 'local' })
|
|
9
9
|
|
|
10
|
+
const insightErrorMap: Record<string, string> = {
|
|
11
|
+
INSIGHT_UNAUTHORIZED: 'aiInsight.unauthorized',
|
|
12
|
+
INSIGHT_INVALID_KEY: 'aiInsight.invalidKey',
|
|
13
|
+
INSIGHT_KEY_DISABLED: 'aiInsight.keyDisabled',
|
|
14
|
+
INSIGHT_DOMAIN_DENIED: 'aiInsight.domainDenied',
|
|
15
|
+
INSIGHT_DAILY_LIMIT_EXCEEDED: 'aiInsight.dailyLimit',
|
|
16
|
+
INSIGHT_MONTHLY_LIMIT_EXCEEDED: 'aiInsight.monthlyLimit',
|
|
17
|
+
CONFIG_ENDPOINT_MISSING: 'aiInsight.endpointMissing',
|
|
18
|
+
CONFIG_KEY_MISSING: 'aiInsight.keyMissing',
|
|
19
|
+
BACKEND_UNREACHABLE: 'aiInsight.backendUnreachable',
|
|
20
|
+
BACKEND_ERROR: 'aiInsight.backendError'
|
|
21
|
+
}
|
|
22
|
+
|
|
10
23
|
interface Props {
|
|
11
24
|
open: boolean
|
|
12
25
|
data: unknown
|
|
@@ -83,7 +96,8 @@ async function loadInsight() {
|
|
|
83
96
|
let msg = t('aiInsight.requestError', { status: response.status })
|
|
84
97
|
try {
|
|
85
98
|
const json = await response.json()
|
|
86
|
-
|
|
99
|
+
const code = json.statusMessage || json.data?.message || json.message || ''
|
|
100
|
+
msg = (code && insightErrorMap[code] ? t(insightErrorMap[code]) : code) || msg
|
|
87
101
|
} catch {
|
|
88
102
|
const text = await response.text().catch(() => '')
|
|
89
103
|
if (text) msg = text
|
|
@@ -92,7 +106,7 @@ async function loadInsight() {
|
|
|
92
106
|
}
|
|
93
107
|
|
|
94
108
|
let result = ''
|
|
95
|
-
for await (const delta of parseUIMessageStream(response)) {
|
|
109
|
+
for await (const delta of parseUIMessageStream(response, t('aiInsight.streamError'))) {
|
|
96
110
|
result += delta
|
|
97
111
|
insight.value = result
|
|
98
112
|
}
|
|
@@ -7,7 +7,18 @@ export const messages = {
|
|
|
7
7
|
askQuestion: 'Задать вопрос ИИ...',
|
|
8
8
|
retry: 'Повторить',
|
|
9
9
|
requestError: 'Ошибка запроса ({status})',
|
|
10
|
-
unknownError: 'Неизвестная ошибка'
|
|
10
|
+
unknownError: 'Неизвестная ошибка',
|
|
11
|
+
streamError: 'Ошибка AI-сервиса',
|
|
12
|
+
unauthorized: 'Неверный или отсутствует заголовок Authorization',
|
|
13
|
+
invalidKey: 'Неверный API-ключ',
|
|
14
|
+
keyDisabled: 'API-ключ отключён',
|
|
15
|
+
domainDenied: 'Домен не разрешён',
|
|
16
|
+
dailyLimit: 'Превышен дневной лимит запросов',
|
|
17
|
+
monthlyLimit: 'Превышен месячный лимит запросов',
|
|
18
|
+
endpointMissing: 'Не указан endpoint AI Insight',
|
|
19
|
+
keyMissing: 'Не указан API-ключ AI Insight',
|
|
20
|
+
backendUnreachable: 'Не удалось подключиться к AI Insight',
|
|
21
|
+
backendError: 'Ошибка AI Insight'
|
|
11
22
|
}
|
|
12
23
|
},
|
|
13
24
|
en: {
|
|
@@ -18,7 +29,18 @@ export const messages = {
|
|
|
18
29
|
askQuestion: 'Ask the AI...',
|
|
19
30
|
retry: 'Retry',
|
|
20
31
|
requestError: 'Request failed ({status})',
|
|
21
|
-
unknownError: 'Unknown error'
|
|
32
|
+
unknownError: 'Unknown error',
|
|
33
|
+
streamError: 'AI service error',
|
|
34
|
+
unauthorized: 'Invalid or missing Authorization header',
|
|
35
|
+
invalidKey: 'Invalid API key',
|
|
36
|
+
keyDisabled: 'API key is disabled',
|
|
37
|
+
domainDenied: 'Domain is not allowed',
|
|
38
|
+
dailyLimit: 'Daily request limit exceeded',
|
|
39
|
+
monthlyLimit: 'Monthly request limit exceeded',
|
|
40
|
+
endpointMissing: 'AI Insight endpoint is not configured',
|
|
41
|
+
keyMissing: 'AI Insight API key is not configured',
|
|
42
|
+
backendUnreachable: 'Failed to reach AI Insight backend',
|
|
43
|
+
backendError: 'AI Insight error'
|
|
22
44
|
}
|
|
23
45
|
}
|
|
24
46
|
}
|
|
@@ -1,23 +1,8 @@
|
|
|
1
1
|
import { defineEventHandler, createError, getHeader, setHeaders, readBody } from 'h3'
|
|
2
2
|
import { useRuntimeConfig } from '#imports'
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
CONFIG_ENDPOINT_MISSING: 'Не указан endpoint AI Insight',
|
|
7
|
-
CONFIG_KEY_MISSING: 'Не указан API-ключ AI Insight',
|
|
8
|
-
BACKEND_UNREACHABLE: 'Не удалось подключиться к AI Insight',
|
|
9
|
-
BACKEND_ERROR: 'Ошибка AI Insight'
|
|
10
|
-
},
|
|
11
|
-
en: {
|
|
12
|
-
CONFIG_ENDPOINT_MISSING: 'AI Insight endpoint is not configured',
|
|
13
|
-
CONFIG_KEY_MISSING: 'AI Insight API key is not configured',
|
|
14
|
-
BACKEND_UNREACHABLE: 'Failed to reach AI Insight backend',
|
|
15
|
-
BACKEND_ERROR: 'AI Insight error'
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function tError(key, lang) {
|
|
20
|
-
return tErrorMessages[lang === 'en' ? 'en' : 'ru']?.[key] || key
|
|
4
|
+
function proxyError(statusCode, code) {
|
|
5
|
+
return createError({ statusCode, statusMessage: code, message: code, data: { message: code } })
|
|
21
6
|
}
|
|
22
7
|
|
|
23
8
|
export default defineEventHandler(async (event) => {
|
|
@@ -26,13 +11,12 @@ export default defineEventHandler(async (event) => {
|
|
|
26
11
|
|
|
27
12
|
const endpoint = config.aiInsight.endpoint
|
|
28
13
|
const hasKey = !!config.aiInsight.key
|
|
29
|
-
const lang = getHeader(event, 'accept-language')?.slice(0, 2) || 'ru'
|
|
30
14
|
|
|
31
15
|
if (!endpoint) {
|
|
32
|
-
throw
|
|
16
|
+
throw proxyError(500, 'CONFIG_ENDPOINT_MISSING')
|
|
33
17
|
}
|
|
34
18
|
if (!hasKey) {
|
|
35
|
-
throw
|
|
19
|
+
throw proxyError(500, 'CONFIG_KEY_MISSING')
|
|
36
20
|
}
|
|
37
21
|
|
|
38
22
|
console.log('[ai-insight/proxy] →', endpoint, 'key:', hasKey ? 'yes' : 'NO KEY', 'data:', typeof body?.data, 'prompt:', body?.prompt?.slice(0, 80))
|
|
@@ -54,21 +38,21 @@ export default defineEventHandler(async (event) => {
|
|
|
54
38
|
})
|
|
55
39
|
} catch (fetchErr) {
|
|
56
40
|
console.error('[ai-insight/proxy] fetch error:', fetchErr)
|
|
57
|
-
throw
|
|
41
|
+
throw proxyError(502, 'BACKEND_UNREACHABLE')
|
|
58
42
|
}
|
|
59
43
|
|
|
60
44
|
console.log('[ai-insight/proxy] ←', response.status, response.statusText)
|
|
61
45
|
|
|
62
46
|
if (!response.ok) {
|
|
63
47
|
const text = await response.text().catch(() => '')
|
|
64
|
-
let
|
|
48
|
+
let code
|
|
65
49
|
try {
|
|
66
50
|
const json = JSON.parse(text)
|
|
67
|
-
|
|
51
|
+
code = json.statusMessage || json.data?.message || json.message || text
|
|
68
52
|
} catch {
|
|
69
|
-
|
|
53
|
+
code = text || response.statusText
|
|
70
54
|
}
|
|
71
|
-
throw
|
|
55
|
+
throw proxyError(response.status, code || 'BACKEND_ERROR')
|
|
72
56
|
}
|
|
73
57
|
|
|
74
58
|
setHeaders(event, {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export async function* parseUIMessageStream(response: Response) {
|
|
1
|
+
export async function* parseUIMessageStream(response: Response, fallbackErrorText = 'AI stream error') {
|
|
2
2
|
const reader = response.body!.getReader()
|
|
3
3
|
const decoder = new TextDecoder()
|
|
4
4
|
let buffer = ''
|
|
@@ -19,9 +19,18 @@ export async function* parseUIMessageStream(response: Response) {
|
|
|
19
19
|
const chunk = JSON.parse(data)
|
|
20
20
|
if (chunk.type === 'text-delta' && chunk.delta != null) {
|
|
21
21
|
yield chunk.delta
|
|
22
|
+
} else if (chunk.type === 'error') {
|
|
23
|
+
const message = typeof chunk.errorText === 'string'
|
|
24
|
+
? chunk.errorText
|
|
25
|
+
: (chunk.error?.message || chunk.error || fallbackErrorText)
|
|
26
|
+
throw new Error(message)
|
|
27
|
+
}
|
|
28
|
+
} catch (err) {
|
|
29
|
+
if (err instanceof SyntaxError) {
|
|
30
|
+
console.warn('[parseUIMessageStream] parse error:', data)
|
|
31
|
+
} else {
|
|
32
|
+
throw err
|
|
22
33
|
}
|
|
23
|
-
} catch {
|
|
24
|
-
console.warn('[parseUIMessageStream] parse error:', data)
|
|
25
34
|
}
|
|
26
35
|
}
|
|
27
36
|
}
|