feat(project-doc-bot): migrate to shared-llm, remove OpenAI dependency

Migrate matrix-project-doc-bot from raw fetch to @manacore/shared-llm
and remove the unused openai npm package. The bot was already using
mana-llm and mana-stt (not OpenAI directly), but the code still had
raw fetch calls and the openai package installed.

Changes:
- generation.service.ts: raw fetch → llm.chat() via LlmClientService
- app.module.ts: add LlmModule.forRootAsync()
- Remove openai dependency (was unused in code)
- Update CLAUDE.md: document actual AI stack (mana-llm + mana-stt)
- Update TECH_STACK_INDEPENDENCE.md: mark Prio 1-3 as completed
  - Prio 1: Picture App → mana-image-gen 
  - Prio 2: Project Doc Bot → Ollama + mana-stt 
  - Prio 3: All LLM calls via mana-llm 
  - Self-hosted percentage: 75% → ~80%

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-03-24 10:44:56 +01:00
parent 357fbb3d50
commit 62c5dddab0
6 changed files with 136 additions and 91 deletions

View file

@ -94,18 +94,13 @@ Brevo ist SPOF für alle Transaktions-Emails (Verifizierung, Passwort-Reset).
#### 2.1 Alle LLM-Requests über mana-llm routen
**Aufwand:** 1 Woche | **Impact:** Hoch
**Status: ✅ ERLEDIGT** (2026-03-24)
Aktuell nutzen einige Apps direkt Cloud-SDKs (OpenAI, Gemini, Azure). Wenn alles über `mana-llm` läuft:
- Ein Switch reicht um Provider zu wechseln
- Zentrales Logging, Rate-Limiting, Cost-Tracking
- Einfacherer Wechsel zu lokalen Modellen
**Betroffene Apps:**
- Context App → `apps/context/apps/backend/src/ai/ai.service.ts`
- NutriPhi → Google Gemini SDK direkt
- Planta → Google Gemini SDK direkt
- Matrix Project Doc Bot → OpenAI SDK direkt
Alle 9 Backends nutzen jetzt `@manacore/shared-llm``mana-llm` Gateway:
- Auth, Chat, Context, NutriPhi, Planta, Traces, ManaDeck, Bot Services, Matrix Bots
- Google Gemini als automatischer Fallback wenn Ollama überlastet
- OpenAI SDK komplett entfernt (Project Doc Bot)
- Google Gemini SDK entfernt (ManaDeck)
#### 2.2 PostgreSQL Backup stärken
@ -201,9 +196,9 @@ NutriPhi und Planta nutzen Google Gemini Vision. Alternativen via Ollama:
| Prio | Maßnahme | Aufwand | Gewinn |
|------|----------|---------|--------|
| **1** | Picture App → mana-image-gen | 1-2 Tage | Replicate-Kosten = 0, volle Kontrolle |
| **2** | Project Doc Bot → Ollama + mana-stt | 1 Tag | OpenAI-Abhängigkeit weg |
| **3** | Alle LLM-Calls über mana-llm routen | 1 Woche | Zentrale Provider-Kontrolle |
| ~~**1**~~ | ~~Picture App → mana-image-gen~~ | ✅ Erledigt | Lokales FLUX.2 klein als Default, Replicate für Premium |
| ~~**2**~~ | ~~Project Doc Bot → Ollama + mana-stt~~ | ✅ Erledigt | OpenAI SDK entfernt, nutzt mana-llm + mana-stt |
| ~~**3**~~ | ~~Alle LLM-Calls über mana-llm routen~~ | ✅ Erledigt | @manacore/shared-llm + Google Fallback |
| **4** | PostgreSQL Backup mit pgBackRest | 1-2 Tage | Disaster Recovery |
| **5** | Brevo → Postal/Stalwart | 2-3 Tage | Email-Unabhängigkeit |
| **6** | Landing Pages self-hosted | 0.5 Tage | Cloudflare Pages weg |
@ -216,8 +211,15 @@ NutriPhi und Planta nutzen Google Gemini Vision. Alternativen via Ollama:
## Zusammenfassung
**Aktuell self-hosted:** ~75% der Infrastruktur
**Nach Roadmap (Prio 1-6):** ~90%
**Aktuell self-hosted:** ~80% der Infrastruktur (Stand: 2026-03-24)
**Nach Roadmap (Prio 4-6):** ~90%
**Unvermeidbare Cloud-Abhängigkeiten:** Stripe (Payment Gateway), Google OAuth (Contacts API)
Der Stack ist bereits sehr gut aufgestellt. Die größten Quick Wins sind die Integration von `mana-image-gen` in die Picture App und das Routing aller LLM-Calls über `mana-llm`. Die kritischste Verbesserung ist die Backup-Strategie und Server-Redundanz.
**Erledigte Meilensteine (2026-03-24):**
- ✅ Alle LLM-Calls über `mana-llm` geroutet (9 Backends, `@manacore/shared-llm`)
- ✅ Google Gemini Fallback in mana-llm (automatisch bei Ollama-Überlastung)
- ✅ Picture App nutzt lokales `mana-image-gen` (FLUX.2 klein) als Default
- ✅ Project Doc Bot: OpenAI SDK komplett entfernt
- ✅ ManaDeck: Google Gemini SDK entfernt
**Nächste Schritte:** PostgreSQL Backup (Prio 4), E-Mail-Unabhängigkeit (Prio 5), Landing Pages self-hosted (Prio 6).

132
pnpm-lock.yaml generated
View file

@ -8564,9 +8564,6 @@ importers:
matrix-bot-sdk:
specifier: ^0.7.1
version: 0.7.1
openai:
specifier: ^4.77.0
version: 4.104.0(encoding@0.1.13)(ws@8.18.3)(zod@3.25.76)
postgres:
specifier: ^3.4.5
version: 3.4.7
@ -8579,7 +8576,7 @@ importers:
devDependencies:
'@nestjs/cli':
specifier: ^10.4.9
version: 10.4.9(esbuild@0.27.4)
version: 10.4.9(esbuild@0.19.12)
'@nestjs/schematics':
specifier: ^10.2.3
version: 10.2.3(chokidar@3.6.0)(typescript@5.9.3)
@ -31227,7 +31224,6 @@ snapshots:
- supports-color
- typescript
- utf-8-validate
optional: true
'@expo/code-signing-certificates@0.0.5':
dependencies:
@ -31473,7 +31469,6 @@ snapshots:
optionalDependencies:
react: 19.2.4
react-native: 0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4)
optional: true
'@expo/dom-webview@55.0.3(expo@52.0.47)(react-native@0.76.7(@babel/core@7.28.5)(@babel/preset-env@7.28.5(@babel/core@7.28.5))(@types/react@18.3.27)(encoding@0.1.13)(react@18.3.1))(react@18.3.1)':
dependencies:
@ -31542,7 +31537,6 @@ snapshots:
expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@6.1.2)(expo-router@55.0.5)(react-dom@19.2.4(react@19.2.4))(react-native-webview@13.12.2(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)(typescript@5.9.3)
react: 19.2.4
react-native: 0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4)
optional: true
'@expo/env@0.4.2':
dependencies:
@ -31724,7 +31718,6 @@ snapshots:
react: 19.2.4
react-native: 0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4)
stacktrace-parser: 0.1.11
optional: true
'@expo/mcp-tunnel@0.0.8':
dependencies:
@ -31939,7 +31932,7 @@ snapshots:
postcss: 8.4.49
resolve-from: 5.0.0
optionalDependencies:
expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@6.1.2)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3)
expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@6.1.2)(expo-router@55.0.5)(react-dom@19.2.4(react@19.2.4))(react-native-webview@13.12.2(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)(typescript@5.9.3)
transitivePeerDependencies:
- bufferutil
- supports-color
@ -32246,7 +32239,7 @@ snapshots:
'@expo/json-file': 10.0.12
'@react-native/normalize-colors': 0.83.2
debug: 4.4.3
expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@6.1.2)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3)
expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@6.1.2)(expo-router@55.0.5)(react-dom@19.2.4(react@19.2.4))(react-native-webview@13.12.2(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)(typescript@5.9.3)
resolve-from: 5.0.0
semver: 7.7.3
xml2js: 0.6.0
@ -32320,7 +32313,6 @@ snapshots:
react-dom: 19.2.4(react@19.2.4)
transitivePeerDependencies:
- supports-color
optional: true
'@expo/rudder-sdk-node@1.1.1(encoding@0.1.13)':
dependencies:
@ -32443,7 +32435,6 @@ snapshots:
expo-font: 55.0.4(expo@55.0.5)(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)
react: 19.2.4
react-native: 0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4)
optional: true
'@expo/ws-tunnel@1.0.6': {}
@ -33780,6 +33771,32 @@ snapshots:
- uglify-js
- webpack-cli
'@nestjs/cli@10.4.9(esbuild@0.19.12)':
dependencies:
'@angular-devkit/core': 17.3.11(chokidar@3.6.0)
'@angular-devkit/schematics': 17.3.11(chokidar@3.6.0)
'@angular-devkit/schematics-cli': 17.3.11(chokidar@3.6.0)
'@nestjs/schematics': 10.2.3(chokidar@3.6.0)(typescript@5.7.2)
chalk: 4.1.2
chokidar: 3.6.0
cli-table3: 0.6.5
commander: 4.1.1
fork-ts-checker-webpack-plugin: 9.0.2(typescript@5.7.2)(webpack@5.97.1(esbuild@0.19.12))
glob: 10.4.5
inquirer: 8.2.6
node-emoji: 1.11.0
ora: 5.4.1
tree-kill: 1.2.2
tsconfig-paths: 4.2.0
tsconfig-paths-webpack-plugin: 4.2.0
typescript: 5.7.2
webpack: 5.97.1(esbuild@0.19.12)
webpack-node-externals: 3.0.0
transitivePeerDependencies:
- esbuild
- uglify-js
- webpack-cli
'@nestjs/cli@10.4.9(esbuild@0.27.4)':
dependencies:
'@angular-devkit/core': 17.3.11(chokidar@3.6.0)
@ -36066,8 +36083,7 @@ snapshots:
'@react-native/assets-registry@0.83.2': {}
'@react-native/assets-registry@0.84.1':
optional: true
'@react-native/assets-registry@0.84.1': {}
'@react-native/babel-plugin-codegen@0.76.7(@babel/preset-env@7.28.5(@babel/core@7.28.5))':
dependencies:
@ -36368,7 +36384,6 @@ snapshots:
nullthrows: 1.1.1
tinyglobby: 0.2.15
yargs: 17.7.2
optional: true
'@react-native/community-cli-plugin@0.76.7(@babel/core@7.28.5)(@babel/preset-env@7.28.5(@babel/core@7.28.5))(encoding@0.1.13)':
dependencies:
@ -36467,7 +36482,6 @@ snapshots:
- bufferutil
- supports-color
- utf-8-validate
optional: true
'@react-native/debugger-frontend@0.76.7': {}
@ -36479,8 +36493,7 @@ snapshots:
'@react-native/debugger-frontend@0.83.2': {}
'@react-native/debugger-frontend@0.84.1':
optional: true
'@react-native/debugger-frontend@0.84.1': {}
'@react-native/debugger-shell@0.83.2':
dependencies:
@ -36494,7 +36507,6 @@ snapshots:
fb-dotslash: 0.5.8
transitivePeerDependencies:
- supports-color
optional: true
'@react-native/dev-middleware@0.76.7':
dependencies:
@ -36607,7 +36619,6 @@ snapshots:
- bufferutil
- supports-color
- utf-8-validate
optional: true
'@react-native/gradle-plugin@0.76.7': {}
@ -36619,8 +36630,7 @@ snapshots:
'@react-native/gradle-plugin@0.83.2': {}
'@react-native/gradle-plugin@0.84.1':
optional: true
'@react-native/gradle-plugin@0.84.1': {}
'@react-native/js-polyfills@0.76.7': {}
@ -36632,8 +36642,7 @@ snapshots:
'@react-native/js-polyfills@0.83.2': {}
'@react-native/js-polyfills@0.84.1':
optional: true
'@react-native/js-polyfills@0.84.1': {}
'@react-native/metro-babel-transformer@0.76.7(@babel/core@7.28.5)(@babel/preset-env@7.28.5(@babel/core@7.28.5))':
dependencies:
@ -36669,8 +36678,7 @@ snapshots:
'@react-native/normalize-colors@0.83.2': {}
'@react-native/normalize-colors@0.84.1':
optional: true
'@react-native/normalize-colors@0.84.1': {}
'@react-native/virtualized-lists@0.76.7(@types/react@18.3.27)(react-native@0.76.7(@babel/core@7.28.5)(@babel/preset-env@7.28.5(@babel/core@7.28.5))(@types/react@18.3.27)(encoding@0.1.13)(react@18.3.1))(react@18.3.1)':
dependencies:
@ -36779,7 +36787,6 @@ snapshots:
react-native: 0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4)
optionalDependencies:
'@types/react': 19.2.14
optional: true
'@react-navigation/bottom-tabs@7.15.5(@react-navigation/native@7.1.33(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)':
dependencies:
@ -42292,7 +42299,7 @@ snapshots:
resolve-from: 5.0.0
optionalDependencies:
'@babel/runtime': 7.28.4
expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@6.1.2)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3)
expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@6.1.2)(expo-router@55.0.5)(react-dom@19.2.4(react@19.2.4))(react-native-webview@13.12.2(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)(typescript@5.9.3)
transitivePeerDependencies:
- '@babel/core'
- supports-color
@ -45397,7 +45404,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
- typescript
optional: true
expo-audio@55.0.8(expo-asset@55.0.8(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3))(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0):
dependencies:
@ -45567,7 +45573,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
- typescript
optional: true
expo-dev-client@5.0.20(expo@52.0.47):
dependencies:
@ -45755,7 +45760,6 @@ snapshots:
dependencies:
expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@6.1.2)(expo-router@55.0.5)(react-dom@19.2.4(react@19.2.4))(react-native-webview@13.12.2(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)(typescript@5.9.3)
react-native: 0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4)
optional: true
expo-font@13.0.4(expo@52.0.47)(react@18.3.1):
dependencies:
@ -45861,7 +45865,6 @@ snapshots:
fontfaceobserver: 2.3.0
react: 19.2.4
react-native: 0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4)
optional: true
expo-glass-effect@55.0.8(expo@54.0.25)(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.1.0))(react@19.1.0):
dependencies:
@ -46001,7 +46004,6 @@ snapshots:
dependencies:
expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@6.1.2)(expo-router@55.0.5)(react-dom@19.2.4(react@19.2.4))(react-native-webview@13.12.2(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4))(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)(typescript@5.9.3)
react: 19.2.4
optional: true
expo-linear-gradient@15.0.7(expo@54.0.12)(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0):
dependencies:
@ -46284,7 +46286,6 @@ snapshots:
invariant: 2.2.4
react: 19.2.4
react-native: 0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4)
optional: true
expo-notifications@55.0.12(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3):
dependencies:
@ -47410,7 +47411,6 @@ snapshots:
- supports-color
- typescript
- utf-8-validate
optional: true
exponential-backoff@3.1.3: {}
@ -47812,6 +47812,23 @@ snapshots:
forever-agent@0.6.1: {}
fork-ts-checker-webpack-plugin@9.0.2(typescript@5.7.2)(webpack@5.97.1(esbuild@0.19.12)):
dependencies:
'@babel/code-frame': 7.27.1
chalk: 4.1.2
chokidar: 3.6.0
cosmiconfig: 8.3.6(typescript@5.7.2)
deepmerge: 4.3.1
fs-extra: 10.1.0
memfs: 3.5.3
minimatch: 3.1.2
node-abort-controller: 3.1.1
schema-utils: 3.3.0
semver: 7.7.3
tapable: 2.3.0
typescript: 5.7.2
webpack: 5.97.1(esbuild@0.19.12)
fork-ts-checker-webpack-plugin@9.0.2(typescript@5.7.2)(webpack@5.97.1(esbuild@0.27.4)):
dependencies:
'@babel/code-frame': 7.27.1
@ -48573,8 +48590,7 @@ snapshots:
hermes-compiler@0.14.1: {}
hermes-compiler@250829098.0.9:
optional: true
hermes-compiler@250829098.0.9: {}
hermes-estree@0.23.1: {}
@ -55540,7 +55556,6 @@ snapshots:
- bufferutil
- supports-color
- utf-8-validate
optional: true
react-refresh@0.14.2: {}
@ -57249,6 +57264,17 @@ snapshots:
ansi-escapes: 4.3.2
supports-hyperlinks: 2.3.0
terser-webpack-plugin@5.3.14(esbuild@0.19.12)(webpack@5.97.1(esbuild@0.19.12)):
dependencies:
'@jridgewell/trace-mapping': 0.3.31
jest-worker: 27.5.1
schema-utils: 4.3.3
serialize-javascript: 6.0.2
terser: 5.44.1
webpack: 5.97.1(esbuild@0.19.12)
optionalDependencies:
esbuild: 0.19.12
terser-webpack-plugin@5.3.14(esbuild@0.27.4)(webpack@5.100.2(esbuild@0.27.4)):
dependencies:
'@jridgewell/trace-mapping': 0.3.31
@ -59317,6 +59343,36 @@ snapshots:
- esbuild
- uglify-js
webpack@5.97.1(esbuild@0.19.12):
dependencies:
'@types/eslint-scope': 3.7.7
'@types/estree': 1.0.8
'@webassemblyjs/ast': 1.14.1
'@webassemblyjs/wasm-edit': 1.14.1
'@webassemblyjs/wasm-parser': 1.14.1
acorn: 8.15.0
browserslist: 4.28.0
chrome-trace-event: 1.0.4
enhanced-resolve: 5.18.3
es-module-lexer: 1.7.0
eslint-scope: 5.1.1
events: 3.3.0
glob-to-regexp: 0.4.1
graceful-fs: 4.2.11
json-parse-even-better-errors: 2.3.1
loader-runner: 4.3.1
mime-types: 2.1.35
neo-async: 2.6.2
schema-utils: 3.3.0
tapable: 2.3.0
terser-webpack-plugin: 5.3.14(esbuild@0.19.12)(webpack@5.97.1(esbuild@0.19.12))
watchpack: 2.4.4
webpack-sources: 3.3.3
transitivePeerDependencies:
- '@swc/core'
- esbuild
- uglify-js
webpack@5.97.1(esbuild@0.27.4):
dependencies:
'@types/eslint-scope': 3.7.7

View file

@ -10,7 +10,7 @@ Matrix Project Doc Bot collects photos, voice notes, and text for projects and g
- **Matrix**: matrix-bot-sdk
- **Database**: Drizzle ORM + PostgreSQL
- **Storage**: S3 (MinIO)
- **AI**: OpenAI (Whisper for transcription, GPT-4o-mini for generation)
- **AI**: mana-llm (Ollama/Gemma 3 for generation), mana-stt (Whisper for transcription) — fully self-hosted
## Commands

View file

@ -27,14 +27,15 @@
"db:studio": "drizzle-kit studio"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.721.0",
"@aws-sdk/s3-request-presigner": "^3.721.0",
"@manacore/bot-services": "workspace:*",
"@manacore/matrix-bot-common": "workspace:*",
"@manacore/shared-llm": "workspace:^",
"@nestjs/common": "^10.4.15",
"@nestjs/config": "^3.3.0",
"@nestjs/core": "^10.4.15",
"@nestjs/platform-express": "^10.4.15",
"@aws-sdk/client-s3": "^3.721.0",
"@aws-sdk/s3-request-presigner": "^3.721.0",
"drizzle-orm": "^0.38.3",
"matrix-bot-sdk": "^0.7.1",
"openai": "^4.77.0",

View file

@ -1,5 +1,6 @@
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { LlmModule } from '@manacore/shared-llm';
import { HealthController, createHealthProvider } from '@manacore/matrix-bot-common';
import { DatabaseModule } from './database/database.module';
import { BotModule } from './bot/bot.module';
@ -11,6 +12,14 @@ import configuration from './config/configuration';
isGlobal: true,
load: [configuration],
}),
LlmModule.forRootAsync({
imports: [ConfigModule],
useFactory: (config: ConfigService) => ({
manaLlmUrl: config.get('MANA_LLM_URL') || 'http://localhost:3025',
debug: config.get('NODE_ENV') === 'development',
}),
inject: [ConfigService],
}),
DatabaseModule,
BotModule,
],

View file

@ -1,6 +1,6 @@
import { Injectable, Inject, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { eq, desc } from 'drizzle-orm';
import { LlmClientService } from '@manacore/shared-llm';
import { DATABASE_CONNECTION } from '../database/database.module';
import { generations, projectItems, projects } from '../database/schema';
import { BLOG_STYLES } from '../config/configuration';
@ -12,16 +12,11 @@ type Database = PostgresJsDatabase<typeof schema>;
@Injectable()
export class GenerationService {
private readonly logger = new Logger(GenerationService.name);
private readonly manaLlmUrl: string;
private readonly model: string;
constructor(
@Inject(DATABASE_CONNECTION) private db: Database,
private configService: ConfigService
) {
this.manaLlmUrl = this.configService.get<string>('MANA_LLM_URL') || 'http://localhost:3025';
this.model = this.configService.get<string>('openai.model') || 'ollama/gemma3:4b';
}
private readonly llm: LlmClientService
) {}
async generateBlogpost(projectId: string, style: keyof typeof BLOG_STYLES): Promise<string> {
// Get project info
@ -70,39 +65,21 @@ Erstellt am: ${project.createdAt.toLocaleDateString('de-DE')}
Die folgenden Inhalte wurden während des Projekts gesammelt:`;
const response = await fetch(`${this.manaLlmUrl}/v1/chat/completions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model: this.model,
messages: [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: contentSummary },
],
temperature: 0.7,
max_tokens: 2000,
}),
signal: AbortSignal.timeout(120000),
const result = await this.llm.chat(contentSummary, {
systemPrompt,
temperature: 0.7,
maxTokens: 2000,
});
if (!response.ok) {
const errorText = await response.text();
this.logger.error(`mana-llm error: ${response.status} - ${errorText}`);
throw new Error(`LLM generation failed: ${response.status}`);
}
const data = await response.json();
const content = data.choices?.[0]?.message?.content || '';
// Save generation
await this.db.insert(generations).values({
projectId,
style,
content,
content: result.content,
});
this.logger.log(`Generated ${style} blogpost for project ${projectId}`);
return content;
return result.content;
}
async getLatestGeneration(projectId: string) {