Skip to content

Commit 6553b52

Browse files
committed
merge: resolve conflict for #128 Groq LLM, add groq to GlobalSettingsOverride
2 parents 270b721 + 21eec16 commit 6553b52

9 files changed

Lines changed: 296 additions & 1 deletion

File tree

apps/server-ts/src/engine/executor.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ const GLOBAL_SETTINGS_MAP: Record<string, Record<string, string>> = {
212212
"anthropic-llm": { apiKey: "anthropic.apiKey", model: "anthropic.model" },
213213
"google-llm": { apiKey: "google.apiKey", model: "google.model" },
214214
"ollama-llm": { host: "ollama.host", model: "ollama.model" },
215+
"groq-llm": { apiKey: "groq.apiKey", model: "groq.model" },
215216
"voicevox-tts": { host: "voicevox.host" },
216217
"coeiroink-tts": { host: "coeiroink.host" },
217218
"sbv2-tts": { host: "sbv2.host" },

apps/web/components/panels/GlobalSettingsOverride.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export const GLOBAL_SETTINGS_MAP: Record<string, Record<string, string>> = {
1010
"anthropic-llm": { apiKey: "anthropic.apiKey", model: "anthropic.model" },
1111
"google-llm": { apiKey: "google.apiKey", model: "google.model" },
1212
"ollama-llm": { host: "ollama.host", model: "ollama.model" },
13+
"groq-llm": { apiKey: "groq.apiKey", model: "groq.model" },
1314
"voicevox-tts": { host: "voicevox.host" },
1415
"coeiroink-tts": { host: "coeiroink.host" },
1516
"sbv2-tts": { host: "sbv2.host" },

apps/web/components/panels/NodeSettings.tsx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,40 @@ const nodeConfigs: Record<string, { label: string; fields: NodeField[] }> = {
268268
},
269269
],
270270
},
271+
"groq-llm": {
272+
label: "LLM (Groq)",
273+
fields: [
274+
{
275+
key: "apiKey",
276+
type: "password",
277+
label: "API Key",
278+
placeholder: "gsk_...",
279+
},
280+
{
281+
key: "model",
282+
type: "select",
283+
label: "Model",
284+
options: LLM_MODEL_OPTIONS.groq,
285+
},
286+
{
287+
key: "systemPrompt",
288+
type: "textarea",
289+
label: "System Prompt",
290+
placeholder: "Enter character settings...",
291+
},
292+
{
293+
key: "promptSections",
294+
type: "prompt-builder",
295+
label: "Prompt Builder",
296+
},
297+
{
298+
key: "temperature",
299+
type: "number",
300+
label: "Temperature",
301+
placeholder: "0.7",
302+
},
303+
],
304+
},
271305
"voicevox-tts": {
272306
label: "TTS (VOICEVOX)",
273307
fields: [

apps/web/components/ui/SettingsModal.tsx

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ function TextInput({
115115
const OPENAI_MODELS = LLM_MODEL_OPTIONS.openai;
116116
const ANTHROPIC_MODELS = LLM_MODEL_OPTIONS.anthropic;
117117
const GOOGLE_MODELS = LLM_MODEL_OPTIONS.google;
118+
const GROQ_MODELS = LLM_MODEL_OPTIONS.groq;
118119

119120
export default function SettingsModal({ onClose }: SettingsModalProps) {
120121
const { t } = useTranslation();
@@ -282,7 +283,7 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
282283
</div>
283284

284285
{/* Ollama */}
285-
<div className="p-3 rounded-lg" style={{ background: 'rgba(0,0,0,0.2)' }}>
286+
<div className="mb-4 p-3 rounded-lg" style={{ background: 'rgba(0,0,0,0.2)' }}>
286287
<h4 className="text-xs font-medium text-white/60 mb-2">Ollama</h4>
287288
<div className="space-y-2">
288289
<div>
@@ -303,6 +304,29 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
303304
</div>
304305
</div>
305306
</div>
307+
308+
{/* Groq */}
309+
<div className="p-3 rounded-lg" style={{ background: 'rgba(0,0,0,0.2)' }}>
310+
<h4 className="text-xs font-medium text-white/60 mb-2">Groq</h4>
311+
<div className="space-y-2">
312+
<div>
313+
<label className="block text-[11px] text-white/50 mb-1">{t('globalSettings.apiKey')}</label>
314+
<PasswordInput
315+
value={local['groq.apiKey'] || ''}
316+
onChange={(v) => set('groq.apiKey', v)}
317+
placeholder="gsk_..."
318+
/>
319+
</div>
320+
<div>
321+
<label className="block text-[11px] text-white/50 mb-1">{t('globalSettings.model')}</label>
322+
<ModelSelect
323+
value={local['groq.model'] || ''}
324+
onChange={(v) => set('groq.model', v)}
325+
options={GROQ_MODELS}
326+
/>
327+
</div>
328+
</div>
329+
</div>
306330
</section>
307331

308332
{/* TTS Engines */}

apps/web/lib/constants.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,11 @@ export const LLM_MODEL_OPTIONS: Record<string, { label: string; value: string }[
3737
{ label: 'Gemini 2.5 Pro', value: 'gemini-2.5-pro-preview-05-06' },
3838
{ label: 'Gemini 2.5 Flash', value: 'gemini-2.5-flash-preview-05-20' },
3939
],
40+
groq: [
41+
{ label: 'Llama 3.3 70B Versatile', value: 'llama-3.3-70b-versatile' },
42+
{ label: 'Llama 3.1 8B Instant', value: 'llama-3.1-8b-instant' },
43+
{ label: 'Llama Guard 3 8B', value: 'llama-guard-3-8b' },
44+
{ label: 'Mixtral 8x7B', value: 'mixtral-8x7b-32768' },
45+
{ label: 'Gemma 2 9B', value: 'gemma2-9b-it' },
46+
],
4047
};

apps/web/locales/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@
100100
"anthropic-llm": "LLM node using Anthropic API.\n[Usage] Connect prompt input. Set model and system prompt in config.",
101101
"google-llm": "LLM node using Google AI API.\n[Usage] Connect prompt input. Set model and system prompt in config.",
102102
"ollama-llm": "LLM node using local Ollama.\n[Usage] Connect prompt input. Set model in config. *Requires Ollama running.",
103+
"groq-llm": "LLM node using Groq's high-speed inference API.\n[Usage] Connect prompt input. Set model and system prompt in config.",
103104
"coeiroink-tts": "TTS node using COEIROINK.\n[Usage] Connect text to read. Select speaker in config. *Requires COEIROINK running.",
104105
"sbv2-tts": "TTS node using Style-Bert-VITS2.\n[Usage] Connect text to read. Select model in config. *Requires API running.",
105106
"avatar-configuration": "VRM avatar configuration node.\n[Usage] Set model URL and idle animation. Avatar displays on overlay.",

apps/web/locales/ja.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@
100100
"anthropic-llm": "Anthropic APIでテキスト生成するLLMノード。\n【使い方】promptに質問を接続。設定でモデルとシステムプロンプトを指定。",
101101
"google-llm": "Google AI APIでテキスト生成するLLMノード。\n【使い方】promptに質問を接続。設定でモデルとシステムプロンプトを指定。",
102102
"ollama-llm": "ローカルOllamaでテキスト生成するLLMノード。\n【使い方】promptに質問を接続。設定でモデルを指定。※Ollama起動が必要",
103+
"groq-llm": "Groqの高速推論APIでテキスト生成するLLMノード。\n【使い方】promptに質問を接続。設定でモデルとシステムプロンプトを指定。",
103104
"coeiroink-tts": "COEIROINKでテキストを音声に変換するTTSノード。\n【使い方】textに読み上げテキストを接続。設定で話者を選択。※COEIROINK起動が必要",
104105
"sbv2-tts": "Style-Bert-VITS2でテキストを音声に変換するTTSノード。\n【使い方】textに読み上げテキストを接続。設定でモデルを選択。※API起動が必要",
105106
"avatar-configuration": "VRMアバターの設定ノード。\n【使い方】モデルURL、アイドルアニメーションを設定。オーバーレイでアバターが表示。",

plugins/groq-llm/manifest.json

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
{
2+
"$schema": "https://aituber-flow.dev/schemas/plugin-manifest.json",
3+
"id": "groq-llm",
4+
"name": "Groq LLM",
5+
"version": "1.0.0",
6+
"description": "Generate text using Groq's high-speed inference API. Supports Llama, Mixtral, and Gemma models.",
7+
"author": {
8+
"name": "AITuberFlow",
9+
"url": "https://github.com/oboroge0/AITuberFlow"
10+
},
11+
"license": "MIT",
12+
"category": "llm",
13+
"ui": {
14+
"label": "Groq",
15+
"icon": "Zap",
16+
"color": "#F55036",
17+
"bgColor": "rgba(245, 80, 54, 0.1)",
18+
"statusText": "High-speed inference"
19+
},
20+
"node": {
21+
"inputs": [
22+
{
23+
"id": "prompt",
24+
"type": "string",
25+
"description": "The input prompt to send to the model"
26+
}
27+
],
28+
"outputs": [
29+
{
30+
"id": "response",
31+
"type": "string",
32+
"description": "The generated response from the model"
33+
}
34+
],
35+
"events": {
36+
"emits": [
37+
"response.generated"
38+
],
39+
"listens": []
40+
}
41+
},
42+
"config": {
43+
"apiKey": {
44+
"type": "password",
45+
"label": "API Key",
46+
"description": "Your Groq API key",
47+
"required": true
48+
},
49+
"model": {
50+
"type": "select",
51+
"label": "Model",
52+
"description": "The model to use for inference",
53+
"default": "llama-3.3-70b-versatile",
54+
"options": [
55+
{ "label": "Llama 3.3 70B Versatile", "value": "llama-3.3-70b-versatile" },
56+
{ "label": "Llama 3.1 8B Instant", "value": "llama-3.1-8b-instant" },
57+
{ "label": "Llama Guard 3 8B", "value": "llama-guard-3-8b" },
58+
{ "label": "Mixtral 8x7B", "value": "mixtral-8x7b-32768" },
59+
{ "label": "Gemma 2 9B", "value": "gemma2-9b-it" }
60+
]
61+
},
62+
"systemPrompt": {
63+
"type": "textarea",
64+
"label": "System Prompt",
65+
"description": "The system message that sets the AI's behavior",
66+
"default": "You are a helpful assistant."
67+
},
68+
"promptSections": {
69+
"type": "prompt-builder",
70+
"label": "Prompt Builder",
71+
"description": "Build complex prompts by combining static text, system prompt, and dynamic input sections"
72+
},
73+
"temperature": {
74+
"type": "number",
75+
"label": "Temperature",
76+
"description": "Controls randomness. Higher = more creative, Lower = more focused (0-2)",
77+
"default": 0.7,
78+
"min": 0,
79+
"max": 2
80+
},
81+
"maxTokens": {
82+
"type": "number",
83+
"label": "Max Tokens",
84+
"description": "Maximum number of tokens in the response",
85+
"default": 1024,
86+
"min": 1,
87+
"max": 8192
88+
}
89+
}
90+
}

plugins/groq-llm/node.ts

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/**
2+
* Groq LLM Node
3+
*
4+
* Generates text responses using Groq's high-speed inference API.
5+
* Uses OpenAI-compatible API format with custom baseURL.
6+
*/
7+
8+
import { BaseNode, NodeContext, createEvent, handleLLMError } from "@aituber-flow/sdk";
9+
import type { Event } from "@aituber-flow/sdk";
10+
import OpenAI from "openai";
11+
12+
interface PromptSection {
13+
type: "text" | "input";
14+
content: string;
15+
}
16+
17+
export default class GroqLLMNode extends BaseNode {
18+
private static readonly DEMO_RESPONSE =
19+
"これはデモモードの応答です。実際のLLMを使用するにはAPIキーを設定してください。";
20+
21+
private client: OpenAI | null = null;
22+
private model: string = "llama-3.3-70b-versatile";
23+
private systemPrompt: string = "You are a helpful assistant.";
24+
private temperature: number = 0.7;
25+
private maxTokens: number = 1024;
26+
private promptSections: PromptSection[] | null = null;
27+
28+
async setup(config: Record<string, any>, context: NodeContext): Promise<void> {
29+
const apiKey = config.apiKey ?? "";
30+
this.model = config.model ?? "llama-3.3-70b-versatile";
31+
this.systemPrompt = config.systemPrompt ?? "You are a helpful assistant.";
32+
this.temperature = config.temperature ?? 0.7;
33+
this.maxTokens = config.maxTokens ?? 1024;
34+
this.promptSections = config.promptSections ?? null;
35+
36+
if (!apiKey) {
37+
await context.log(
38+
"[デモモード] Groq APIキー未設定 - 定型文応答を返します",
39+
"warning",
40+
);
41+
} else {
42+
this.client = new OpenAI({
43+
apiKey,
44+
baseURL: "https://api.groq.com/openai/v1",
45+
});
46+
await context.log(`Groq client initialized (model: ${this.model})`);
47+
}
48+
}
49+
50+
private buildPromptFromSections(inputs: Record<string, any>): string {
51+
if (!this.promptSections) {
52+
return (inputs.prompt as string) ?? "";
53+
}
54+
55+
const parts: string[] = [];
56+
for (const section of this.promptSections) {
57+
if (section.type === "text") {
58+
parts.push(section.content);
59+
} else if (section.type === "input") {
60+
let inputValue: any = inputs[section.content] ?? "";
61+
if (typeof inputValue === "object" && inputValue !== null && !Array.isArray(inputValue)) {
62+
if ("message" in inputValue) {
63+
inputValue = inputValue.message;
64+
} else if ("text" in inputValue) {
65+
inputValue = inputValue.text;
66+
} else {
67+
inputValue = String(inputValue);
68+
}
69+
}
70+
parts.push(inputValue ? String(inputValue) : "");
71+
}
72+
}
73+
74+
return parts.join("\n");
75+
}
76+
77+
async execute(
78+
inputs: Record<string, any>,
79+
context: NodeContext,
80+
): Promise<Record<string, any>> {
81+
if (!this.client) {
82+
await context.log("[デモモード] 定型文応答を返します", "info");
83+
return { response: GroqLLMNode.DEMO_RESPONSE };
84+
}
85+
86+
const prompt = this.promptSections
87+
? this.buildPromptFromSections(inputs)
88+
: ((inputs.prompt as string) ?? "");
89+
90+
if (!prompt) {
91+
await context.log("No prompt provided", "warning");
92+
return { response: "" };
93+
}
94+
95+
try {
96+
await context.log(`Calling Groq API (${this.model})...`);
97+
98+
const characterName = context.getCharacterName();
99+
const characterPersonality = context.getCharacterPersonality();
100+
101+
let fullSystemPrompt = this.systemPrompt;
102+
if (characterPersonality) {
103+
fullSystemPrompt = `${this.systemPrompt}\n\nYou are ${characterName}. ${characterPersonality}`;
104+
}
105+
106+
const response = await this.client.chat.completions.create({
107+
model: this.model,
108+
messages: [
109+
{ role: "system" as const, content: fullSystemPrompt },
110+
{ role: "user" as const, content: prompt },
111+
],
112+
temperature: this.temperature,
113+
max_tokens: this.maxTokens,
114+
});
115+
116+
const result = response.choices[0].message.content ?? "";
117+
await context.log(`Response received (${result.length} chars)`);
118+
119+
await context.emitEvent(
120+
createEvent("response.generated", {
121+
text: result,
122+
model: this.model,
123+
}),
124+
);
125+
126+
return { response: result };
127+
} catch (error: unknown) {
128+
const result = await handleLLMError(error, "Groq", context);
129+
return { response: result.response };
130+
}
131+
}
132+
133+
async teardown(): Promise<void> {
134+
this.client = null;
135+
}
136+
}

0 commit comments

Comments
 (0)