update ai stuff

This commit is contained in:
overcuriousity
2025-07-16 21:14:50 +02:00
parent 073f52bada
commit 89f45b85be
6 changed files with 1014 additions and 18 deletions

View File

@@ -5,10 +5,8 @@ import { promises as fs } from 'fs';
import { load } from 'js-yaml';
import path from 'path';
export const prerender = false;
function getEnv(key: string): string {
const value = process.env[key];
if (!value) {
@@ -211,7 +209,7 @@ export const POST: APIRoute = async ({ request }) => {
'Authorization': `Bearer ${process.env.AI_API_KEY}`
},
body: JSON.stringify({
model: AI_MODEL, // or whatever model is available
model: 'gpt-4o-mini', // or whatever model is available
messages: [
{
role: 'system',
@@ -248,8 +246,7 @@ export const POST: APIRoute = async ({ request }) => {
// Parse AI JSON response
let recommendation;
try {
const cleanedContent = stripMarkdownJson(aiContent);
recommendation = JSON.parse(cleanedContent);
recommendation = JSON.parse(aiContent);
} catch (error) {
console.error('Failed to parse AI response:', aiContent);
return new Response(JSON.stringify({ error: 'Invalid AI response format' }), {

View File

@@ -1,13 +1,31 @@
// src/pages/api/auth/status.ts
import type { APIRoute } from 'astro';
import { getSessionFromRequest, verifySession } from '../../../utils/auth.js';
export const prerender = false;
export const GET: APIRoute = async ({ request }) => {
try {
// Check if authentication is required
const authRequired = process.env.AUTHENTICATION_NECESSARY !== 'false';
if (!authRequired) {
// If authentication is not required, always return authenticated
return new Response(JSON.stringify({
authenticated: true,
authRequired: false
}), {
status: 200,
headers: { 'Content-Type': 'application/json' }
});
}
const sessionToken = getSessionFromRequest(request);
if (!sessionToken) {
return new Response(JSON.stringify({
authenticated: false
authenticated: false,
authRequired: true
}), {
status: 200,
headers: { 'Content-Type': 'application/json' }
@@ -18,6 +36,7 @@ export const GET: APIRoute = async ({ request }) => {
return new Response(JSON.stringify({
authenticated: session !== null,
authRequired: true,
expires: session?.exp ? new Date(session.exp * 1000).toISOString() : null
}), {
status: 200,
@@ -27,6 +46,7 @@ export const GET: APIRoute = async ({ request }) => {
} catch (error) {
return new Response(JSON.stringify({
authenticated: false,
authRequired: process.env.AUTHENTICATION_NECESSARY !== 'false',
error: 'Session verification failed'
}), {
status: 200,

View File

@@ -3,6 +3,7 @@ import BaseLayout from '../layouts/BaseLayout.astro';
import ToolCard from '../components/ToolCard.astro';
import ToolFilters from '../components/ToolFilters.astro';
import ToolMatrix from '../components/ToolMatrix.astro';
import AIQueryInterface from '../components/AIQueryInterface.astro';
import { promises as fs } from 'fs';
import { load } from 'js-yaml';
import path from 'path';
@@ -45,6 +46,16 @@ const tools = data.tools;
</svg>
SSO & Zugang erfahren
</a>
<!-- New AI Query Button -->
<button id="ai-query-btn" class="btn btn-accent" style="padding: 0.75rem 1.5rem; background-color: var(--color-accent); color: white;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem;">
<path d="M9 11H5a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7a2 2 0 0 0-2-2h-4"/>
<path d="M9 11V7a3 3 0 0 1 6 0v4"/>
</svg>
KI befragen
</button>
<a href="#filters-section" class="btn btn-secondary" style="padding: 0.75rem 1.5rem;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem;">
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path>
@@ -61,6 +72,9 @@ const tools = data.tools;
<section id="filters-section" style="padding: 2rem 0;">
<ToolFilters />
</section>
<!-- AI Query Interface -->
<AIQueryInterface />
<!-- Tools Grid -->
<section id="tools-grid" style="padding-bottom: 2rem;">
@@ -86,10 +100,13 @@ const tools = data.tools;
const toolsContainer = document.getElementById('tools-container');
const toolsGrid = document.getElementById('tools-grid');
const matrixContainer = document.getElementById('matrix-container');
const aiInterface = document.getElementById('ai-interface');
const filtersSection = document.getElementById('filters-section');
const noResults = document.getElementById('no-results');
const aiQueryBtn = document.getElementById('ai-query-btn');
// Guard against null elements
if (!toolsContainer || !toolsGrid || !matrixContainer || !noResults) {
if (!toolsContainer || !toolsGrid || !matrixContainer || !noResults || !aiInterface || !filtersSection) {
console.error('Required DOM elements not found');
return;
}
@@ -97,14 +114,90 @@ const tools = data.tools;
// Initial tools HTML
const initialToolsHTML = toolsContainer.innerHTML;
// Authentication check function
async function checkAuthentication() {
try {
const response = await fetch('/api/auth/status');
const data = await response.json();
return data.authenticated;
} catch (error) {
console.error('Auth check failed:', error);
return false;
}
}
// AI Query Button Handler
if (aiQueryBtn) {
aiQueryBtn.addEventListener('click', async () => {
const isAuthenticated = await checkAuthentication();
if (!isAuthenticated) {
// Redirect to login, then back to AI view
const returnUrl = `${window.location.pathname}?view=ai`;
window.location.href = `/api/auth/login?returnTo=${encodeURIComponent(returnUrl)}`;
} else {
// Switch to AI view
switchToView('ai');
}
});
}
// Check URL parameters on page load for view switching
const urlParams = new URLSearchParams(window.location.search);
const viewParam = urlParams.get('view');
if (viewParam === 'ai') {
// User was redirected after authentication, switch to AI view
switchToView('ai');
}
// Function to switch between different views
function switchToView(view) {
// Hide all views first (using non-null assertions since we've already checked)
toolsGrid!.style.display = 'none';
matrixContainer!.style.display = 'none';
aiInterface!.style.display = 'none';
filtersSection!.style.display = 'none';
// Update view toggle buttons
const viewToggles = document.querySelectorAll('.view-toggle');
viewToggles.forEach(btn => {
btn.classList.toggle('active', btn.getAttribute('data-view') === view);
});
// Show appropriate view
switch (view) {
case 'ai':
aiInterface!.style.display = 'block';
// Focus on the input
const aiInput = document.getElementById('ai-query-input');
if (aiInput) {
setTimeout(() => aiInput.focus(), 100);
}
break;
case 'matrix':
matrixContainer!.style.display = 'block';
filtersSection!.style.display = 'block';
break;
default: // grid
toolsGrid!.style.display = 'block';
filtersSection!.style.display = 'block';
break;
}
// Clear URL parameters after switching
if (window.location.search) {
window.history.replaceState({}, '', window.location.pathname);
}
}
// Handle filtered results
window.addEventListener('toolsFiltered', (event: Event) => {
const customEvent = event as CustomEvent;
const filtered = customEvent.detail;
const currentView = document.querySelector('.view-toggle.active')?.getAttribute('data-view');
if (currentView === 'matrix') {
// Matrix view handles its own rendering
if (currentView === 'matrix' || currentView === 'ai') {
// Matrix and AI views handle their own rendering
return;
}
@@ -128,16 +221,12 @@ const tools = data.tools;
window.addEventListener('viewChanged', (event: Event) => {
const customEvent = event as CustomEvent;
const view = customEvent.detail;
if (view === 'matrix') {
toolsGrid.style.display = 'none';
matrixContainer.style.display = 'block';
} else {
toolsGrid.style.display = 'block';
matrixContainer.style.display = 'none';
}
switchToView(view);
});
// Make switchToView available globally for the AI button
(window as any).switchToAIView = () => switchToView('ai');
function createToolCard(tool) {
const hasValidProjectUrl = tool.projectUrl !== undefined &&
tool.projectUrl !== null &&