Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions services/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"drizzle-orm": "^0.30.10",
"hono": "^4.6.10",
"jose": "^5.9.6",
"jspdf": "^2.5.2",
"ky": "^1.7.2",
"openai": "^4.72.0",
"p-limit": "^6.1.0",
Expand Down
303 changes: 271 additions & 32 deletions services/api/src/core/evaluation.ts

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions services/api/src/core/general.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ generalRouter.post('/configs', async (c: Context) => {
reviews: metadata.reviews || "",
questionAnswerPairs: questionAnswerPairs,
fileContents: fileContent,
model: metadata.model,
};

const result = await db.insert(configs).values(config).returning({
Expand Down
21 changes: 15 additions & 6 deletions services/api/src/core/process.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { NeonHttpDatabase } from "drizzle-orm/neon-http";
import OpenAI from "openai";
import { generateResponse, generateReport } from "../utils/openai"; // Ensure these functions are exported correctly
import { generateResponse, generateReport } from "../utils/openai";
import { evaluations } from "../db/schema";

export async function evaluateQuestion(
Expand Down Expand Up @@ -42,23 +42,32 @@ export async function evaluateQuestionBatch(
openai: OpenAI,
model: string,
questions: { content: string; answer: string }[]
): Promise<{ question: string; answer: string; generated: string }[]> {
): Promise<{ question: string; answer: string; generated: string; evaluationId: number }[]> {
const results = await Promise.all(
questions.map(async (question) => {
const result = await openai.chat.completions.create({
model: model,
messages: [{ role: "user", content: question.content }],
max_tokens: 150,
});

const generatedText = result.choices[0]?.message?.content?.trim() || ""; // Ensures fallback to an empty string if content is null or undefined

const generatedText = result.choices[0]?.message?.content?.trim() || "";

const newEvaluation = await db.insert(evaluations).values({
model,
question: question.content,
answer: question.answer,
output: generatedText,
createdAt: new Date(),
}).returning();

return {
question: question.content,
answer: question.answer,
generated: generatedText
generated: generatedText,
evaluationId: newEvaluation[0].id,
};
})
);
return results;
}
}
8 changes: 6 additions & 2 deletions services/api/src/db/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,21 @@ export const configs = pgTable('configs', {
submittedBy: json('submitted_by').notNull(),
questionAnswerPairs: json('questionAnswerPairs').notNull().default([]),
fileContents: text('file_contents').notNull().default('No file uploaded'),
averageScore: text('average_score').default('0'),
averageScore: text('average_score').default('0'),
model: text('model').notNull().default('unknown'),
});


export const models = pgTable('models', {
id: serial('id').primaryKey(),
name: text('name').notNull().unique(),
company: text('company'),
description: text('description'),
image: text('image'),
tags: json('tags'),
createdAt: timestamp('created_at').defaultNow()
createdAt: timestamp('created_at').defaultNow(),
averageScore: text('average_score').default('0'),

});

export const evaluations = pgTable('evaluations', {
Expand Down
2 changes: 2 additions & 0 deletions services/api/src/utils/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type Config = {
reviews?: string;
questionAnswerPairs: QAPair[];
fileContents: string;
model: string;
}

export async function createConfig(db: NeonHttpDatabase, config: Config) {
Expand All @@ -33,6 +34,7 @@ export async function createConfig(db: NeonHttpDatabase, config: Config) {
rating: config.rating || 0,
questionAnswerPairs: config.questionAnswerPairs.length ? config.questionAnswerPairs : [],
fileContents: config.fileContents ?? "No file uploaded",
model: config.model,
});

}
Expand Down
1 change: 0 additions & 1 deletion services/api/src/utils/openai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ export async function generateReport(
});

const messageContent = response.choices[0]?.message?.content?.trim() ?? "{}";
console.log(messageContent);
const { choice, score } = JSON.parse(messageContent);
console.log(choice, score);
return { choice, score };
Expand Down
164 changes: 164 additions & 0 deletions services/app/components/ModelScoresGraph.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
<template>
<div ref="chartContainer" class="chart-container"></div>
</template>

<script>
import { defineComponent, ref, onMounted, nextTick, onBeforeUnmount } from 'vue';
import ky from 'ky';
import uPlot from 'uplot';
import 'uplot/dist/uPlot.min.css';

export default defineComponent({
name: 'ModelScoresGraph',
setup() {
const chartContainer = ref(null);
const labels = ref([]);
const scores = ref([]);
let uplotInstance = null;
let tooltip = null;

const createTooltip = () => {
tooltip = document.createElement('div');
tooltip.className = 'uplot-tooltip';
chartContainer.value.appendChild(tooltip);
};

const renderChart = () => {
if (typeof window === "undefined") {
console.warn("RenderChart skipped during SSR");
return;
}

if (!chartContainer.value || !labels.value.length || !scores.value.length) return;

const xAxisIndices = labels.value.map((_, idx) => idx);
const chartData = [xAxisIndices, [...scores.value]];

if (uplotInstance) {
uplotInstance.destroy();
}

if (!tooltip) {
createTooltip();
}

const tooltipPlugin = {
hooks: {
setCursor: (u) => {
const { left, top, idx } = u.cursor;
if (idx !== null) {
tooltip.style.display = 'block';
tooltip.style.left = `${left}px`;
tooltip.style.top = `${top - 40}px`;
tooltip.textContent = `${labels.value[idx]}: ${scores.value[idx].toFixed(2)}`;
} else {
tooltip.style.display = 'none';
}
},
},
};

try {
uplotInstance = new uPlot(
{
title: 'Average Scores of Models',
width: chartContainer.value.offsetWidth,
height: 400,
scales: {
x: { time: false },
y: { auto: true },
},
axes: [
{
label: 'Models',
values: (_, ticks) => ticks.map((tick) => labels.value[tick] || ""),
},
{
label: 'Average Score',
},
],
series: [
{},
{
label: 'Average Score',
stroke: '#1e90ff',
fill: 'rgba(30, 144, 255, 0.3)',
bars: { show: true, width: 0.6 },
points: { show: true, size: 5 },
},
],
grid: {
x: { show: true, stroke: '#ddd', width: 1 },
y: { show: true, stroke: '#ddd', width: 1 },
},
cursor: {
show: true,
x: true,
y: true,
lock: false,
},
plugins: [tooltipPlugin],
},
chartData,
chartContainer.value
);
} catch (error) {
console.error('Error rendering chart:', error);
}
};

const fetchData = async () => {
try {
const response = await ky.get('http://localhost:8787/models').json();
labels.value = response.map((model) => model.name);
scores.value = response.map((model) => parseFloat(model.averageScore));
nextTick(() => renderChart());
} catch (error) {
console.error('Error fetching data:', error);
}
};

onMounted(() => {
if (typeof window !== "undefined") {
fetchData();
window.addEventListener("resize", renderChart);
}
});

onBeforeUnmount(() => {
if (uplotInstance) {
uplotInstance.destroy();
}
window.removeEventListener("resize", renderChart);
if (tooltip) {
tooltip.remove();
tooltip = null;
}
});

return {
chartContainer,
};
},
});
</script>

<style scoped>
.chart-container {
width: 100%;
height: 400px;
position: relative;
}

.uplot-tooltip {
position: absolute;
background: rgba(0, 0, 0, 0.7);
color: #fff;
padding: 5px;
border-radius: 3px;
pointer-events: none;
font-size: 12px;
white-space: nowrap;
display: none;
}
</style>
Loading