feat: Add AI subtask reproduction script, refactor Admin MCP settings UI, update Vite config, and add i18n gamification rules.
continuous-integration/drone/push Build is passing

This commit is contained in:
2025-12-15 16:20:47 +01:00
parent bc40969ad7
commit e8d9d0c686
13 changed files with 233 additions and 70 deletions
+6
View File
@@ -1 +1,7 @@
DATABASE_URL=postgresql://taskflow:taskflow_password@localhost:5432/taskflow
POSTGRES_USER=taskflow
POSTGRES_PASSWORD=taskflow_password
POSTGRES_DB=taskflow
SESSION_SECRET=local_development_secret
+52
View File
@@ -0,0 +1,52 @@
64: } catch (e) {
95: } catch (e) {
127: } catch (e) {
138: } catch (e) {
164: } catch (e) {
190: } catch (e) {
216: } catch (e) {
238: } catch (e) {
298: } catch (e: any) {
326: } catch (e: any) {
340: } catch (e) {
363: } catch (e) {
392: } catch (e) {
419: } catch (e) {
432: } catch (e) {
457: } catch (e) {
472: } catch (e) {
489: } catch (e: any) {
556: } catch (e: any) {
633: } catch (e: any) {
665: } catch (error) {
677: } catch (error) {
704: } catch (error) {
732: } catch (error) {
755: } catch (error) {
789: } catch (e) {
813: } catch (e) {
833: } catch (e) {
844: } catch (error) {
858: } catch (error) {
892: } catch (e: any) {
926: } catch (error) {
968: } catch (error: any) {
991: } catch (error) {
1002: } catch (error) {
1015: } catch (error) {
1025: } catch (error) {
1052: } catch (error) {
1137: } catch (error) {
1175: } catch (e) {
1195: } catch (e) {
1214: } catch (e) {
1232: } catch (e) {
1248: } catch (e) {
1266: } catch (e) {
1287: } catch (e) {
1300: } catch (e) {
1325: } catch (e) {
1345: } catch (e) {
1370: } catch (e) {
1384: } catch (e) {
1400: } catch (e) {
@@ -0,0 +1,21 @@
{
"gamification": {
"rules": {
"title": "Gamification Rules",
"xpSystem": "XP System",
"levelRequirements": "Level Requirements",
"actions": "Actions & Rewards",
"level": "Level {{level}}",
"xp": "{{xp}} XP",
"action": "Action",
"points": "Points",
"createTask": "Create Task",
"createSubtask": "Create Subtask",
"updateTask": "Update Task",
"completeTask": "Complete Task (On Time)",
"completeTaskLate": "Complete Task (Late)",
"aiAction": "Use AI Feature",
"dailyStreak": "Daily Streak Bonus"
}
}
}
+8 -1
View File
@@ -37,6 +37,10 @@ export default function AdminSettings() {
<Settings className="h-4 w-4" />
General & AI
</TabsTrigger>
<TabsTrigger value="mcp" className="flex items-center gap-2">
<Bot className="h-4 w-4" />
MCP Integration
</TabsTrigger>
<TabsTrigger value="audit" className="flex items-center gap-2">
<FileText className="h-4 w-4" />
Audit Logs
@@ -45,10 +49,13 @@ export default function AdminSettings() {
<TabsContent value="settings" className="space-y-6">
<AiSettingsCard />
<McpSettingsCard />
<SMTPSettingsCard />
</TabsContent>
<TabsContent value="mcp">
<McpSettingsCard />
</TabsContent>
<TabsContent value="audit">
<AuditLogsTable />
</TabsContent>
-57
View File
@@ -330,64 +330,7 @@ export default function Settings({ onNavigateToTemplates }: SettingsProps) {
<ShareAccessModal open={isShareAccessOpen} onOpenChange={setIsShareAccessOpen} />
{/* MCP Integration */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Server className="w-5 h-5" />
{t('settings.mcp.title')}
</CardTitle>
<CardDescription>{t('settings.mcp.description')}</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<p className="font-medium">{t('settings.mcp.status')}</p>
</div>
<div className="flex items-center gap-2 text-green-500">
<div className="w-2 h-2 rounded-full bg-green-500 animate-pulse" />
<span className="font-medium">{t('settings.mcp.running')}</span>
</div>
</div>
<div className="space-y-2">
<p className="text-sm font-medium">{t('settings.mcp.url')}</p>
<div className="flex gap-2">
<Input readOnly value={`${window.location.protocol}//${window.location.host}/api/mcp/sse`} />
<Button variant="outline" size="icon" onClick={() => copyToClipboard(`${window.location.protocol}//${window.location.host}/api/mcp/sse`)}>
<Copy className="w-4 h-4" />
</Button>
</div>
</div>
<div className="space-y-2">
<p className="text-sm font-medium">{t('settings.mcp.apiKey')}</p>
{user?.apiKey ? (
<div className="flex gap-2">
<Input type="password" readOnly value={user.apiKey} />
{/* Show full key on click/copy only, usually concealed */}
<Button variant="outline" size="icon" onClick={() => copyToClipboard(user.apiKey!)}>
<Copy className="w-4 h-4" />
</Button>
<Button variant="destructive" onClick={() => revokeApiKeyMutation.mutate()} disabled={revokeApiKeyMutation.isPending}>
{t('settings.mcp.revoke')}
</Button>
</div>
) : (
<div className="space-y-2">
<p className="text-sm text-muted-foreground">{t('settings.mcp.noKey')}</p>
<Button onClick={() => generateApiKeyMutation.mutate()} disabled={generateApiKeyMutation.isPending}>
{t('settings.mcp.generate')}
</Button>
</div>
)}
</div>
<div className="bg-muted/50 p-3 rounded-lg text-sm text-muted-foreground">
{t('settings.mcp.instructions')}
</div>
</CardContent>
</Card>
+1 -2
View File
@@ -30,8 +30,7 @@ services:
PORT: 5000
SESSION_SECRET: ${SESSION_SECRET}
depends_on:
postgres:
condition: service_healthy
- postgres
healthcheck:
test: [ "CMD", "node", "-e", "require('http').get('http://localhost:5000/api/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})" ]
interval: 30s
View File
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "rest-express",
"version": "1.0.3",
"version": "1.0.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "rest-express",
"version": "1.0.3",
"version": "1.0.4",
"license": "MIT",
"dependencies": {
"@dnd-kit/core": "^6.3.1",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "rest-express",
"version": "1.0.3",
"version": "1.0.4",
"type": "module",
"license": "MIT",
"scripts": {
+76
View File
@@ -0,0 +1,76 @@
import { AiService } from "./ai";
import { MemStorage } from "./storage";
import { User } from "../shared/schema";
async function run() {
console.log("Starting Subtask Repro...");
const storage = new MemStorage();
// Create dummy user
const user: User = await storage.createUser({
username: "testuser",
password: "hashedpassword",
role: "user",
aiEnabled: true,
xp: 0,
level: 1
});
// Mock settings - Use env variables if available for real testing, or mock if just testing logic flow (but we need real AI for logic flow testing)
if (process.env.OPENAI_API_KEY) {
await storage.updateSystemSetting("ai_provider", "openai");
await storage.updateSystemSetting("ai_api_key", process.env.OPENAI_API_KEY);
await storage.updateSystemSetting("ai_model", "gpt-4o");
} else {
console.warn("No OPENAI_API_KEY provided. This test requires a valid key to actually hit the AI.");
process.exit(1);
}
const aiService = new AiService(storage);
// Scenario 1: Create new parent and subtask in one go
console.log("\n--- Scenario 1: Create New Parent + Subtask ---");
const prompt1 = "Create a task 'Main Project A'. Then create a subtask 'Subtask A1' for it. Also edit 'Subtask A1' to be 'In Progress'.";
try {
const response1 = await aiService.chat(
[{ role: "user", content: prompt1 }],
user,
"Current Date: 2025-12-12. No existing tasks."
);
console.log("Response 1:", response1);
} catch (e) {
console.error("Error in Scenario 1:", e);
}
const tasks1 = await storage.getTasks(user.id);
console.log("Tasks after Scenario 1:", tasks1);
// Scenario 2: Add subtask to existing task
console.log("\n--- Scenario 2: Add Subtask to Existing ---");
// Create an existing task manually first
const existingTask = await storage.createTask({
title: "Existing Project B",
priority: "high",
status: "todo",
userId: user.id
});
console.log("Created existing task:", existingTask.id, existingTask.title);
const prompt2 = "Add a subtask 'Subtask B1' to 'Existing Project B'.";
try {
const response2 = await aiService.chat(
[{ role: "user", content: prompt2 }],
user,
`Current Date: 2025-12-12.`
);
console.log("Response 2:", response2);
} catch (e) {
console.error("Error in Scenario 2:", e);
}
const tasks2 = await storage.getTasks(user.id);
console.log("Final Task List:", tasks2.map(t => ({ id: t.id, title: t.title, parent: t.parentTaskId, status: t.status })));
}
run();
+2
View File
@@ -218,6 +218,8 @@
- [x] Create `pre-commit` hook for patch increments
- [x] Update `vite.config.ts` to expose version
- [x] Display version in Admin Settings UI
- [x] Refactor MCP Settings UI
- [x] Move MCP settings to dedicated tab in Admin Settings
- [x] Implement Shared Labels
- [x] Schema: `sharedLabels` table
- [x] Storage: Logic for sharing and retrieving labels (MemStorage verified)
+53
View File
@@ -0,0 +1,53 @@
47: try {
71: try {
102: try {
135: try {
144: try {
170: try {
196: try {
235: try {
293: try {
311: try {
337: try {
349: try {
372: try {
403: try {
427: try {
440: try {
465: try {
483: try {
501: try {
568: try {
646: try {
671: try {
683: try {
711: try {
738: try {
763: try {
796: try {
820: try {
841: try {
852: try {
865: try {
900: try {
933: try {
975: try {
998: try {
1008: try {
1022: try {
1031: try {
1123: try {
1144: try {
1182: try {
1192: try {
1202: try {
1222: try {
1239: try {
1255: try {
1273: try {
1295: try {
1307: try {
1332: try {
1352: try {
1377: try {
1397: try {
+11 -7
View File
@@ -1,13 +1,17 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import path, { dirname } from "path";
import path from "path";
import { readFileSync } from "fs";
import runtimeErrorOverlay from "@replit/vite-plugin-runtime-error-modal";
import { fileURLToPath } from "url";
import { VitePWA } from 'vite-plugin-pwa';
// Read package.json to get the version
const packageJson = JSON.parse(readFileSync(path.resolve(process.cwd(), "package.json"), "utf-8"));
export default defineConfig({
define: {
__APP_VERSION__: JSON.stringify(process.env.npm_package_version),
__APP_VERSION__: JSON.stringify(packageJson.version),
},
plugins: [
react(),
@@ -24,15 +28,15 @@ export default defineConfig({
],
resolve: {
alias: {
"@": path.resolve(import.meta.dirname, "client", "src"),
"@shared": path.resolve(import.meta.dirname, "shared"),
"@assets": path.resolve(import.meta.dirname, "attached_assets"),
"@": path.resolve(process.cwd(), "client", "src"),
"@shared": path.resolve(process.cwd(), "shared"),
"@assets": path.resolve(process.cwd(), "attached_assets"),
},
},
root: path.resolve(import.meta.dirname, "client"),
root: path.resolve(process.cwd(), "client"),
base: process.env.VITE_BASE_PATH || "/",
build: {
outDir: path.resolve(import.meta.dirname, "dist/public"),
outDir: path.resolve(process.cwd(), "dist/public"),
emptyOutDir: true,
},
server: {