@@ -1,2 +1,3 @@
|
|||||||
|
DATABASE_URL=""
|
||||||
USER_KEY=""
|
USER_KEY=""
|
||||||
ANTHROPIC_API_KEY=""
|
ANTHROPIC_API_KEY=""
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
|
DATABASE_URL=""
|
||||||
USER_KEY="test-key-123"
|
USER_KEY="test-key-123"
|
||||||
ANTHROPIC_API_KEY=""
|
ANTHROPIC_API_KEY=""
|
||||||
10
package.json
10
package.json
@@ -5,9 +5,10 @@
|
|||||||
"author": "riccardo@frompixels.com",
|
"author": "riccardo@frompixels.com",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
"build": "next build",
|
"build": "prisma generate && next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
|
"lint:fix": "next lint --fix && prettier --config .prettierrc '**/*.{js,jsx,ts,tsx,json,css,scss,md}' --write",
|
||||||
"format": "prettier --config .prettierrc '**/*.{js,jsx,ts,tsx,json,css,scss,md}' --write",
|
"format": "prettier --config .prettierrc '**/*.{js,jsx,ts,tsx,json,css,scss,md}' --write",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
@@ -17,10 +18,14 @@
|
|||||||
"analyze": "BUNDLE_ANALYZE=true next build",
|
"analyze": "BUNDLE_ANALYZE=true next build",
|
||||||
"audit": "audit-ci",
|
"audit": "audit-ci",
|
||||||
"vercel:link": "vercel link",
|
"vercel:link": "vercel link",
|
||||||
"vercel:env": "vercel env pull .env"
|
"vercel:env": "vercel env pull .env",
|
||||||
|
"db:generate": "prisma generate",
|
||||||
|
"db:push": "prisma db push",
|
||||||
|
"db:migrate": "prisma migrate dev"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@anthropic-ai/sdk": "^0.33.1",
|
"@anthropic-ai/sdk": "^0.33.1",
|
||||||
|
"@prisma/client": "^5.22.0",
|
||||||
"axios": "^1.8.2",
|
"axios": "^1.8.2",
|
||||||
"next": "^15.2.4",
|
"next": "^15.2.4",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
@@ -45,6 +50,7 @@
|
|||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"lint-staged": "^15.3.0",
|
"lint-staged": "^15.3.0",
|
||||||
"prettier": "^3.4.2",
|
"prettier": "^3.4.2",
|
||||||
|
"prisma": "^5.22.0",
|
||||||
"ts-jest": "^29.2.5",
|
"ts-jest": "^29.2.5",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"typescript": "^5.7.2"
|
"typescript": "^5.7.2"
|
||||||
|
|||||||
23
prisma/schema.prisma
Normal file
23
prisma/schema.prisma
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
generator client {
|
||||||
|
provider = "prisma-client-js"
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource db {
|
||||||
|
provider = "postgresql"
|
||||||
|
url = env("DATABASE_URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
model AnthropicQuery {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
question String @db.Text
|
||||||
|
response String @db.Text
|
||||||
|
success Boolean
|
||||||
|
errorMessage String? @db.Text
|
||||||
|
tokensUsed Int?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
@@map("anthropic_queries")
|
||||||
|
@@index([createdAt])
|
||||||
|
@@index([success])
|
||||||
|
}
|
||||||
@@ -1,6 +1,11 @@
|
|||||||
import Anthropic from '@anthropic-ai/sdk';
|
import Anthropic from '@anthropic-ai/sdk';
|
||||||
|
|
||||||
export async function getMessage(text: string) {
|
interface AnthropicResponse {
|
||||||
|
text: string;
|
||||||
|
tokensUsed: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getMessage(text: string): Promise<AnthropicResponse> {
|
||||||
const anthropic = new Anthropic({
|
const anthropic = new Anthropic({
|
||||||
apiKey: process.env.ANTHROPIC_API_KEY
|
apiKey: process.env.ANTHROPIC_API_KEY
|
||||||
});
|
});
|
||||||
@@ -18,7 +23,20 @@ export async function getMessage(text: string) {
|
|||||||
try {
|
try {
|
||||||
const data = response.content as [{ type: string; text: string }];
|
const data = response.content as [{ type: string; text: string }];
|
||||||
|
|
||||||
return data[0].text;
|
const tokensUsed =
|
||||||
|
(response.usage?.input_tokens || 0) +
|
||||||
|
(response.usage?.output_tokens || 0);
|
||||||
|
|
||||||
|
console.info('Token usage:', {
|
||||||
|
input_tokens: response.usage?.input_tokens,
|
||||||
|
output_tokens: response.usage?.output_tokens,
|
||||||
|
total: tokensUsed
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
text: data[0].text,
|
||||||
|
tokensUsed
|
||||||
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`Anthropic client error: ${JSON.stringify(error)}.`);
|
throw new Error(`Anthropic client error: ${JSON.stringify(error)}.`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,66 @@
|
|||||||
import { getMessage } from '@utils/anthropicClient';
|
import { getMessage } from '@utils/anthropicClient';
|
||||||
import { ShortcutsResponse } from '../types';
|
import { ShortcutsResponse } from '../types';
|
||||||
|
import { dbOperations } from '@utils/db';
|
||||||
|
|
||||||
export async function anthropicCommand(
|
export async function anthropicCommand(
|
||||||
parameters: Record<string, string> | undefined
|
parameters: Record<string, string> | undefined
|
||||||
): Promise<ShortcutsResponse> {
|
): Promise<ShortcutsResponse> {
|
||||||
|
let question = '';
|
||||||
|
let response = '';
|
||||||
|
let success = false;
|
||||||
|
let errorMessage: string | undefined;
|
||||||
|
let tokensUsed: number | undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!parameters || !parameters['question']) {
|
if (!parameters || !parameters['question']) {
|
||||||
|
errorMessage = 'Need to provide a question.';
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Sorry. Need to provide a question.'
|
message: 'Sorry. Need to provide a question.'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await getMessage(
|
question = parameters['question'];
|
||||||
|
const prompt =
|
||||||
'I want to know ' +
|
'I want to know ' +
|
||||||
parameters['question'] +
|
question +
|
||||||
'. Structure the response in a manner suitable for spoken communication.'
|
'. Structure the response in a manner suitable for spoken communication.';
|
||||||
);
|
|
||||||
|
const anthropicResponse = await getMessage(prompt);
|
||||||
|
response = anthropicResponse.text;
|
||||||
|
tokensUsed = anthropicResponse.tokensUsed;
|
||||||
|
success = true;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: response
|
message: response,
|
||||||
|
data: {
|
||||||
|
tokensUsed
|
||||||
|
}
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error('Anthropic command error:', error);
|
||||||
|
success = false;
|
||||||
|
errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||||
|
response = 'Sorry. There was a problem with Anthropic.';
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Sorry. There was a problem with Anthropic.'
|
message: response
|
||||||
};
|
};
|
||||||
|
} finally {
|
||||||
|
if (question) {
|
||||||
|
try {
|
||||||
|
await dbOperations.saveAnthropicQuery({
|
||||||
|
question,
|
||||||
|
response,
|
||||||
|
success,
|
||||||
|
errorMessage,
|
||||||
|
tokensUsed
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to log query to database:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
44
utils/db.ts
Normal file
44
utils/db.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
var prisma: PrismaClient | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const db = global.prisma || new PrismaClient();
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
global.prisma = db;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const dbOperations = {
|
||||||
|
async saveAnthropicQuery({
|
||||||
|
question,
|
||||||
|
response,
|
||||||
|
success,
|
||||||
|
errorMessage,
|
||||||
|
tokensUsed
|
||||||
|
}: {
|
||||||
|
question: string;
|
||||||
|
response: string;
|
||||||
|
success: boolean;
|
||||||
|
errorMessage?: string;
|
||||||
|
tokensUsed?: number;
|
||||||
|
}) {
|
||||||
|
try {
|
||||||
|
return await db.anthropicQuery.create({
|
||||||
|
data: {
|
||||||
|
question,
|
||||||
|
response,
|
||||||
|
success,
|
||||||
|
errorMessage,
|
||||||
|
tokensUsed
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to save query:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export { db };
|
||||||
52
yarn.lock
52
yarn.lock
@@ -945,6 +945,47 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz#3dc35ba0f1e66b403c00b39344f870298ebb1c8e"
|
resolved "https://registry.yarnpkg.com/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz#3dc35ba0f1e66b403c00b39344f870298ebb1c8e"
|
||||||
integrity sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==
|
integrity sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==
|
||||||
|
|
||||||
|
"@prisma/client@^5.22.0":
|
||||||
|
version "5.22.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@prisma/client/-/client-5.22.0.tgz#da1ca9c133fbefe89e0da781c75e1c59da5f8802"
|
||||||
|
integrity sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA==
|
||||||
|
|
||||||
|
"@prisma/debug@5.22.0":
|
||||||
|
version "5.22.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@prisma/debug/-/debug-5.22.0.tgz#58af56ed7f6f313df9fb1042b6224d3174bbf412"
|
||||||
|
integrity sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ==
|
||||||
|
|
||||||
|
"@prisma/engines-version@5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2":
|
||||||
|
version "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2.tgz#d534dd7235c1ba5a23bacd5b92cc0ca3894c28f4"
|
||||||
|
integrity sha512-2PTmxFR2yHW/eB3uqWtcgRcgAbG1rwG9ZriSvQw+nnb7c4uCr3RAcGMb6/zfE88SKlC1Nj2ziUvc96Z379mHgQ==
|
||||||
|
|
||||||
|
"@prisma/engines@5.22.0":
|
||||||
|
version "5.22.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-5.22.0.tgz#28f3f52a2812c990a8b66eb93a0987816a5b6d84"
|
||||||
|
integrity sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==
|
||||||
|
dependencies:
|
||||||
|
"@prisma/debug" "5.22.0"
|
||||||
|
"@prisma/engines-version" "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2"
|
||||||
|
"@prisma/fetch-engine" "5.22.0"
|
||||||
|
"@prisma/get-platform" "5.22.0"
|
||||||
|
|
||||||
|
"@prisma/fetch-engine@5.22.0":
|
||||||
|
version "5.22.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@prisma/fetch-engine/-/fetch-engine-5.22.0.tgz#4fb691b483a450c5548aac2f837b267dd50ef52e"
|
||||||
|
integrity sha512-bkrD/Mc2fSvkQBV5EpoFcZ87AvOgDxbG99488a5cexp5Ccny+UM6MAe/UFkUC0wLYD9+9befNOqGiIJhhq+HbA==
|
||||||
|
dependencies:
|
||||||
|
"@prisma/debug" "5.22.0"
|
||||||
|
"@prisma/engines-version" "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2"
|
||||||
|
"@prisma/get-platform" "5.22.0"
|
||||||
|
|
||||||
|
"@prisma/get-platform@5.22.0":
|
||||||
|
version "5.22.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@prisma/get-platform/-/get-platform-5.22.0.tgz#fc675bc9d12614ca2dade0506c9c4a77e7dddacd"
|
||||||
|
integrity sha512-pHhpQdr1UPFpt+zFfnPazhulaZYCUqeIcPpJViYoq9R+D/yw4fjE+CtnsnKzPYm0ddUbeXUzjGVGIRVgPDCk4Q==
|
||||||
|
dependencies:
|
||||||
|
"@prisma/debug" "5.22.0"
|
||||||
|
|
||||||
"@rtsao/scc@^1.1.0":
|
"@rtsao/scc@^1.1.0":
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8"
|
resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8"
|
||||||
@@ -2648,7 +2689,7 @@ fs.realpath@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
||||||
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
|
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
|
||||||
|
|
||||||
fsevents@^2.3.2:
|
fsevents@2.3.3, fsevents@^2.3.2:
|
||||||
version "2.3.3"
|
version "2.3.3"
|
||||||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
|
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
|
||||||
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
|
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
|
||||||
@@ -4322,6 +4363,15 @@ pretty-format@^29.0.0, pretty-format@^29.7.0:
|
|||||||
ansi-styles "^5.0.0"
|
ansi-styles "^5.0.0"
|
||||||
react-is "^18.0.0"
|
react-is "^18.0.0"
|
||||||
|
|
||||||
|
prisma@^5.22.0:
|
||||||
|
version "5.22.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/prisma/-/prisma-5.22.0.tgz#1f6717ff487cdef5f5799cc1010459920e2e6197"
|
||||||
|
integrity sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A==
|
||||||
|
dependencies:
|
||||||
|
"@prisma/engines" "5.22.0"
|
||||||
|
optionalDependencies:
|
||||||
|
fsevents "2.3.3"
|
||||||
|
|
||||||
prompts@^2.0.1:
|
prompts@^2.0.1:
|
||||||
version "2.4.2"
|
version "2.4.2"
|
||||||
resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069"
|
resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069"
|
||||||
|
|||||||
Reference in New Issue
Block a user