diff --git a/package-lock.json b/package-lock.json index 6f4d05d..55b7ade 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,15 @@ { "name": "app-ts", - "version": "2.0.2", + "version": "2.0.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "app-ts", - "version": "2.0.2", + "version": "2.0.8", "license": "ISC", "dependencies": { + "@anthropic-ai/sdk": "^0.102.0", "@dnd-kit/core": "^6.3.1", "@dnd-kit/modifiers": "^9.0.0", "@dnd-kit/sortable": "^10.0.0", @@ -71,6 +72,27 @@ "vitest": "^4.1.0" } }, + "node_modules/@anthropic-ai/sdk": { + "version": "0.102.0", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.102.0.tgz", + "integrity": "sha512-cThh3KcPW3lzkFyTz1cjyhJvOVw45NkLMoowO2ZJ/76CBz44ADUon+NsjEc/PypAkARs72Xu8qxTnx6PAOTQUQ==", + "license": "MIT", + "dependencies": { + "json-schema-to-ts": "^3.1.1", + "standardwebhooks": "^1.0.0" + }, + "bin": { + "anthropic-ai-sdk": "bin/cli" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, "node_modules/@asamuzakjp/css-color": { "version": "5.1.11", "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz", @@ -2249,6 +2271,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@stablelib/base64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz", + "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==", + "license": "MIT" + }, "node_modules/@standard-schema/spec": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", @@ -4073,6 +4101,12 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-sha256": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz", + "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==", + "license": "Unlicense" + }, "node_modules/fast-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", @@ -4801,6 +4835,19 @@ "dequal": "^2.0.3" } }, + "node_modules/json-schema-to-ts": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", + "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "ts-algebra": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -6751,6 +6798,16 @@ "dev": true, "license": "MIT" }, + "node_modules/standardwebhooks": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/standardwebhooks/-/standardwebhooks-1.0.0.tgz", + "integrity": "sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==", + "license": "MIT", + "dependencies": { + "@stablelib/base64": "^1.0.0", + "fast-sha256": "^1.3.0" + } + }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", @@ -7139,6 +7196,12 @@ "tree-kill": "cli.js" } }, + "node_modules/ts-algebra": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", + "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", + "license": "MIT" + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", diff --git a/package.json b/package.json index bc684a6..eb2514a 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "license": "ISC", "type": "commonjs", "dependencies": { + "@anthropic-ai/sdk": "^0.102.0", "@dnd-kit/core": "^6.3.1", "@dnd-kit/modifiers": "^9.0.0", "@dnd-kit/sortable": "^10.0.0", diff --git a/prisma/migrations/20260608120000_add_ai_assistant/migration.sql b/prisma/migrations/20260608120000_add_ai_assistant/migration.sql new file mode 100644 index 0000000..96d9be6 --- /dev/null +++ b/prisma/migrations/20260608120000_add_ai_assistant/migration.sql @@ -0,0 +1,29 @@ +-- AI assistant (Phase 1): usage-tracking table, monthly budget column, ai.use permission. + +CREATE TABLE `ai_usage` ( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `user_id` INTEGER NULL, + `kind` VARCHAR(20) NOT NULL, + `model` VARCHAR(50) NOT NULL, + `input_tokens` INTEGER NOT NULL DEFAULT 0, + `output_tokens` INTEGER NOT NULL DEFAULT 0, + `cost_usd` DECIMAL(10, 6) NOT NULL DEFAULT 0, + `created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0), + PRIMARY KEY (`id`), + INDEX `idx_ai_usage_created` (`created_at`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +ALTER TABLE `company_settings` + ADD COLUMN `ai_monthly_budget_usd` DECIMAL(10, 2) NULL DEFAULT 50.00; + +-- ai.use permission + grant to admin (INSERT IGNORE → idempotent), mirroring +-- the warehouse-permissions migration. +INSERT IGNORE INTO `permissions` (`name`, `display_name`, `module`, `description`, `created_at`) VALUES + ('ai.use', 'AI asistent', 'ai', 'Používat AI asistenta (chat a import faktur)', NOW()); + +INSERT IGNORE INTO `role_permissions` (`role_id`, `permission_id`) +SELECT r.id, p.id +FROM `roles` r +CROSS JOIN `permissions` p +WHERE r.name = 'admin' + AND p.name = 'ai.use'; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 5377496..1240440 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -76,6 +76,19 @@ model audit_logs { @@fulltext([description], map: "idx_audit_search") } +model ai_usage { + id Int @id @default(autoincrement()) + user_id Int? + kind String @db.VarChar(20) + model String @db.VarChar(50) + input_tokens Int @default(0) + output_tokens Int @default(0) + cost_usd Decimal @default(0) @db.Decimal(10, 6) + created_at DateTime @default(now()) @db.Timestamp(0) + + @@index([created_at], map: "idx_ai_usage_created") +} + model bank_accounts { id Int @id @default(autoincrement()) account_name String? @db.VarChar(255) @@ -134,6 +147,7 @@ model company_settings { warehouse_issue_number_pattern String? @db.VarChar(100) warehouse_inventory_prefix String? @db.VarChar(20) warehouse_inventory_number_pattern String? @db.VarChar(100) + ai_monthly_budget_usd Decimal? @default(50.00) @db.Decimal(10, 2) } model customers { diff --git a/src/config/env.ts b/src/config/env.ts index 77e896c..5f28949 100644 --- a/src/config/env.ts +++ b/src/config/env.ts @@ -61,6 +61,10 @@ export const config = { ), }, + anthropic: { + apiKey: process.env.ANTHROPIC_API_KEY || "", + }, + nas: { path: process.env.NAS_PATH || "Z:/02_PROJEKTY", financialsPath: process.env.NAS_FINANCIALS_PATH || "",