Skip to content

Commit 67bcad1

Browse files
authored
Merge pull request #399 from netzbegruenung/feature/expo-mobile-app
Add Production-Ready Collaborative Docs Platform
2 parents b81e36d + 659dfea commit 67bcad1

2,589 files changed

Lines changed: 143016 additions & 21421 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ apps/web/node_modules/.cache
3636
*.pack
3737
apps/api/local_cache/
3838
apps/api/fastembed_cache/
39+
.docusaurus
3940

4041
# Playwright MCP files
4142
.playwright-mcp/
@@ -85,6 +86,7 @@ scripts/
8586
# Documentation (local development)
8687
docs/
8788
!apps/api/docs/
89+
!apps/docs/
8890

8991
# Backup files
9092
backup/
@@ -119,3 +121,11 @@ apps/desktop/src-tauri/gen/schemas/*.json
119121
target/
120122
Cargo.lock
121123
!apps/desktop/src-tauri/Cargo.lock
124+
125+
# Typecheck outputs
126+
apps/web/typecheck_output.txt
127+
apps/web/typecheck_output_full.txt
128+
129+
# Turbo
130+
.turbo/
131+
**/.turbo/

GEMINI.md

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# Grünerator - Project Context
2+
3+
## Project Overview
4+
5+
**Grünerator** is an AI-powered content creation platform built specifically for the German Green Party (Die Grünen). It enables users to generate political content (press releases, social media posts), create sharepics, and subtitle videos with domain-specific AI knowledge.
6+
7+
This is a **monorepo** managed with **pnpm workspaces**, containing a full-stack application with web, mobile, and desktop clients.
8+
9+
### Key Technologies
10+
11+
* **Frontend:** React 19, Vite 7, Zustand (state management).
12+
* **Styling:** **Standard CSS with CSS Variables**. No frameworks like Tailwind or Bootstrap are used.
13+
* **Backend:** Node.js, Express.js (Cluster mode), PostgreSQL, Redis, Keycloak (Auth), Qdrant (Vector DB).
14+
* **AI:** Mistral AI (primary), Anthropic Claude (via Bedrock), Flux (Images), AssemblyAI (Transcription).
15+
* **Mobile:** React Native (Expo).
16+
* **Desktop:** Tauri (Rust + Web Frontend).
17+
* **Package Manager:** pnpm.
18+
19+
## Architecture
20+
21+
The system follows a multi-tier architecture designed for data sovereignty (EU hosting) and scalability.
22+
23+
* **API (`apps/api`):** The core backend.
24+
* **Cluster Mode:** Uses Node.js `cluster` module for vertical scaling.
25+
* **AI Worker Pool:** Dedicated worker threads for non-blocking AI operations.
26+
* **Services:** Decoupled services for Auth, Database, Profiles, and specific AI tasks (Subtitler, Sharepic).
27+
* **Data Stores:** PostgreSQL (User/Content data), Redis (Sessions/Cache), Qdrant (Vector Embeddings).
28+
* **Web Client (`apps/web`):** The primary user interface.
29+
* **Feature-Sliced Design:** Modular architecture.
30+
* **Real-time:** Y.js for collaborative editing.
31+
* **Mobile App (`apps/mobile`):** Expo-based React Native app for iOS and Android.
32+
* **Desktop App (`apps/desktop`):** Tauri wrapper around the web frontend for offline/native capabilities.
33+
34+
## Directory Structure
35+
36+
* `apps/`
37+
* `api/`: Backend Express server.
38+
* `web/`: Main React frontend.
39+
* `mobile/`: React Native (Expo) mobile app.
40+
* `desktop/`: Tauri desktop app.
41+
* `sites/`: Static sites/landing pages.
42+
* `packages/`: Shared libraries (e.g., `@gruenerator/shared`).
43+
* `services/`: Microservices, specifically `mcp` (Model Context Protocol).
44+
* `docs/`: Documentation.
45+
46+
## Building and Running
47+
48+
The project uses `pnpm` for script management. Run these commands from the **root directory**.
49+
50+
### Installation
51+
```bash
52+
pnpm install
53+
```
54+
55+
### Development
56+
* **Web + Backend:**
57+
```bash
58+
pnpm dev:web # Starts frontend at localhost:5173
59+
pnpm dev:backend # Starts backend at localhost:3000 (requires DBs running)
60+
```
61+
* **Mobile:**
62+
```bash
63+
pnpm dev:mobile # Starts Expo bundler
64+
```
65+
* **Desktop:**
66+
```bash
67+
pnpm --filter @gruenerator/desktop dev # Starts Tauri dev environment
68+
```
69+
70+
### Production Build
71+
* **Web:** `pnpm build:web`
72+
* **Desktop:** `pnpm build:desktop`
73+
* **Shared:** `pnpm build:shared`
74+
75+
### Testing
76+
* **Root:** `npm test` (Runs tests across packages)
77+
* **Backend Auth:** `pnpm --filter @gruenerator/api test:auth`
78+
79+
## Development Conventions
80+
81+
* **Language:** TypeScript is used across the entire stack.
82+
* **Styling:**
83+
* **NO Tailwind.** Use standard CSS files.
84+
* **Variables:** Use CSS variables defined in `apps/web/src/assets/styles/common/variables.css` for colors (`--primary-600`, `--secondary-600`), spacing, and theming.
85+
* **Structure:**
86+
* Global: `apps/web/src/assets/styles/common/`
87+
* Components: `apps/web/src/assets/styles/components/`
88+
* Features: `apps/web/src/features/**/*.css`
89+
* **Dark Mode:** Handled via `[data-theme="dark"]` and `@media (prefers-color-scheme: dark)` in `variables.css`.
90+
* **Commits:** Follows Semantic Release/Conventional Commits (e.g., `feat:`, `fix:`, `chore:`).
91+
* **Branching:** Create feature branches (`feature/name`) and PR to `main`.
92+
* **State Management:** `zustand` is preferred for global state in frontend apps.
93+
* **API Communication:** The backend exposes a REST API. Frontend uses `axios` or `tanstack-query` (implied from React context) for data fetching.
94+
95+
## Environment Setup
96+
97+
Ensure the following services are running locally or accessible:
98+
1. **PostgreSQL** (Port 5432)
99+
2. **Redis** (Port 6379)
100+
3. **Keycloak** (Auth Provider)
101+
4. **Qdrant** (Vector DB)
102+
103+
Copy `.env.example` to `.env` in `apps/api` and `apps/web` and configure secrets (API keys, DB credentials).

README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,35 @@ npm run test:auth # Authentication tests
280280

281281
---
282282

283+
## Documentation
284+
285+
User-facing documentation is maintained in the `/documentation` directory using Docusaurus.
286+
287+
### Development
288+
```bash
289+
pnpm run dev:documentation # Start documentation dev server (localhost:3000)
290+
pnpm run build:documentation # Build documentation site
291+
```
292+
293+
### Documentation Structure
294+
```
295+
documentation/
296+
├── docs/ # Main documentation pages
297+
│ ├── Grundlagen/ # Basics and guides
298+
│ ├── Profil/ # Profile and cloud features
299+
│ ├── gruenerieren/ # Content generation features
300+
│ ├── llm-basics/ # AI/LLM fundamentals
301+
│ └── ueber-den-gruenerator/ # About Grünerator
302+
├── blog/ # News and updates
303+
├── src/ # Custom pages and components
304+
└── static/ # Images and assets
305+
```
306+
307+
### Deployment
308+
Documentation is deployed to: https://xgwok08o0ccgo4g4cgcoksc8.services.moritz-waechter.de
309+
310+
---
311+
283312
## Roadmap
284313

285314
- [x] Core text generation

api_typecheck_output.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
2+
> @gruenerator/api@1.0.0 typecheck /home/morit/gruenerator/apps/api
3+
> tsc --noEmit
4+
5+
../../packages/shared/src/canvas-editor/index.ts(1,15): error TS2835: Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './types.js'?
6+
../../packages/shared/src/canvas-editor/index.ts(2,15): error TS2835: Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './hooks/index.js'?
7+
../../packages/shared/src/search/collections/index.ts(14,8): error TS2835: Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './types.js'?
8+
../../packages/shared/src/search/collections/index.ts(25,8): error TS2835: Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './config.js'?
9+
../../packages/shared/src/search/filters/index.ts(17,8): error TS2835: Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './types.js'?
10+
../../packages/shared/src/search/filters/index.ts(30,8): error TS2835: Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './QdrantFilterBuilder.js'?
11+
../../packages/shared/src/utils/index.ts(10,8): error TS2835: Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './textNormalization.js'?
12+
../../packages/shared/src/utils/index.ts(18,8): error TS2835: Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './stringDistance.js'?
13+
/home/morit/gruenerator/apps/api:
14+
 ERR_PNPM_RECURSIVE_RUN_FIRST_FAIL  @gruenerator/api@1.0.0 typecheck: `tsc --noEmit`
15+
Exit status 2

api_typecheck_output_v2.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
2+
> @gruenerator/api@1.0.0 typecheck /home/morit/gruenerator/apps/api
3+
> tsc --noEmit
4+
5+
../../packages/shared/src/canvas-editor/hooks/index.ts(1,33): error TS2835: Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './useCanvasLayers.js'?
6+
../../packages/shared/src/canvas-editor/hooks/index.ts(2,68): error TS2835: Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './useCanvasLayers.js'?
7+
../../packages/shared/src/canvas-editor/hooks/index.ts(4,34): error TS2835: Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './useCanvasHistory.js'?
8+
../../packages/shared/src/canvas-editor/hooks/index.ts(5,70): error TS2835: Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './useCanvasHistory.js'?
9+
../../packages/shared/src/utils/stringDistance.ts(8,29): error TS2835: Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './textNormalization.js'?
10+
/home/morit/gruenerator/apps/api:
11+
 ERR_PNPM_RECURSIVE_RUN_FIRST_FAIL  @gruenerator/api@1.0.0 typecheck: `tsc --noEmit`
12+
Exit status 2

api_typecheck_output_v3.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
2+
> @gruenerator/api@1.0.0 typecheck /home/morit/gruenerator/apps/api
3+
> tsc --noEmit
4+
5+
../../packages/shared/src/canvas-editor/hooks/useCanvasHistory.ts(7,48): error TS2835: Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean '../types.js'?
6+
../../packages/shared/src/canvas-editor/hooks/useCanvasLayers.ts(7,63): error TS2835: Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean '../types.js'?
7+
/home/morit/gruenerator/apps/api:
8+
 ERR_PNPM_RECURSIVE_RUN_FIRST_FAIL  @gruenerator/api@1.0.0 typecheck: `tsc --noEmit`
9+
Exit status 2

api_typecheck_output_v4.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
> @gruenerator/api@1.0.0 typecheck /home/morit/gruenerator/apps/api
3+
> tsc --noEmit
4+

apps/api/agents/chat/IntentClassifier.ts

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -334,8 +334,22 @@ REGELN:
334334
- Bei "conversation" → NUR {"agent": "universal"}, NIEMALS mehrere Intents!
335335
- Bei "document_query" → NUR {"agent": "universal"}
336336
- Bei "content_creation" → passende Agents (social, sharepic, antrag, imagine, etc.)
337+
338+
SHAREPIC-SPEZIFISCHE REGELN (WICHTIG!):
339+
- "zitat", "quote", "spruch", "aussage" → IMMER agent: "zitat" (NICHT dreizeilen!)
340+
- "dreizeilen", "slogan", "drei zeilen", "3 zeilen" → agent: "dreizeilen"
341+
- "info", "fakten", "information" → agent: "info"
342+
- "sharepic" OHNE spezifischen Typ → agent: "sharepic_auto"
337343
- Mit Bild + zitat → zitat_with_image
338344
- Ohne Bild + zitat → zitat
345+
346+
BEISPIELE SHAREPIC:
347+
- "Erstelle ein Zitat zum Thema Klimaschutz" → agent: "zitat" (NICHT dreizeilen!)
348+
- "Mach einen Slogan über Windenergie" → agent: "dreizeilen"
349+
- "Sharepic mit 3 Zeilen" → agent: "dreizeilen"
350+
- "Quote von Annalena Baerbock" → agent: "zitat"
351+
352+
ANDERE REGELN:
339353
- "bild erstellen", "generiere bild", "visualisiere", "illustriere", "flux", "ki-bild" → imagine
340354
- Mit Bild + "transformiere"/"begrüne"/"bearbeite" → imagine (für Bildbearbeitung)
341355
- Im Zweifel: "conversation" mit "universal" (weniger ist mehr!)
@@ -362,7 +376,7 @@ Beispiele für requestType:
362376
- "Sharepic und Instagram Post" → content_creation, [sharepic_auto, instagram]
363377
364378
Antworte als JSON:
365-
{"requestType": "conversation|document_query|content_creation", "subIntent": "summarize|translate|compare|explain|brainstorm|general", "intents": [{"agent": "...", "confidence": 0.9}]}`;
379+
{"requestType": "conversation|document_query|content_creation", "subIntent": "summarize|translate|compare|explain|brainstorm|general", "intents": [{"agent": "...", "confidence": 0.9}]}${context.singleIntentOnly ? '\n\nWICHTIG: Gib NUR EINEN Intent zurück - den besten Match! Keine mehreren Intents.' : ''}`;
366380

367381
try {
368382
console.log('[IntentClassifier] Calling AI for multi-intent classification');
@@ -415,8 +429,8 @@ Antworte als JSON:
415429
parsedIntents = [parsedIntents];
416430
}
417431

418-
// Validate and enrich intents
419-
const validIntents = parsedIntents
432+
// Validate and enrich intents - pick only the highest confidence intent for sharepic requests
433+
let validIntents = parsedIntents
420434
.filter(intent => intent && intent.agent && AGENT_MAPPINGS[intent.agent])
421435
.map(intent => ({
422436
agent: intent.agent,
@@ -428,6 +442,19 @@ Antworte als JSON:
428442
confidence: intent.confidence || 0.8
429443
}));
430444

445+
// For sharepic intents, only keep the highest confidence one to avoid multi-intent confusion
446+
const sharepicAgents = ['zitat', 'zitat_with_image', 'dreizeilen', 'info', 'sharepic_auto', 'quote'];
447+
const sharepicIntents = validIntents.filter(i => sharepicAgents.includes(i.agent));
448+
if (sharepicIntents.length > 1) {
449+
// Sort by confidence and keep only the best one
450+
sharepicIntents.sort((a, b) => (b.confidence || 0) - (a.confidence || 0));
451+
const bestSharepic = sharepicIntents[0];
452+
console.log('[IntentClassifier] Multiple sharepic intents detected, keeping best:', bestSharepic.agent);
453+
// Replace all sharepic intents with just the best one
454+
validIntents = validIntents.filter(i => !sharepicAgents.includes(i.agent));
455+
validIntents.push(bestSharepic);
456+
}
457+
431458
if (validIntents.length === 0) {
432459
console.warn('[IntentClassifier] No valid intents found in AI response');
433460
return null;

apps/api/agents/chat/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ export interface ChatContext {
6868
lastAgent?: string;
6969
/** Current topic of conversation */
7070
topic?: string;
71+
/** When true, AI returns only the single best intent (used by Image Studio) */
72+
singleIntentOnly?: boolean;
7173
}
7274

7375
/**

apps/api/database/postgres/schema.sql

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,22 @@ CREATE TABLE IF NOT EXISTS saved_generators (
348348
CREATE INDEX IF NOT EXISTS idx_saved_generators_user_id ON saved_generators(user_id);
349349
CREATE INDEX IF NOT EXISTS idx_saved_generators_generator_id ON saved_generators(generator_id);
350350

351+
-- Template Likes (Users can like/favorite templates for ranking)
352+
CREATE TABLE IF NOT EXISTS template_likes (
353+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
354+
user_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
355+
template_id TEXT NOT NULL, -- Supports both UUID (user templates) and string IDs (system templates)
356+
template_type TEXT NOT NULL DEFAULT 'system', -- 'user' | 'system' | 'file'
357+
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
358+
359+
-- Ensure user can only like a template once
360+
UNIQUE(user_id, template_id)
361+
);
362+
363+
CREATE INDEX IF NOT EXISTS idx_template_likes_user_id ON template_likes(user_id);
364+
CREATE INDEX IF NOT EXISTS idx_template_likes_template_id ON template_likes(template_id);
365+
CREATE INDEX IF NOT EXISTS idx_template_likes_popularity ON template_likes(template_id, created_at);
366+
351367
-- User Templates (Canva templates and other user templates)
352368
CREATE TABLE IF NOT EXISTS user_templates (
353369
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
@@ -875,9 +891,26 @@ ALTER TABLE shared_media ADD COLUMN IF NOT EXISTS alt_text TEXT;
875891
ALTER TABLE shared_media ADD COLUMN IF NOT EXISTS upload_source TEXT DEFAULT 'upload';
876892
ALTER TABLE shared_media ADD COLUMN IF NOT EXISTS original_filename TEXT;
877893

894+
-- Template feature columns for canvas editor
895+
ALTER TABLE shared_media ADD COLUMN IF NOT EXISTS is_template BOOLEAN DEFAULT FALSE;
896+
ALTER TABLE shared_media ADD COLUMN IF NOT EXISTS template_visibility TEXT DEFAULT 'private'
897+
CHECK (template_visibility IN ('private', 'unlisted', 'public'));
898+
ALTER TABLE shared_media ADD COLUMN IF NOT EXISTS template_use_count INTEGER DEFAULT 0;
899+
ALTER TABLE shared_media ADD COLUMN IF NOT EXISTS template_creator_name TEXT;
900+
ALTER TABLE shared_media ADD COLUMN IF NOT EXISTS original_template_id UUID REFERENCES shared_media(id);
901+
878902
-- Media library indexes
879903
CREATE INDEX IF NOT EXISTS idx_shared_media_library ON shared_media(user_id, is_library_item, created_at DESC);
880904

905+
-- Template indexes for efficient discovery
906+
CREATE INDEX IF NOT EXISTS idx_shared_media_templates
907+
ON shared_media(is_template, template_visibility, created_at DESC)
908+
WHERE is_template = TRUE;
909+
910+
CREATE INDEX IF NOT EXISTS idx_shared_media_public_templates
911+
ON shared_media(is_template, template_visibility, image_type, created_at DESC)
912+
WHERE is_template = TRUE AND template_visibility = 'public';
913+
881914
-- Download tracking table for shared media
882915
CREATE TABLE IF NOT EXISTS shared_media_downloads (
883916
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),

0 commit comments

Comments
 (0)