ai impl
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
import { Separator } from '$lib/components/ui/separator';
|
||||
import TrashIcon from '@lucide/svelte/icons/trash';
|
||||
import ImageUpload from '$lib/components/custom/image-upload/image-upload.svelte';
|
||||
import { genDescription } from '$lib/db/items.remote';
|
||||
import { approveDenyItem, genDescription } from '$lib/db/items.remote';
|
||||
import { EMAIL_REGEX_STRING } from '$lib/consts';
|
||||
import type { Item } from '$lib/types/item.server';
|
||||
import { Textarea } from '$lib/components/ui/textarea';
|
||||
@@ -26,9 +26,9 @@
|
||||
let description: string = $derived(item ? item.description : '');
|
||||
let isGenerating = $state(false);
|
||||
|
||||
async function onSelect() {
|
||||
async function onSelect(file: File) {
|
||||
isGenerating = true;
|
||||
description = await genDescription();
|
||||
description = await genDescription(file);
|
||||
isGenerating = false;
|
||||
}
|
||||
|
||||
@@ -110,13 +110,23 @@
|
||||
</Field.Group>
|
||||
|
||||
<Dialog.Footer class="mt-4">
|
||||
<Dialog.Close
|
||||
type="button"
|
||||
class={buttonVariants({ variant: "outline" })}
|
||||
>
|
||||
Cancel
|
||||
</Dialog.Close>
|
||||
<Button type="submit">Submit</Button>
|
||||
<div class="flex justify-between w-full">
|
||||
<Button variant="destructive"
|
||||
onclick={() => {approveDenyItem({id: item?.id || 0, approved: false}); invalidateAll();}}>
|
||||
Delete
|
||||
</Button>
|
||||
|
||||
<div>
|
||||
|
||||
<Dialog.Close
|
||||
type="button"
|
||||
class={buttonVariants({ variant: "outline" })}
|
||||
>
|
||||
Cancel
|
||||
</Dialog.Close>
|
||||
<Button type="submit" class="ml-1">Submit</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Footer>
|
||||
</form>
|
||||
{#if item?.threads}
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
export let previewUrl: string | null = null;
|
||||
let dragging = false;
|
||||
|
||||
console.log(previewUrl);
|
||||
|
||||
function openFileDialog() {
|
||||
if (!disabled) {
|
||||
@@ -69,6 +68,7 @@
|
||||
disabled={disabled}
|
||||
capture="environment"
|
||||
on:change={onInputChange}
|
||||
formaction="/item?/describe"
|
||||
hidden
|
||||
/>
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="h-full bg-card text-card-foreground flex flex-col gap-2 rounded-xl border shadow-sm max-w-sm overflow-hidden min-2-3xs">
|
||||
class="h-full bg-card text-card-foreground flex flex-col gap-2 rounded-xl border shadow-sm max-w-sm overflow-hidden min-w-2xs">
|
||||
{#if item.image}
|
||||
<img src="https://fbla26.marinodev.com/uploads/{item.id}.jpg" class="object-cover min-h-48 max-h-48"
|
||||
alt="Lost item">
|
||||
|
||||
@@ -16,9 +16,15 @@
|
||||
let description: string | undefined = $state();
|
||||
let isGenerating = $state(false);
|
||||
|
||||
async function onSelect() {
|
||||
async function onSelect(file: File) {
|
||||
isGenerating = true;
|
||||
description = await genDescription();
|
||||
|
||||
const buffer = await file.arrayBuffer();
|
||||
const base64 = btoa(
|
||||
String.fromCharCode(...new Uint8Array(buffer))
|
||||
);
|
||||
|
||||
description = await genDescription(base64);
|
||||
isGenerating = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,31 @@
|
||||
import { command, getRequestEvent, query } from '$app/server';
|
||||
import { command, getRequestEvent } from '$app/server';
|
||||
import * as v from 'valibot';
|
||||
import sql from '$lib/db/db.server';
|
||||
import { verifyJWT } from '$lib/auth/index.server';
|
||||
import sharp from 'sharp';
|
||||
import { LLMDescribe } from '$lib/llm/llm.server';
|
||||
|
||||
export const genDescription = query(async () => {
|
||||
await new Promise((f) => setTimeout(f, 1000));
|
||||
export const genDescription = command(v.string(), async (data) => {
|
||||
const inputBuffer = Buffer.from(data, 'base64');
|
||||
|
||||
return 'A matte black water bottle with a black lid and a "BKLYN BENTO" logo on the side, resting on a tree trunk in a forest.';
|
||||
// Convert File → Buffer
|
||||
// const inputBuffer = Buffer.from(await file.arrayBuffer());
|
||||
|
||||
// Detect format (Sharp does this internally)
|
||||
const image = sharp(inputBuffer).rotate();
|
||||
|
||||
const outputBuffer = await image
|
||||
.rotate()
|
||||
.resize({ fit: 'outside', height: 400, width: 400 })
|
||||
.jpeg({ quality: 80 }) // adjust if needed
|
||||
.toBuffer();
|
||||
|
||||
const description = await LLMDescribe(
|
||||
`data:image/jpeg;base64,${outputBuffer.toString('base64')}`
|
||||
);
|
||||
console.log(description);
|
||||
|
||||
return description;
|
||||
});
|
||||
|
||||
export const approveDenyItem = command(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import nodemailer from 'nodemailer';
|
||||
|
||||
// Create a transporter object using SMTP transport
|
||||
const transporter = nodemailer.createTransport({
|
||||
export const transporter = nodemailer.createTransport({
|
||||
host: process.env.EMAIL_HOST,
|
||||
port: Number(process.env.EMAIL_PORT),
|
||||
secure: true, // true for 465, false for other ports
|
||||
@@ -11,13 +11,13 @@ const transporter = nodemailer.createTransport({
|
||||
}
|
||||
});
|
||||
|
||||
export async function sendEmployerNotificationEmail() {
|
||||
// Send mail with defined transport object
|
||||
await transporter.sendMail({
|
||||
from: `CareerConnect Notifications <${process.env.EMAIL_USER}>`,
|
||||
// to: info.emails.join(', '), // EMAILING OF REAL COMPANIES DISABLED, UNCOMMENT TO ENABLE
|
||||
to: 'drake@marinodev.com', // TEMPORARY EMAIL FOR TESTING
|
||||
subject: 'New Application Received!',
|
||||
text: `A new application has been received for the posting ''!\n\nCheck it out at ${process.env.BASE_URL}/`
|
||||
});
|
||||
}
|
||||
// export async function sendEmail() {
|
||||
// // Send mail with defined transport object
|
||||
// await transporter.sendMail({
|
||||
// from: `Westuffind Notifier <${process.env.EMAIL_USER}>`,
|
||||
// // to: info.emails.join(', '), // EMAILING OF REAL COMPANIES DISABLED, UNCOMMENT TO ENABLE
|
||||
// to: 'drake@marinodev.com', // TEMPORARY EMAIL FOR TESTING
|
||||
// subject: 'New Application Received!',
|
||||
// text: `A new application has been received for the posting ''!\n\nCheck it out at ${process.env.BASE_URL}/`
|
||||
// });
|
||||
// }
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
export async function LLMDescribe(imageData: string) {
|
||||
const payload = {
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Describe only the main object in this image in one sentence. Include its color, shape, brand, or any text on it. Focus solely on the object itself. Example: A blue Hydroflask water bottle with a dented lid and a sticker of a mountain on the side.'
|
||||
},
|
||||
{
|
||||
type: 'image_url',
|
||||
image_url: {
|
||||
url: imageData
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
max_tokens: 128,
|
||||
temperature: 0.2
|
||||
};
|
||||
|
||||
console.log('AIing it');
|
||||
|
||||
const res = await fetch(
|
||||
`http://${process.env.LLAMA_HOST!}:${process.env.LLAMA_PORT!}/v1/chat/completions`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
}
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
console.error(await res.text());
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
return data.choices[0].message.content;
|
||||
}
|
||||
Reference in New Issue
Block a user