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
continuous-integration/drone/push Build is passing
This commit is contained in:
@@ -1 +1,7 @@
|
|||||||
DATABASE_URL=postgresql://taskflow:taskflow_password@localhost:5432/taskflow
|
DATABASE_URL=postgresql://taskflow:taskflow_password@localhost:5432/taskflow
|
||||||
|
|
||||||
|
POSTGRES_USER=taskflow
|
||||||
|
POSTGRES_PASSWORD=taskflow_password
|
||||||
|
POSTGRES_DB=taskflow
|
||||||
|
SESSION_SECRET=local_development_secret
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,6 +37,10 @@ export default function AdminSettings() {
|
|||||||
<Settings className="h-4 w-4" />
|
<Settings className="h-4 w-4" />
|
||||||
General & AI
|
General & AI
|
||||||
</TabsTrigger>
|
</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">
|
<TabsTrigger value="audit" className="flex items-center gap-2">
|
||||||
<FileText className="h-4 w-4" />
|
<FileText className="h-4 w-4" />
|
||||||
Audit Logs
|
Audit Logs
|
||||||
@@ -45,10 +49,13 @@ export default function AdminSettings() {
|
|||||||
|
|
||||||
<TabsContent value="settings" className="space-y-6">
|
<TabsContent value="settings" className="space-y-6">
|
||||||
<AiSettingsCard />
|
<AiSettingsCard />
|
||||||
<McpSettingsCard />
|
|
||||||
<SMTPSettingsCard />
|
<SMTPSettingsCard />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="mcp">
|
||||||
|
<McpSettingsCard />
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="audit">
|
<TabsContent value="audit">
|
||||||
<AuditLogsTable />
|
<AuditLogsTable />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|||||||
@@ -330,64 +330,7 @@ export default function Settings({ onNavigateToTemplates }: SettingsProps) {
|
|||||||
|
|
||||||
<ShareAccessModal open={isShareAccessOpen} onOpenChange={setIsShareAccessOpen} />
|
<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>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -30,8 +30,7 @@ services:
|
|||||||
PORT: 5000
|
PORT: 5000
|
||||||
SESSION_SECRET: ${SESSION_SECRET}
|
SESSION_SECRET: ${SESSION_SECRET}
|
||||||
depends_on:
|
depends_on:
|
||||||
postgres:
|
- postgres
|
||||||
condition: service_healthy
|
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: [ "CMD", "node", "-e", "require('http').get('http://localhost:5000/api/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})" ]
|
test: [ "CMD", "node", "-e", "require('http').get('http://localhost:5000/api/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})" ]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "rest-express",
|
"name": "rest-express",
|
||||||
"version": "1.0.3",
|
"version": "1.0.4",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "rest-express",
|
"name": "rest-express",
|
||||||
"version": "1.0.3",
|
"version": "1.0.4",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dnd-kit/core": "^6.3.1",
|
"@dnd-kit/core": "^6.3.1",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "rest-express",
|
"name": "rest-express",
|
||||||
"version": "1.0.3",
|
"version": "1.0.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -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();
|
||||||
@@ -218,6 +218,8 @@
|
|||||||
- [x] Create `pre-commit` hook for patch increments
|
- [x] Create `pre-commit` hook for patch increments
|
||||||
- [x] Update `vite.config.ts` to expose version
|
- [x] Update `vite.config.ts` to expose version
|
||||||
- [x] Display version in Admin Settings UI
|
- [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] Implement Shared Labels
|
||||||
- [x] Schema: `sharedLabels` table
|
- [x] Schema: `sharedLabels` table
|
||||||
- [x] Storage: Logic for sharing and retrieving labels (MemStorage verified)
|
- [x] Storage: Logic for sharing and retrieving labels (MemStorage verified)
|
||||||
|
|||||||
@@ -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
@@ -1,13 +1,17 @@
|
|||||||
import { defineConfig } from "vite";
|
import { defineConfig } from "vite";
|
||||||
import react from "@vitejs/plugin-react";
|
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 runtimeErrorOverlay from "@replit/vite-plugin-runtime-error-modal";
|
||||||
import { fileURLToPath } from "url";
|
import { fileURLToPath } from "url";
|
||||||
import { VitePWA } from 'vite-plugin-pwa';
|
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({
|
export default defineConfig({
|
||||||
define: {
|
define: {
|
||||||
__APP_VERSION__: JSON.stringify(process.env.npm_package_version),
|
__APP_VERSION__: JSON.stringify(packageJson.version),
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
react(),
|
react(),
|
||||||
@@ -24,15 +28,15 @@ export default defineConfig({
|
|||||||
],
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
"@": path.resolve(import.meta.dirname, "client", "src"),
|
"@": path.resolve(process.cwd(), "client", "src"),
|
||||||
"@shared": path.resolve(import.meta.dirname, "shared"),
|
"@shared": path.resolve(process.cwd(), "shared"),
|
||||||
"@assets": path.resolve(import.meta.dirname, "attached_assets"),
|
"@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 || "/",
|
base: process.env.VITE_BASE_PATH || "/",
|
||||||
build: {
|
build: {
|
||||||
outDir: path.resolve(import.meta.dirname, "dist/public"),
|
outDir: path.resolve(process.cwd(), "dist/public"),
|
||||||
emptyOutDir: true,
|
emptyOutDir: true,
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
|
|||||||
Reference in New Issue
Block a user