@@ -1,2 +1,3 @@
|
||||
DATABASE_URL=""
|
||||
USER_KEY=""
|
||||
ANTHROPIC_API_KEY=""
|
||||
10
package.json
10
package.json
@@ -5,9 +5,10 @@
|
||||
"author": "riccardo@frompixels.com",
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"build": "prisma generate && next build",
|
||||
"start": "next start",
|
||||
"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",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"test": "jest",
|
||||
@@ -17,10 +18,14 @@
|
||||
"analyze": "BUNDLE_ANALYZE=true next build",
|
||||
"audit": "audit-ci",
|
||||
"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": {
|
||||
"@anthropic-ai/sdk": "^0.33.1",
|
||||
"@prisma/client": "^5.22.0",
|
||||
"axios": "^1.8.2",
|
||||
"next": "^15.2.4",
|
||||
"react": "^19.0.0",
|
||||
@@ -45,6 +50,7 @@
|
||||
"jest": "^29.7.0",
|
||||
"lint-staged": "^15.3.0",
|
||||
"prettier": "^3.4.2",
|
||||
"prisma": "^5.22.0",
|
||||
"ts-jest": "^29.2.5",
|
||||
"ts-node": "^10.9.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';
|
||||
|
||||
export async function getMessage(text: string) {
|
||||
interface AnthropicResponse {
|
||||
text: string;
|
||||
tokensUsed: number;
|
||||
}
|
||||
|
||||
export async function getMessage(text: string): Promise<AnthropicResponse> {
|
||||
const anthropic = new Anthropic({
|
||||
apiKey: process.env.ANTHROPIC_API_KEY
|
||||
});
|
||||
@@ -18,7 +23,20 @@ export async function getMessage(text: string) {
|
||||
try {
|
||||
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) {
|
||||
throw new Error(`Anthropic client error: ${JSON.stringify(error)}.`);
|
||||
}
|
||||
|
||||
@@ -1,32 +1,66 @@
|
||||
import { getMessage } from '@utils/anthropicClient';
|
||||
import { ShortcutsResponse } from '../types';
|
||||
import { dbOperations } from '@utils/db';
|
||||
|
||||
export async function anthropicCommand(
|
||||
parameters: Record<string, string> | undefined
|
||||
): Promise<ShortcutsResponse> {
|
||||
let question = '';
|
||||
let response = '';
|
||||
let success = false;
|
||||
let errorMessage: string | undefined;
|
||||
let tokensUsed: number | undefined;
|
||||
|
||||
try {
|
||||
if (!parameters || !parameters['question']) {
|
||||
errorMessage = 'Need to provide a question.';
|
||||
return {
|
||||
success: false,
|
||||
message: 'Sorry. Need to provide a question.'
|
||||
};
|
||||
}
|
||||
|
||||
const response = await getMessage(
|
||||
question = parameters['question'];
|
||||
const prompt =
|
||||
'I want to know ' +
|
||||
parameters['question'] +
|
||||
'. Structure the response in a manner suitable for spoken communication.'
|
||||
);
|
||||
question +
|
||||
'. Structure the response in a manner suitable for spoken communication.';
|
||||
|
||||
const anthropicResponse = await getMessage(prompt);
|
||||
response = anthropicResponse.text;
|
||||
tokensUsed = anthropicResponse.tokensUsed;
|
||||
success = true;
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: response
|
||||
message: response,
|
||||
data: {
|
||||
tokensUsed
|
||||
}
|
||||
};
|
||||
} 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 {
|
||||
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"
|
||||
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":
|
||||
version "1.1.0"
|
||||
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"
|
||||
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
|
||||
|
||||
fsevents@^2.3.2:
|
||||
fsevents@2.3.3, fsevents@^2.3.2:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
|
||||
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"
|
||||
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:
|
||||
version "2.4.2"
|
||||
resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069"
|
||||
|
||||
Reference in New Issue
Block a user