refactor: clean up top toolbar and integrate direct Gemini API support
This commit is contained in:
@@ -26,18 +26,6 @@
|
|||||||
|
|
||||||
<template v-if="userRole !== 'viewer'">
|
<template v-if="userRole !== 'viewer'">
|
||||||
<button @click="addNodeAtCenter" class="px-2 py-1 bg-blue-600 hover:bg-blue-500 text-white rounded text-xs">+ Узел</button>
|
<button @click="addNodeAtCenter" class="px-2 py-1 bg-blue-600 hover:bg-blue-500 text-white rounded text-xs">+ Узел</button>
|
||||||
<button @click="createSmartNodeManual" class="px-2 py-1 bg-indigo-600 hover:bg-indigo-500 text-white rounded text-xs flex items-center gap-1 transition" title="Создать цель S.M.A.R.T">
|
|
||||||
<span>🎯 +SMART</span>
|
|
||||||
</button>
|
|
||||||
<button @click="createGrowChainManual" class="px-2 py-1 bg-teal-650 hover:bg-teal-600 text-white rounded text-xs flex items-center gap-1 transition" title="Создать цепочку G.R.O.W.">
|
|
||||||
<span>🌱 +GROW</span>
|
|
||||||
</button>
|
|
||||||
<button @click="createDiltsChainManual" class="px-2 py-1 bg-violet-600 hover:bg-violet-500 text-white rounded text-xs flex items-center gap-1 transition" title="Создать логические уровни Дилтса">
|
|
||||||
<span>📐 +Дилтс</span>
|
|
||||||
</button>
|
|
||||||
<button @click="createEisenhowerMatrixManual" class="px-2 py-1 bg-amber-600 hover:bg-amber-500 text-white rounded text-xs flex items-center gap-1 transition" title="Создать матрицу Эйзенхауэра">
|
|
||||||
<span>📊 +Эйзенхауэр</span>
|
|
||||||
</button>
|
|
||||||
<button @click="createGoalsTemplateManual" class="px-2 py-1 bg-emerald-600 hover:bg-emerald-500 text-white rounded text-xs flex items-center gap-1 transition" title="Создать карту целей на год (Практическая работа)">
|
<button @click="createGoalsTemplateManual" class="px-2 py-1 bg-emerald-600 hover:bg-emerald-500 text-white rounded text-xs flex items-center gap-1 transition" title="Создать карту целей на год (Практическая работа)">
|
||||||
<span>📋 +Цели на Год</span>
|
<span>📋 +Цели на Год</span>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
+68
-22
@@ -1,10 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* XFlow AI Utility — OpenRouter Integration (Stable Models)
|
* XFlow AI Utility — OpenRouter & Google Gemini Integration
|
||||||
*
|
|
||||||
* Поддерживаемые модели (наиболее стабильные и без жестких rate-limit):
|
|
||||||
*
|
|
||||||
* • poolside/laguna-m.1:free — быстрые инженерные задачи, Claude-based (default)
|
|
||||||
* • nvidia/nemotron-3-super-120b-a12b:free — MoE 120B, тяжёлая аналитика, сложные рассуждения
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const OPENROUTER_BASE_URL = 'https://openrouter.ai/api/v1/chat/completions'
|
const OPENROUTER_BASE_URL = 'https://openrouter.ai/api/v1/chat/completions'
|
||||||
@@ -15,41 +10,90 @@ export const AI_MODELS = {
|
|||||||
laguna: 'poolside/laguna-m.1:free',
|
laguna: 'poolside/laguna-m.1:free',
|
||||||
/** MoE 120B тяжёлая аналитика, архитектурный анализ, сложные рассуждения */
|
/** MoE 120B тяжёлая аналитика, архитектурный анализ, сложные рассуждения */
|
||||||
nemotron: 'nvidia/nemotron-3-super-120b-a12b:free',
|
nemotron: 'nvidia/nemotron-3-super-120b-a12b:free',
|
||||||
|
/** Прямой вызов Gemini Flash */
|
||||||
|
geminiFlash: 'gemini-1.5-flash',
|
||||||
|
/** Прямой вызов Gemini Pro */
|
||||||
|
geminiPro: 'gemini-1.5-pro'
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export type AiModelKey = keyof typeof AI_MODELS
|
export type AiModelKey = keyof typeof AI_MODELS
|
||||||
|
|
||||||
/** Дефолтная модель, если не указана */
|
/** Дефолтная модель, если не указана */
|
||||||
const DEFAULT_MODEL: AiModelKey = 'laguna'
|
const DEFAULT_MODEL: AiModelKey = 'geminiFlash'
|
||||||
|
|
||||||
export const generateAIResponse = async (
|
export const generateAIResponse = async (
|
||||||
prompt: string,
|
prompt: string,
|
||||||
systemPrompt = 'You are a helpful assistant.',
|
systemPrompt = 'You are a helpful assistant.',
|
||||||
modelKey?: AiModelKey | string,
|
modelKey?: AiModelKey | string,
|
||||||
): Promise<string> => {
|
): Promise<string> => {
|
||||||
const apiKey =
|
// Получаем ключи из окружения
|
||||||
process.env.NUXT_OPEN_ROUTER_API_KEY ||
|
const openRouterKey = process.env.NUXT_OPEN_ROUTER_API_KEY || process.env.OPEN_ROUTER_API_KEY
|
||||||
process.env.OPEN_ROUTER_API_KEY
|
const geminiKey = process.env.GEMINI_API_KEY || process.env.NUXT_GEMINI_API_KEY || process.env.GOOGLE_API_KEY
|
||||||
|
|
||||||
if (!apiKey) {
|
|
||||||
throw new Error('OPEN_ROUTER_API_KEY is not configured')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Резолвим модель: принимаем как ключ ('laguna') так и полное имя
|
// Резолвим модель: принимаем как ключ ('laguna') так и полное имя
|
||||||
const resolvedModel: string =
|
const resolvedModel: string =
|
||||||
modelKey && modelKey in AI_MODELS
|
modelKey && modelKey in AI_MODELS
|
||||||
? AI_MODELS[modelKey as AiModelKey]
|
? AI_MODELS[modelKey as AiModelKey]
|
||||||
: modelKey && modelKey.includes('/')
|
: modelKey && (modelKey.includes('/') || modelKey.startsWith('gemini'))
|
||||||
? modelKey
|
? modelKey
|
||||||
: AI_MODELS[DEFAULT_MODEL]
|
: AI_MODELS[DEFAULT_MODEL]
|
||||||
|
|
||||||
console.log(`[AI] Using model: ${resolvedModel}`)
|
// Определяем, использовать ли Gemini напрямую
|
||||||
|
const useGeminiDirect = resolvedModel.startsWith('gemini') || (!openRouterKey && geminiKey)
|
||||||
|
|
||||||
|
if (useGeminiDirect) {
|
||||||
|
if (!geminiKey) {
|
||||||
|
throw new Error('Gemini API key (GEMINI_API_KEY or GOOGLE_API_KEY) is not configured')
|
||||||
|
}
|
||||||
|
const geminiModel = resolvedModel.startsWith('gemini') ? resolvedModel : 'gemini-1.5-flash'
|
||||||
|
console.log(`[AI] Direct Google Gemini call: ${geminiModel}`)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const url = `https://generativelanguage.googleapis.com/v1beta/models/${geminiModel}:generateContent?key=${geminiKey}`
|
||||||
|
const response = await $fetch<any>(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
contents: [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
parts: [{ text: prompt }]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
systemInstruction: {
|
||||||
|
parts: [{ text: systemPrompt }]
|
||||||
|
},
|
||||||
|
generationConfig: {
|
||||||
|
temperature: 0.3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const content = response?.candidates?.[0]?.content?.parts?.[0]?.text
|
||||||
|
if (!content) {
|
||||||
|
throw new Error('Empty response from Gemini API')
|
||||||
|
}
|
||||||
|
return content
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error(`[AI] Gemini direct error:`, error?.data || error?.message)
|
||||||
|
throw new Error(`Gemini direct generation failed: ${error?.message || error?.toString()}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Иначе используем OpenRouter
|
||||||
|
if (!openRouterKey) {
|
||||||
|
throw new Error('Neither OPEN_ROUTER_API_KEY nor GEMINI_API_KEY is configured')
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[AI] OpenRouter call: ${resolvedModel}`)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await $fetch<any>(OPENROUTER_BASE_URL, {
|
const response = await $fetch<any>(OPENROUTER_BASE_URL, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${apiKey}`,
|
'Authorization': `Bearer ${openRouterKey}`,
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'HTTP-Referer': 'https://a-crm.controlsystem.kz',
|
'HTTP-Referer': 'https://a-crm.controlsystem.kz',
|
||||||
'X-Title': 'XFlow AI Node',
|
'X-Title': 'XFlow AI Node',
|
||||||
@@ -66,12 +110,12 @@ export const generateAIResponse = async (
|
|||||||
|
|
||||||
const content = response?.choices?.[0]?.message?.content
|
const content = response?.choices?.[0]?.message?.content
|
||||||
if (!content) {
|
if (!content) {
|
||||||
throw new Error('Empty response from AI model')
|
throw new Error('Empty response from OpenRouter model')
|
||||||
}
|
}
|
||||||
return content
|
return content
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(`[AI] Error with model ${resolvedModel}:`, error?.data || error?.message)
|
console.error(`[AI] OpenRouter error with model ${resolvedModel}:`, error?.data || error?.message)
|
||||||
throw new Error(`AI generation failed (${resolvedModel}): ${error?.message}`)
|
throw new Error(`OpenRouter generation failed (${resolvedModel}): ${error?.message}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,6 +128,8 @@ export const getAvailableModels = () =>
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
const modelLabels: Record<AiModelKey, string> = {
|
const modelLabels: Record<AiModelKey, string> = {
|
||||||
laguna: '🏊 Laguna M.1 — стабильная инженерная (Claude)',
|
laguna: '🏊 Laguna M.1 — стабильная инженерная (Claude via OpenRouter)',
|
||||||
nemotron: '🔬 Nemotron Super 120B — тяжёлая аналитика (NVIDIA)',
|
nemotron: '🔬 Nemotron Super 120B — тяжёлая аналитика (NVIDIA via OpenRouter)',
|
||||||
|
geminiFlash: '⚡ Gemini 1.5 Flash — быстрая и стабильная (Google Direct)',
|
||||||
|
geminiPro: '🧠 Gemini 1.5 Pro — продвинутая аналитика (Google Direct)'
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user