Added Email, Contacts and ChatGPT

This commit is contained in:
Samuel Andert 2023-09-21 20:45:49 +02:00
parent 0312f50d51
commit 5e3631c49d
19 changed files with 762 additions and 70 deletions

View File

@ -0,0 +1,20 @@
// .wundergraph/operations/getChatwootContacts.ts
import { createOperation, z } from '../generated/wundergraph.factory';
import axios from 'axios';
export default createOperation.query({
input: z.object({
page: z.number().optional(),
}),
handler: async ({ page = 1 }) => {
console.log('Making request to Chatwoot API');
const { data } = await axios.get(`https://chatwoot.andert.me/api/v1/accounts/1/contacts?page=${page}`, {
headers: {
api_access_token: process.env.CHATWOOT_API_ACCESS_TOKEN
},
});
return data;
},
});

View File

@ -0,0 +1,19 @@
// .wundergraph/operations/getChatwootConversations.ts
import { createOperation, z } from '../generated/wundergraph.factory';
import axios from 'axios';
export default createOperation.query({
input: z.object({}),
handler: async () => {
console.log('Making request to Chatwoot API');
const { data } = await axios.get('https://chatwoot.andert.me/api/v1/accounts/1/conversations?status=open&sort_by=last_activity_at', {
headers: {
api_access_token: process.env.CHATWOOT_API_ACCESS_TOKEN
},
});
return data;
},
});

View File

@ -6,34 +6,58 @@ export default createOperation.query({
input: z.object({}),
handler: async () => {
console.log('Making request to Paperless API');
const { data } = await axios.get('https://paperless.andert.me/api/documents/', {
headers: {
Authorization: process.env.PAPERLESS_TOKEN,
},
});
console.log('Received response:', data.results);
// Add download link, thumbnail link, preview link, and PDF data to each document
const documentsWithLinksAndData = await Promise.all(data.results.map(async doc => {
// Add download link, thumbnail link, preview link, PDF data, and metadata to each document
const documentsWithLinksDataAndMetadata = await Promise.all(data.results.map(async doc => {
const response = await axios.get(`https://paperless.andert.me/api/documents/${doc.id}/preview/`, {
responseType: 'arraybuffer',
headers: {
Authorization: process.env.PAPERLESS_TOKEN,
},
});
const pdfData = Buffer.from(response.data, 'binary').toString('base64');
const correspondentResponse = await axios.get(`https://paperless.andert.me/api/correspondents/${doc.correspondent}/`, {
headers: {
Authorization: process.env.PAPERLESS_TOKEN,
},
});
const correspondent = correspondentResponse.data;
const tagsResponse = await Promise.all(doc.tags.map(tag => axios.get(`https://paperless.andert.me/api/tags/${tag}/`, {
headers: {
Authorization: process.env.PAPERLESS_TOKEN,
},
})));
const tags = tagsResponse.map(response => response.data);
const documentTypeResponse = await axios.get(`https://paperless.andert.me/api/document_types/${doc.document_type}/`, {
headers: {
Authorization: process.env.PAPERLESS_TOKEN,
},
});
const documentType = documentTypeResponse.data;
return {
...doc,
downloadLink: `https://paperless.andert.me/api/documents/${doc.id}/download/`,
thumbnailLink: `https://paperless.andert.me/api/documents/${doc.id}/thumb/`,
previewLink: `https://paperless.andert.me/api/documents/${doc.id}/preview/`,
pdfData,
correspondent,
tags,
document_type: documentType,
};
}));
return documentsWithLinksAndData;
return documentsWithLinksDataAndMetadata;
},
});

View File

@ -2,41 +2,40 @@ type Query {
projects(filter: projects_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): [projects!]!
projects_by_id(id: ID!): projects
projects_aggregated(groupBy: [String], filter: projects_filter, limit: Int, offset: Int, page: Int, search: String, sort: [String]): [projects_aggregated!]!
todos(filter: todos_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): [todos!]!
todos_by_id(id: ID!): todos
todos_aggregated(groupBy: [String], filter: todos_filter, limit: Int, offset: Int, page: Int, search: String, sort: [String]): [todos_aggregated!]!
bookmarks(filter: bookmarks_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): [bookmarks!]!
bookmarks_by_id(id: ID!): bookmarks
bookmarks_aggregated(groupBy: [String], filter: bookmarks_filter, limit: Int, offset: Int, page: Int, search: String, sort: [String]): [bookmarks_aggregated!]!
todos(filter: todos_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): [todos!]!
todos_by_id(id: ID!): todos
todos_aggregated(groupBy: [String], filter: todos_filter, limit: Int, offset: Int, page: Int, search: String, sort: [String]): [todos_aggregated!]!
}
type Mutation {
create_projects_items(filter: projects_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String, data: [create_projects_input!]): [projects!]!
create_projects_item(data: create_projects_input!): projects
create_todos_items(filter: todos_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String, data: [create_todos_input!]): [todos!]!
create_todos_item(data: create_todos_input!): todos
create_bookmarks_items(filter: bookmarks_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String, data: [create_bookmarks_input!]): [bookmarks!]!
create_bookmarks_item(data: create_bookmarks_input!): bookmarks
create_todos_items(filter: todos_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String, data: [create_todos_input!]): [todos!]!
create_todos_item(data: create_todos_input!): todos
update_projects_items(filter: projects_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String, ids: [ID]!, data: update_projects_input!): [projects!]!
update_projects_batch(filter: projects_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String, data: [update_projects_input!]): [projects!]!
update_projects_item(id: ID!, data: update_projects_input!): projects
update_todos_items(filter: todos_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String, ids: [ID]!, data: update_todos_input!): [todos!]!
update_todos_batch(filter: todos_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String, data: [update_todos_input!]): [todos!]!
update_todos_item(id: ID!, data: update_todos_input!): todos
update_bookmarks_items(filter: bookmarks_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String, ids: [ID]!, data: update_bookmarks_input!): [bookmarks!]!
update_bookmarks_batch(filter: bookmarks_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String, data: [update_bookmarks_input!]): [bookmarks!]!
update_bookmarks_item(id: ID!, data: update_bookmarks_input!): bookmarks
update_todos_items(filter: todos_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String, ids: [ID]!, data: update_todos_input!): [todos!]!
update_todos_batch(filter: todos_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String, data: [update_todos_input!]): [todos!]!
update_todos_item(id: ID!, data: update_todos_input!): todos
delete_projects_items(ids: [ID]!): delete_many
delete_projects_item(id: ID!): delete_one
delete_todos_items(ids: [ID]!): delete_many
delete_todos_item(id: ID!): delete_one
delete_bookmarks_items(ids: [ID]!): delete_many
delete_bookmarks_item(id: ID!): delete_one
delete_todos_items(ids: [ID]!): delete_many
delete_todos_item(id: ID!): delete_one
}
type Subscription {
projects_mutated(event: EventEnum): projects_mutated
todos_mutated(event: EventEnum): todos_mutated
directus_dashboards_mutated(event: EventEnum): directus_dashboards_mutated
directus_activity_mutated(event: EventEnum): directus_activity_mutated
directus_notifications_mutated(event: EventEnum): directus_notifications_mutated
@ -55,6 +54,7 @@ type Subscription {
directus_shares_mutated(event: EventEnum): directus_shares_mutated
directus_webhooks_mutated(event: EventEnum): directus_webhooks_mutated
bookmarks_mutated(event: EventEnum): bookmarks_mutated
todos_mutated(event: EventEnum): todos_mutated
}
"""The `Boolean` scalar type represents `true` or `false`."""
@ -620,15 +620,16 @@ type projects_mutated {
type todos {
id: ID!
status: String
sort: Int
user_created(filter: directus_users_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_users
date_created: Date
date_created_func: datetime_functions
user_updated(filter: directus_users_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_users
date_updated: Date
date_updated_func: datetime_functions
enddate: Date
enddate_func: datetime_functions
task: String
type: String
}
type todos_aggregated {
@ -636,27 +637,17 @@ type todos_aggregated {
countAll: Int
count: todos_aggregated_count
countDistinct: todos_aggregated_count
avg: todos_aggregated_fields
sum: todos_aggregated_fields
avgDistinct: todos_aggregated_fields
sumDistinct: todos_aggregated_fields
min: todos_aggregated_fields
max: todos_aggregated_fields
}
type todos_aggregated_count {
id: Int
status: Int
sort: Int
user_created: Int
date_created: Int
user_updated: Int
date_updated: Int
enddate: Int
task: Int
}
type todos_aggregated_fields {
sort: Float
type: Int
}
type todos_mutated {
@ -786,13 +777,13 @@ input create_projects_input {
input create_todos_input {
id: ID
status: String
sort: Int
user_created: create_directus_users_input
date_created: Date
user_updated: create_directus_users_input
date_updated: Date
enddate: Date
task: String
type: String
}
input date_filter_operators {
@ -1073,15 +1064,16 @@ input string_filter_operators {
input todos_filter {
id: string_filter_operators
status: string_filter_operators
sort: number_filter_operators
user_created: directus_users_filter
date_created: date_filter_operators
date_created_func: datetime_function_filter_operators
user_updated: directus_users_filter
date_updated: date_filter_operators
date_updated_func: datetime_function_filter_operators
enddate: date_filter_operators
enddate_func: datetime_function_filter_operators
task: string_filter_operators
type: string_filter_operators
_and: [todos_filter]
_or: [todos_filter]
}
@ -1178,11 +1170,11 @@ input update_projects_input {
input update_todos_input {
id: ID
status: String
sort: Int
user_created: update_directus_users_input
date_created: Date
user_updated: update_directus_users_input
date_updated: Date
enddate: Date
task: String
type: String
}

View File

@ -199,7 +199,6 @@ type Mutation {
type Subscription {
projects_mutated(event: EventEnum): projects_mutated
todos_mutated(event: EventEnum): todos_mutated
directus_dashboards_mutated(event: EventEnum): directus_dashboards_mutated
directus_activity_mutated(event: EventEnum): directus_activity_mutated
directus_notifications_mutated(event: EventEnum): directus_notifications_mutated
@ -218,6 +217,7 @@ type Subscription {
directus_shares_mutated(event: EventEnum): directus_shares_mutated
directus_webhooks_mutated(event: EventEnum): directus_webhooks_mutated
bookmarks_mutated(event: EventEnum): bookmarks_mutated
todos_mutated(event: EventEnum): todos_mutated
}
"""The `Boolean` scalar type represents `true` or `false`."""
@ -1392,15 +1392,16 @@ type server_info_websocket_rest {
type todos {
id: ID!
status: String
sort: Int
user_created(filter: directus_users_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_users
date_created: Date
date_created_func: datetime_functions
user_updated(filter: directus_users_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_users
date_updated: Date
date_updated_func: datetime_functions
enddate: Date
enddate_func: datetime_functions
task: String
type: String
}
type todos_mutated {

View File

@ -58,6 +58,7 @@
"@wundergraph/sdk": "^0.174.5",
"@wundergraph/svelte-query": "^0.3.10",
"@xstate/svelte": "^2.1.0",
"ai": "^2.2.13",
"axios": "^1.4.0",
"cookie": "^0.5.0",
"dotenv": "^16.3.1",
@ -67,6 +68,7 @@
"jsonwebtoken": "^9.0.1",
"jwks-rsa": "^3.0.1",
"node-jose": "^2.2.0",
"openai": "^4.8.0",
"path": "^0.12.7",
"sqlite3": "^5.1.6",
"svelte-kit-cookie-session": "^4.0.0",

View File

@ -52,6 +52,9 @@ dependencies:
'@xstate/svelte':
specifier: ^2.1.0
version: 2.1.0(svelte@3.54.0)(xstate@4.38.2)
ai:
specifier: ^2.2.13
version: 2.2.13(react@18.2.0)(solid-js@1.7.12)(svelte@3.54.0)(vue@3.3.4)
axios:
specifier: ^1.4.0
version: 1.4.0
@ -79,6 +82,9 @@ dependencies:
node-jose:
specifier: ^2.2.0
version: 2.2.0
openai:
specifier: ^4.8.0
version: 4.8.0
path:
specifier: ^0.12.7
version: 0.12.7
@ -5410,6 +5416,13 @@ packages:
resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==}
dev: false
/@types/node-fetch@2.6.5:
resolution: {integrity: sha512-OZsUlr2nxvkqUFLSaY2ZbA+P1q22q+KrlxWOn/38RX+u5kTkYL2mTujEpzUhGkS+K/QCYp9oagfXG39XOzyySg==}
dependencies:
'@types/node': 20.5.8
form-data: 4.0.0
dev: false
/@types/node@12.20.55:
resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==}
dev: false
@ -5502,6 +5515,89 @@ packages:
'@types/yargs-parser': 21.0.0
dev: false
/@vue/compiler-core@3.3.4:
resolution: {integrity: sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==}
dependencies:
'@babel/parser': 7.22.16
'@vue/shared': 3.3.4
estree-walker: 2.0.2
source-map-js: 1.0.2
dev: false
/@vue/compiler-dom@3.3.4:
resolution: {integrity: sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w==}
dependencies:
'@vue/compiler-core': 3.3.4
'@vue/shared': 3.3.4
dev: false
/@vue/compiler-sfc@3.3.4:
resolution: {integrity: sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==}
dependencies:
'@babel/parser': 7.22.16
'@vue/compiler-core': 3.3.4
'@vue/compiler-dom': 3.3.4
'@vue/compiler-ssr': 3.3.4
'@vue/reactivity-transform': 3.3.4
'@vue/shared': 3.3.4
estree-walker: 2.0.2
magic-string: 0.30.3
postcss: 8.4.29
source-map-js: 1.0.2
dev: false
/@vue/compiler-ssr@3.3.4:
resolution: {integrity: sha512-m0v6oKpup2nMSehwA6Uuu+j+wEwcy7QmwMkVNVfrV9P2qE5KshC6RwOCq8fjGS/Eak/uNb8AaWekfiXxbBB6gQ==}
dependencies:
'@vue/compiler-dom': 3.3.4
'@vue/shared': 3.3.4
dev: false
/@vue/reactivity-transform@3.3.4:
resolution: {integrity: sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==}
dependencies:
'@babel/parser': 7.22.16
'@vue/compiler-core': 3.3.4
'@vue/shared': 3.3.4
estree-walker: 2.0.2
magic-string: 0.30.3
dev: false
/@vue/reactivity@3.3.4:
resolution: {integrity: sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ==}
dependencies:
'@vue/shared': 3.3.4
dev: false
/@vue/runtime-core@3.3.4:
resolution: {integrity: sha512-R+bqxMN6pWO7zGI4OMlmvePOdP2c93GsHFM/siJI7O2nxFRzj55pLwkpCedEY+bTMgp5miZ8CxfIZo3S+gFqvA==}
dependencies:
'@vue/reactivity': 3.3.4
'@vue/shared': 3.3.4
dev: false
/@vue/runtime-dom@3.3.4:
resolution: {integrity: sha512-Aj5bTJ3u5sFsUckRghsNjVTtxZQ1OyMWCr5dZRAPijF/0Vy4xEoRCwLyHXcj4D0UFbJ4lbx3gPTgg06K/GnPnQ==}
dependencies:
'@vue/runtime-core': 3.3.4
'@vue/shared': 3.3.4
csstype: 3.1.2
dev: false
/@vue/server-renderer@3.3.4(vue@3.3.4):
resolution: {integrity: sha512-Q6jDDzR23ViIb67v+vM1Dqntu+HUexQcsWKhhQa4ARVzxOY2HbC7QRW/ggkDBd5BU+uM1sV6XOAP0b216o34JQ==}
peerDependencies:
vue: 3.3.4
dependencies:
'@vue/compiler-ssr': 3.3.4
'@vue/shared': 3.3.4
vue: 3.3.4
dev: false
/@vue/shared@3.3.4:
resolution: {integrity: sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==}
dev: false
/@wagmi/chains@1.7.0(typescript@5.0.2):
resolution: {integrity: sha512-TKVeHv0GqP5sV1yQ8BDGYToAFezPnCexbbBpeH14x7ywi5a1dDStPffpt9x+ytE6LJWkZ6pAMs/HNWXBQ5Nqmw==}
peerDependencies:
@ -6247,6 +6343,40 @@ packages:
dev: false
optional: true
/ai@2.2.13(react@18.2.0)(solid-js@1.7.12)(svelte@3.54.0)(vue@3.3.4):
resolution: {integrity: sha512-tq0OpUUryPjSPn7WYhZ7viGUq1d2/L5T0FZawGun9ojYYOzu5eJIAQAnZ2HRTXsFIwuYTb9wAgZUx+sQUDjdnQ==}
engines: {node: '>=14.6'}
peerDependencies:
react: ^18.2.0
solid-js: ^1.7.7
svelte: ^3.0.0 || ^4.0.0
vue: ^3.3.4
peerDependenciesMeta:
react:
optional: true
solid-js:
optional: true
svelte:
optional: true
vue:
optional: true
dependencies:
eventsource-parser: 1.0.0
nanoid: 3.3.6
openai: 4.2.0
react: 18.2.0
solid-js: 1.7.12
solid-swr-store: 0.10.7(solid-js@1.7.12)(swr-store@0.10.6)
sswr: 2.0.0(svelte@3.54.0)
svelte: 3.54.0
swr: 2.2.0(react@18.2.0)
swr-store: 0.10.6
swrv: 1.0.4(vue@3.3.4)
vue: 3.3.4
transitivePeerDependencies:
- encoding
dev: false
/ajv-formats@2.1.1(ajv@8.12.0):
resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==}
peerDependencies:
@ -6916,6 +7046,10 @@ packages:
tslib: 2.6.2
dev: false
/charenc@0.0.2:
resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==}
dev: false
/charset@1.0.1:
resolution: {integrity: sha512-6dVyOOYjpfFcL1Y4qChrAoQLRHvj2ziyhcm0QJlhOcAhykL/k1kTUPbeo+87MNRTRdk2OIIsIXbuF3x2wi5EXg==}
engines: {node: '>=4.0.0'}
@ -7207,12 +7341,20 @@ packages:
which: 2.0.2
dev: false
/crypt@0.0.2:
resolution: {integrity: sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==}
dev: false
/cssesc@3.0.0:
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
engines: {node: '>=4'}
hasBin: true
dev: true
/csstype@3.1.2:
resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==}
dev: false
/d@1.0.1:
resolution: {integrity: sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==}
dependencies:
@ -7334,6 +7476,11 @@ packages:
prop-types: 15.8.1
dev: false
/dequal@2.0.3:
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
engines: {node: '>=6'}
dev: false
/destroy@1.2.0:
resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==}
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
@ -7372,6 +7519,13 @@ packages:
engines: {node: '>=0.3.1'}
dev: false
/digest-fetch@1.3.0:
resolution: {integrity: sha512-CGJuv6iKNM7QyZlM2T3sPAdZWd/p9zQiRNS9G+9COUCwzWFTs0Xp8NF5iePx7wtvhDykReiRRrSeNb4oMmB8lA==}
dependencies:
base-64: 0.1.0
md5: 2.3.0
dev: false
/dijkstrajs@1.0.3:
resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==}
dev: false
@ -7635,6 +7789,10 @@ packages:
hasBin: true
dev: false
/estree-walker@2.0.2:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
dev: false
/esutils@2.0.3:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'}
@ -7794,6 +7952,11 @@ packages:
engines: {node: '>=0.8.x'}
dev: false
/eventsource-parser@1.0.0:
resolution: {integrity: sha512-9jgfSCa3dmEme2ES3mPByGXfgZ87VbP97tng1G2nWwWx6bV2nYxm2AWCrbQjXToSe+yYlqaZNtxffR9IeQr95g==}
engines: {node: '>=14.18'}
dev: false
/execa@5.1.1:
resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
engines: {node: '>=10'}
@ -8048,6 +8211,10 @@ packages:
resolution: {integrity: sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==}
dev: false
/form-data-encoder@1.7.2:
resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==}
dev: false
/form-data@4.0.0:
resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
engines: {node: '>= 6'}
@ -8056,6 +8223,14 @@ packages:
combined-stream: 1.0.8
mime-types: 2.1.35
/formdata-node@4.4.1:
resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==}
engines: {node: '>= 12.20'}
dependencies:
node-domexception: 1.0.0
web-streams-polyfill: 4.0.0-beta.3
dev: false
/forwarded@0.2.0:
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
engines: {node: '>= 0.6'}
@ -8758,6 +8933,10 @@ packages:
binary-extensions: 2.2.0
dev: true
/is-buffer@1.1.6:
resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==}
dev: false
/is-callable@1.2.7:
resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
engines: {node: '>= 0.4'}
@ -9642,7 +9821,6 @@ packages:
engines: {node: '>=12'}
dependencies:
'@jridgewell/sourcemap-codec': 1.4.15
dev: true
/make-dir@2.1.0:
resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==}
@ -9699,6 +9877,14 @@ packages:
resolution: {integrity: sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==}
dev: false
/md5@2.3.0:
resolution: {integrity: sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==}
dependencies:
charenc: 0.0.2
crypt: 0.0.2
is-buffer: 1.1.6
dev: false
/memoize-one@5.2.1:
resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==}
dev: false
@ -10379,6 +10565,11 @@ packages:
minimatch: 3.1.2
dev: false
/node-domexception@1.0.0:
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
engines: {node: '>=10.5.0'}
dev: false
/node-fetch-h2@2.3.0:
resolution: {integrity: sha512-ofRW94Ab0T4AOh5Fk8t0h8OBWrmjb0SSB20xh1H8YnPV9EJ+f5AMoYSUQ2zgJ4Iq2HAK0I2l5/Nequ8YzFS3Hg==}
engines: {node: 4.x || >=6.0.0}
@ -10647,6 +10838,38 @@ packages:
- debug
dev: false
/openai@4.2.0:
resolution: {integrity: sha512-zfvpO2eITIxIjTG8T6Cek7NB2dMvP/LW0TRUJ4P9E8+qbBNKw00DrtfF64b+fAV2+wUYCVyynT6iSycJ//TtbA==}
hasBin: true
dependencies:
'@types/node': 18.15.13
'@types/node-fetch': 2.6.5
abort-controller: 3.0.0
agentkeepalive: 4.5.0
digest-fetch: 1.3.0
form-data-encoder: 1.7.2
formdata-node: 4.4.1
node-fetch: 2.7.0
transitivePeerDependencies:
- encoding
dev: false
/openai@4.8.0:
resolution: {integrity: sha512-CnLZvHi2x4pIoGAWCaj3jHi1a6NA4oFBL6mJDSXkIR5A/wv6lven7uL2gxMevjGBLA7OqYqis3Z2PMluiGauVw==}
hasBin: true
dependencies:
'@types/node': 18.15.13
'@types/node-fetch': 2.6.5
abort-controller: 3.0.0
agentkeepalive: 4.5.0
digest-fetch: 1.3.0
form-data-encoder: 1.7.2
formdata-node: 4.4.1
node-fetch: 2.7.0
transitivePeerDependencies:
- encoding
dev: false
/openapi-types@12.1.0:
resolution: {integrity: sha512-XpeCy01X6L5EpP+6Hc3jWN7rMZJ+/k1lwki/kTmWzbVhdPie3jd5O2ZtedEx8Yp58icJ0osVldLMrTB/zslQXA==}
dev: false
@ -11009,7 +11232,6 @@ packages:
nanoid: 3.3.6
picocolors: 1.0.0
source-map-js: 1.0.2
dev: true
/postman-collection@4.2.0:
resolution: {integrity: sha512-tvOLgN1h6Kab6dt43PmBoV5kYO/YUta3x0C2QqfmbzmHZe47VTpZ/+gIkGlbNhjKNPUUub5X6ehxYKoaTYdy1w==}
@ -11784,6 +12006,11 @@ packages:
engines: {node: '>=0.10.0'}
dev: false
/seroval@0.5.1:
resolution: {integrity: sha512-ZfhQVB59hmIauJG5Ydynupy8KHyr5imGNtdDhbZG68Ufh1Ynkv9KOYOAABf71oVbQxJ8VkWnMHAjEHE7fWkH5g==}
engines: {node: '>=10'}
dev: false
/serve-static@1.15.0:
resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==}
engines: {node: '>= 0.8.0'}
@ -12003,6 +12230,24 @@ packages:
dev: false
optional: true
/solid-js@1.7.12:
resolution: {integrity: sha512-QoyoOUKu14iLoGxjxWFIU8+/1kLT4edQ7mZESFPonsEXZ//VJtPKD8Ud1aTKzotj+MNWmSs9YzK6TdY+fO9Eww==}
dependencies:
csstype: 3.1.2
seroval: 0.5.1
dev: false
/solid-swr-store@0.10.7(solid-js@1.7.12)(swr-store@0.10.6):
resolution: {integrity: sha512-A6d68aJmRP471aWqKKPE2tpgOiR5fH4qXQNfKIec+Vap+MGQm3tvXlT8n0I8UgJSlNAsSAUuw2VTviH2h3Vv5g==}
engines: {node: '>=10'}
peerDependencies:
solid-js: ^1.2
swr-store: ^0.10
dependencies:
solid-js: 1.7.12
swr-store: 0.10.6
dev: false
/sonic-boom@2.8.0:
resolution: {integrity: sha512-kuonw1YOYYNOve5iHdSahXPOK49GqwA+LZhI6Wz/l0rP57iKyXXIHaRagOBHAPmGwJC6od2Z9zgvZ5loSgMlVg==}
dependencies:
@ -12028,7 +12273,6 @@ packages:
/source-map-js@1.0.2:
resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
engines: {node: '>=0.10.0'}
dev: true
/source-map-support@0.5.21:
resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
@ -12106,6 +12350,15 @@ packages:
dev: false
optional: true
/sswr@2.0.0(svelte@3.54.0):
resolution: {integrity: sha512-mV0kkeBHcjcb0M5NqKtKVg/uTIYNlIIniyDfSGrSfxpEdM9C365jK0z55pl9K0xAkNTJi2OAOVFQpgMPUk+V0w==}
peerDependencies:
svelte: ^4.0.0
dependencies:
svelte: 3.54.0
swrev: 4.0.0
dev: false
/stack-utils@2.0.6:
resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==}
engines: {node: '>=10'}
@ -12399,6 +12652,34 @@ packages:
- encoding
dev: false
/swr-store@0.10.6:
resolution: {integrity: sha512-xPjB1hARSiRaNNlUQvWSVrG5SirCjk2TmaUyzzvk69SZQan9hCJqw/5rG9iL7xElHU784GxRPISClq4488/XVw==}
engines: {node: '>=10'}
dependencies:
dequal: 2.0.3
dev: false
/swr@2.2.0(react@18.2.0):
resolution: {integrity: sha512-AjqHOv2lAhkuUdIiBu9xbuettzAzWXmCEcLONNKJRba87WAefz8Ca9d6ds/SzrPc235n1IxWYdhJ2zF3MNUaoQ==}
peerDependencies:
react: ^16.11.0 || ^17.0.0 || ^18.0.0
dependencies:
react: 18.2.0
use-sync-external-store: 1.2.0(react@18.2.0)
dev: false
/swrev@4.0.0:
resolution: {integrity: sha512-LqVcOHSB4cPGgitD1riJ1Hh4vdmITOp+BkmfmXRh4hSF/t7EnS4iD+SOTmq7w5pPm/SiPeto4ADbKS6dHUDWFA==}
dev: false
/swrv@1.0.4(vue@3.3.4):
resolution: {integrity: sha512-zjEkcP8Ywmj+xOJW3lIT65ciY/4AL4e/Or7Gj0MzU3zBJNMdJiT8geVZhINavnlHRMMCcJLHhraLTAiDOTmQ9g==}
peerDependencies:
vue: '>=3.2.26 < 4'
dependencies:
vue: 3.3.4
dev: false
/symbol-observable@2.0.3:
resolution: {integrity: sha512-sQV7phh2WCYAn81oAkakC5qjq2Ml0g8ozqz03wOGnx9dDlG1de6yrF+0RAzSJD8fPUow3PTSMf2SAbOGxb93BA==}
engines: {node: '>=0.10'}
@ -13093,6 +13374,16 @@ packages:
resolution: {integrity: sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==}
dev: false
/vue@3.3.4:
resolution: {integrity: sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw==}
dependencies:
'@vue/compiler-dom': 3.3.4
'@vue/compiler-sfc': 3.3.4
'@vue/runtime-dom': 3.3.4
'@vue/server-renderer': 3.3.4(vue@3.3.4)
'@vue/shared': 3.3.4
dev: false
/wait-on@7.0.1:
resolution: {integrity: sha512-9AnJE9qTjRQOlTZIldAaf/da2eW0eSRSgcqq85mXQja/DW3MriHxkpODDSUEg+Gri/rKEcXUZHe+cevvYItaog==}
engines: {node: '>=12.0.0'}
@ -13124,6 +13415,11 @@ packages:
engines: {node: '>= 8'}
dev: false
/web-streams-polyfill@4.0.0-beta.3:
resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==}
engines: {node: '>= 14'}
dev: false
/web-vitals@3.4.0:
resolution: {integrity: sha512-n9fZ5/bG1oeDkyxLWyep0eahrNcPDF6bFqoyispt7xkW0xhDzpUBTgyDKqWDi1twT0MgH4HvvqzpUyh0ZxZV4A==}
dev: false

49
src/lib/Ai.svelte Normal file
View File

@ -0,0 +1,49 @@
<script>
import { useChat } from "ai/svelte";
import Icon from "@iconify/svelte";
const { input, handleSubmit, messages } = useChat();
</script>
<div class="chat-grid h-full p-4">
<ul class="overflow-auto flex flex-col p-4 text-sm">
{#each $messages as message}
<li class="message {message.role === 'user' ? 'user' : 'assistant'} p-2">
{message.content}
</li>
{/each}
</ul>
<div class="w-full">
<form on:submit={handleSubmit} class="flex items-center">
<input bind:value={$input} class="input flex-grow mr-4" type="text" />
<button class="btn-icon variant-filled-success" type="submit">
<div class="px-4">
<Icon icon="carbon:send-alt-filled" class="" width="24" height="24" />
</div>
</button>
</form>
</div>
</div>
<style>
.chat-grid {
display: grid;
grid-template-rows: 1fr auto;
height: 100%;
}
.message {
max-width: 80%;
padding: px;
margin: 10px;
border-radius: 10px;
}
.user {
align-self: flex-end;
background-color: #0d6efd;
color: white;
}
.assistant {
align-self: flex-start;
background-color: lightblue;
}
</style>

View File

@ -0,0 +1,5 @@
<script>
export let json;
</script>
<pre>{JSON.stringify(json, null, 2)}</pre>

14
src/lib/MailViewer.svelte Normal file
View File

@ -0,0 +1,14 @@
<script>
function shadowroot(node, { html }) {
node.attachShadow({ mode: "open" }).innerHTML = html;
return {
update({ html }) {
node.shadowRoot.innerHTML = html;
},
};
}
export let html = "<h1>Some custom HTML</h1>";
</script>
<div use:shadowroot={{ html }} />

View File

@ -96,19 +96,6 @@
</aside>
<div class="col-span-5 w-full">
<div class="flex justify-end space-x-4">
<div class="w-full">
<input bind:value={search} class="input" type="text" />
</div>
<button type="button" class="btn-icon variant-filled-success">
<div class="px-4">
<Icon
icon="carbon:send-alt-filled"
class=""
width="24"
height="24"
/>
</div>
</button>
<button
on:click={signRequestTrigger}
type="button"

View File

@ -55,22 +55,31 @@
<!-- (fallback contents) -->
{/if}</Drawer
>
<div class="grid h-screen grid-rows-layout bg-color">
<QueryClientProvider client={data.queryClient}>
<div class="grid h-screen grid-layout bg-color">
<QueryClientProvider client={data.queryClient} class="main">
<slot />
</QueryClientProvider>
<div class="row-start-2 row-end-3">
<footer>
<Wallet />
</div>
</footer>
</div>
<style>
.bg-color {
background-color: #e6e7e1;
}
.grid-rows-layout {
.grid-layout {
grid-template-areas:
"main "
"footer ";
grid-template-rows: 1fr auto;
}
.main {
grid-area: main;
}
footer {
grid-area: footer;
}
</style>

View File

@ -0,0 +1,34 @@
import OpenAI from 'openai';
import { OpenAIStream, StreamingTextResponse } from 'ai';
import { env } from '$env/dynamic/private';
// You may want to replace the above with a static private env variable
// for dead-code elimination and build-time type-checking:
// import { OPENAI_API_KEY } from '$env/static/private'
import type { RequestHandler } from './$types';
// Create an OpenAI API client
const openai = new OpenAI({
apiKey: env.OPENAI_API_KEY || '',
});
export const POST = (async ({ request }) => {
// Extract the `prompt` from the body of the request
const { messages } = await request.json();
// Ask OpenAI for a streaming chat completion given the prompt
const response = await openai.chat.completions.create({
model: 'gpt-3.5-turbo',
stream: true,
messages: messages.map((message: any) => ({
content: message.content,
role: message.role,
})),
});
// Convert the response into a friendly text-stream
const stream = OpenAIStream(response);
// Respond with the stream
return new StreamingTextResponse(stream);
}) satisfies RequestHandler;

View File

@ -1,16 +1,85 @@
<script>
import Icon from "@iconify/svelte";
</script>
<div class="w-full h-full overflow-hidden grid grid-cols-6">
<aside class="col-span-1">
<nav class="list-nav p-6">
<nav class="list-nav p-2">
<h3 class="text-xl font-bold mb-4">MY HOME</h3>
<ul>
<li><a href="/me">Dashboard</a></li>
<li><a href="/me/projects">My Projects</a></li>
<!-- <li><a href="/me/documents">My Documents</a></li> -->
<li><a href="/me/banking">Banking</a></li>
<!-- <li><a href="/me/bookmarks">Bookmarks</a></li> -->
<li><a href="/me/paperless">Paperless</a></li>
<li><a href="/me/acc">Access Control</a></li>
<!-- <li><a href="/me/apps">Apps</a></li> -->
<li>
<a href="/me">
<Icon
icon="iconamoon:profile-circle-fill"
class="w-8 h-8 text-gray-500"
/>
Dashboard
</a>
</li>
<li>
<a href="/me/projects">
<Icon
icon="iconamoon:profile-circle-fill"
class="w-8 h-8 text-gray-500"
/>
My Projects
</a>
</li>
<li>
<a href="/me/contacts">
<Icon
icon="iconamoon:profile-circle-fill"
class="w-8 h-8 text-gray-500"
/>
Contacts
</a>
</li>
<li>
<a href="/me/conversations">
<Icon
icon="iconamoon:profile-circle-fill"
class="w-8 h-8 text-gray-500"
/>
Email
</a>
</li>
<li>
<a href="/me/chatGPT">
<Icon
icon="iconamoon:profile-circle-fill"
class="w-8 h-8 text-gray-500"
/>
ChatGPT
</a>
</li>
<li>
<a href="/me/banking">
<Icon
icon="iconamoon:profile-circle-fill"
class="w-8 h-8 text-gray-500"
/>
Banking
</a>
</li>
<li>
<a href="/me/paperless">
<Icon
icon="iconamoon:profile-circle-fill"
class="w-8 h-8 text-gray-500"
/>
Paperless
</a>
</li>
<li>
<a href="/me/acc">
<Icon
icon="iconamoon:profile-circle-fill"
class="w-8 h-8 text-gray-500"
/>
Access Control
</a>
</li>
</ul>
</nav>
</aside>
@ -18,3 +87,9 @@
<slot />
</div>
</div>
<style>
.collapsed {
width: 3rem; /* Adjust as needed */
}
</style>

View File

@ -0,0 +1,5 @@
<script>
import Ai from "$lib/Ai.svelte";
</script>
<Ai />

View File

@ -0,0 +1,37 @@
<script lang="ts">
import JsonViewer from "$lib/JsonViewer.svelte";
import HeaderMain from "$lib/layouts/HeaderMain.svelte";
import { createQuery } from "../../../lib/wundergraph";
const contactsQuery = createQuery({
operationName: "getChatwootContacts",
variables: { page: 2 },
});
</script>
<HeaderMain>
<div slot="header">
<h1>Contacts</h1>
</div>
<div slot="main" class="h-full w-full overflow-scroll">
<div class="w-full h-full overflow-scroll">
{#if $contactsQuery.isLoading}
<p>Loading...</p>
{:else if $contactsQuery.error}
<pre>Error: {JSON.stringify($contactsQuery.error, null, 2)}</pre>
{:else}
<div class="grid grid-cols-3 gap-4">
{#each $contactsQuery.data.payload as contact (contact.id)}
<div class="card">
<header class="card-header">{contact.name}</header>
<section class="p-4">
ID: {contact.id}<br />{contact.email}
</section>
</div>
{/each}
</div>
{/if}
</div>
</div>
</HeaderMain>

View File

@ -0,0 +1,86 @@
<script lang="ts">
import MailViewer from "$lib/MailViewer.svelte";
import { createQuery } from "../../../lib/wundergraph";
import JsonViewer from "$lib/JsonViewer.svelte";
const conversationsQuery = createQuery({
operationName: "getChatwootConversations",
});
let selectedConversation = null;
function selectConversation(conversation) {
selectedConversation = conversation;
}
$: if ($conversationsQuery.data && !selectedConversation) {
selectedConversation = $conversationsQuery.data.data.payload[0];
}
</script>
<div class="h-full w-full overflow-scroll flex">
<div class="w-1/3 h-full overflow-scroll">
{#if $conversationsQuery.isLoading}
<p>Loading...</p>
{:else if $conversationsQuery.error}
Error: <JsonViewer json="$conversationsQuery.error}" />
{:else}
{#each $conversationsQuery.data.data.payload as conversation (conversation.id)}
<div
class="m-1 p-2 border border-gray-200 rounded-md hover:bg-gray-100 cursor-pointer"
on:click={() => selectConversation(conversation)}
>
<h2 class="text-lg font-semibold">
{conversation.meta.sender.name}
</h2>
<p>{conversation.messages[0].content.slice(0, 100)}...</p>
</div>
{/each}
{/if}
</div>
<div class="w-2/3 h-full overflow-scroll relative">
{#if selectedConversation}
<div class="p-4 bg-white z-10 sticky top-0 left-0">
<h2 class="font-bold text-xl">
{selectedConversation.meta.sender.name}
</h2>
<p>
{selectedConversation.id} - {selectedConversation.meta.sender.email}
</p>
</div>
<div class="space-y-4 px-4">
{#each selectedConversation.messages as message (message.id)}
{#if message.content_type == "incoming_email"}
{#if selectedConversation.last_non_activity_message.content != message.content}
<MailViewer
html={selectedConversation.last_non_activity_message
.content_attributes.email.html_content.full}
/>{/if}
<MailViewer
html={message.content_attributes.email.html_content.full}
/>
{:else}
{#if selectedConversation.last_non_activity_message.content != message.content}{selectedConversation
.last_non_activity_message.content}{/if}
<p class="bg-slate-400 py-1 px-2 rounded-sm my-2">
{message.content}
</p>
{/if}
{/each}
</div>
{:else}
<p>Select a conversation to view its details.</p>
{/if}
</div>
</div>
<style>
.btn {
display: inline-block;
padding: 0.5em 1em;
background-color: #007bff;
color: white;
text-decoration: none;
border-radius: 0.25em;
}
</style>

View File

@ -46,6 +46,9 @@
<h2 class="text-lg font-semibold">
{document.archived_file_name}
</h2>
<p>Correspondent: {document.correspondent.name}</p>
<p>Tags: {document.tags.map((tag) => tag.name).join(", ")}</p>
<p>{document.document_type.name}</p>
</div>
</a>
{/each}

View File

@ -0,0 +1,34 @@
import OpenAI from 'openai';
import { OpenAIStream, StreamingTextResponse } from 'ai';
import { env } from '$env/dynamic/private';
// You may want to replace the above with a static private env variable
// for dead-code elimination and build-time type-checking:
// import { OPENAI_API_KEY } from '$env/static/private'
import type { RequestHandler } from '../../server/api/chat/$types';
// Create an OpenAI API client
const openai = new OpenAI({
apiKey: env.OPENAI_API_KEY || '',
});
export const POST = (async ({ request }) => {
// Extract the `prompt` from the body of the request
const { messages } = await request.json();
// Ask OpenAI for a streaming chat completion given the prompt
const response = await openai.chat.completions.create({
model: 'gpt-3.5-turbo',
stream: true,
messages: messages.map((message: any) => ({
content: message.content,
role: message.role,
})),
});
// Convert the response into a friendly text-stream
const stream = OpenAIStream(response);
// Respond with the stream
return new StreamingTextResponse(stream);
}) satisfies RequestHandler;