[update] added validation for article data in NewsList.vue, removed unused Cypress config, expanded LLM models in example.env, adjusted context size and max article length in backend configuration, and updated workspace naming in yarn.lock

This commit is contained in:
2025-08-07 13:15:40 +02:00
parent 338b3ac7c1
commit 0a97a57c76
7 changed files with 112 additions and 86 deletions

View File

@@ -8,11 +8,11 @@ MIN_CRON_HOURS = float(os.getenv("MIN_CRON_HOURS", 0.5))
DEFAULT_CRON_HOURS = float(os.getenv("CRON_HOURS", MIN_CRON_HOURS))
CRON_HOURS = max(MIN_CRON_HOURS, DEFAULT_CRON_HOURS)
SYNC_COOLDOWN_MINUTES = int(os.getenv("SYNC_COOLDOWN_MINUTES", 30))
LLM_MODEL = os.getenv("LLM_MODEL", "mistral-nemo:12b")
LLM_MODEL = os.getenv("LLM_MODEL", "phi3:3.8b-mini-128k-instruct-q4_0")
LLM_TIMEOUT_SECONDS = int(os.getenv("LLM_TIMEOUT_SECONDS", 180))
OLLAMA_API_TIMEOUT_SECONDS = int(os.getenv("OLLAMA_API_TIMEOUT_SECONDS", 10))
ARTICLE_FETCH_TIMEOUT = int(os.getenv("ARTICLE_FETCH_TIMEOUT", 30))
MAX_ARTICLE_LENGTH = int(os.getenv("MAX_ARTICLE_LENGTH", 10_000))
MAX_ARTICLE_LENGTH = int(os.getenv("MAX_ARTICLE_LENGTH", 40_000))
frontend_path = os.path.join(
os.path.dirname(os.path.dirname(os.path.dirname(__file__))),

View File

@@ -209,7 +209,7 @@ class NewsFetcher:
"format": "json",
"options": {
"num_gpu": 1, # Force GPU usage
"num_ctx": 128_000, # Context size
"num_ctx": 64_000, # Context size
}
}

View File

@@ -11,10 +11,11 @@ MIN_CRON_HOURS=0.5
SYNC_COOLDOWN_MINUTES=30
# LLM model to use for summarization
LLM_MODEL=qwen2:7b-instruct-q4_K_M
LLM_MODEL=phi3:3.8b-mini-128k-instruct-q4_0
LLM_MODEL=mistral-nemo:12b
LLM_MODEL=cnjack/mistral-samll-3.1:24b-it-q4_K_S
LLM_MODEL=qwen2:7b-instruct-q4_K_M # ca 7-9GB (typisch 8GB)
LLM_MODEL=phi3:3.8b-mini-128k-instruct-q4_0 # ca 6-8GB (langer kontext)
LLM_MODEL=mistral-nemo:12b # ca 16-24+GB
LLM_MODEL=cnjack/mistral-samll-3.1:24b-it-q4_K_S # ca 22GB
LLM_MODEL=yarn-mistral:7b-64k-q4_K_M # ca 11GB
# Timeout in seconds for LLM requests
LLM_TIMEOUT_SECONDS=180

Binary file not shown.

View File

@@ -1,8 +0,0 @@
import { defineConfig } from 'cypress'
export default defineConfig({
e2e: {
specPattern: 'cypress/e2e/**/*.{cy,spec}.{js,jsx,ts,tsx}',
baseUrl: 'http://localhost:4173',
},
})

View File

@@ -14,75 +14,77 @@
<!-- Articles Grid -->
<div v-else class="grid gap-4 sm:gap-6 md:grid-cols-2 xl:grid-cols-3">
<article
v-for="article in news.articles"
:key="article.id"
class="flex flex-col h-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 hover:shadow-md dark:hover:shadow-lg dark:hover:shadow-gray-800/50 transition-all duration-200 overflow-hidden group"
>
<!-- Article Header -->
<div class="flex-1 p-4 sm:p-6">
<div class="flex items-start justify-between mb-3">
<template v-for="article in news.articles"
:key="article.id">
<article
v-if="isValidArticleContent(article)"
class="flex flex-col h-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 hover:shadow-md dark:hover:shadow-lg dark:hover:shadow-gray-800/50 transition-all duration-200 overflow-hidden group"
>
<!-- Article Header -->
<div class="flex-1 p-4 sm:p-6">
<div class="flex items-start justify-between mb-3">
<span
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200">
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200">
{{ article.country }}
</span>
<time
:datetime="new Date(article.published * 1000).toISOString()"
:title="new Date(article.published * 1000).toLocaleString(userLocale.value, {
<time
:datetime="new Date(article.published * 1000).toISOString()"
:title="new Date(article.published * 1000).toLocaleString(userLocale, {
dateStyle: 'full',
timeStyle: 'long'
})"
class="text-xs text-gray-500 flex-shrink-0 ml-2 cursor-help hover:text-green-600 dark:hover:text-green-400 transition-colors relative group"
>
{{ formatDate(article.published) }}
</time>
class="text-xs text-gray-500 flex-shrink-0 ml-2 cursor-help hover:text-green-600 dark:hover:text-green-400 transition-colors relative group"
>
{{ formatDate(article.published) }}
</time>
</div>
<!-- Title -->
<h3
class="text-base sm:text-lg font-semibold text-gray-900 dark:text-white mb-3 line-clamp-2 group-hover:text-green-600 dark:group-hover:text-green-400 transition-colors">
<a :href="article.url" target="_blank" rel="noopener noreferrer">
{{ article.title }}
</a>
</h3>
<!-- Summary -->
<p
class="text-sm sm:text-base text-gray-700 dark:text-gray-300 line-clamp-5 leading-relaxed">
{{ article.summary }}
</p>
</div>
<!-- Title -->
<h3
class="text-base sm:text-lg font-semibold text-gray-900 dark:text-white mb-3 line-clamp-2 group-hover:text-green-600 dark:group-hover:text-green-400 transition-colors">
<a :href="article.url" target="_blank" rel="noopener noreferrer">
{{ article.title }}
<!-- Article Footer -->
<div
class="flex justify-between items-center gap-4 p-4 sm:p-6">
<button
@click="openModal(article)"
class="flex-1 inline-flex items-center justify-center cursor-pointer px-4 py-2 text-sm font-medium rounded-lg bg-green-100 text-green-700 hover:bg-green-200 dark:bg-green-900/30 dark:text-green-400 dark:hover:bg-green-900/50 transition-colors"
>
Full summary
<svg class="ml-1 w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
</svg>
</button>
<a
:href="article.url"
target="_blank"
rel="noopener noreferrer"
class="flex-1 inline-flex items-center justify-center px-4 py-2 text-sm font-medium rounded-lg bg-green-100 text-green-700 hover:bg-green-200 dark:bg-green-900/30 dark:text-green-400 dark:hover:bg-green-900/50 transition-colors"
>
Full article
<svg class="ml-1 w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/>
</svg>
</a>
</h3>
<!-- Summary -->
<p
class="text-sm sm:text-base text-gray-700 dark:text-gray-300 line-clamp-5 leading-relaxed">
{{ article.summary }}
</p>
</div>
<!-- Article Footer -->
<div
class="flex justify-between items-center gap-4 p-4 sm:p-6">
<button
@click="openModal(article)"
class="flex-1 inline-flex items-center justify-center cursor-pointer px-4 py-2 text-sm font-medium rounded-lg bg-green-100 text-green-700 hover:bg-green-200 dark:bg-green-900/30 dark:text-green-400 dark:hover:bg-green-900/50 transition-colors"
>
Full summary
<svg class="ml-1 w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
</svg>
</button>
<a
:href="article.url"
target="_blank"
rel="noopener noreferrer"
class="flex-1 inline-flex items-center justify-center px-4 py-2 text-sm font-medium rounded-lg bg-green-100 text-green-700 hover:bg-green-200 dark:bg-green-900/30 dark:text-green-400 dark:hover:bg-green-900/50 transition-colors"
>
Full article
<svg class="ml-1 w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/>
</svg>
</a>
</div>
</article>
</div>
</article>
</template>
</div>
<!-- Loading State & Load More Trigger -->
@@ -100,9 +102,9 @@
<!-- Article Modal -->
<ArticleModal
:is-open="isModalOpen"
:article="selectedArticle"
@close="closeModal"
:is-open="isModalOpen"
:article="selectedArticle"
@close="closeModal"
/>
</div>
</template>
@@ -129,17 +131,48 @@ const loadMoreArticles = async () => {
}
};
interface Article {
id: number;
title: string;
summary: string;
url: string;
published: number;
country: string;
created_at: number;
}
const INVALID_MARKERS = ['---', '...', '…', 'Title', 'Summary', 'Titel', 'Zusammenfassung'] as const;
const REQUIRED_TEXT_FIELDS = ['title', 'summary', 'url'] as const;
const isValidArticleContent = (article: Article): boolean => {
const hasEmptyRequiredFields = REQUIRED_TEXT_FIELDS.some(
field => article[field as keyof Pick<Article, typeof REQUIRED_TEXT_FIELDS[number]>].length === 0
);
if (hasEmptyRequiredFields) {
return false;
}
const hasInvalidMarkers = REQUIRED_TEXT_FIELDS.some(field =>
INVALID_MARKERS.some(marker =>
article[field as keyof Pick<Article, typeof REQUIRED_TEXT_FIELDS[number]>].includes(marker)
)
);
return !hasInvalidMarkers;
};
const observer = ref<IntersectionObserver | null>(null);
const loadMoreTrigger = ref<HTMLElement | null>(null);
onMounted(() => {
observer.value = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting) {
loadMoreArticles();
}
},
{threshold: 0.5}
(entries) => {
if (entries[0].isIntersecting) {
loadMoreArticles();
}
},
{threshold: 0.5}
);
if (loadMoreTrigger.value) {

View File

@@ -6470,9 +6470,9 @@ __metadata:
languageName: node
linkType: hard
"owly-news-summariser@workspace:.":
"owly-news@workspace:.":
version: 0.0.0-use.local
resolution: "owly-news-summariser@workspace:."
resolution: "owly-news@workspace:."
dependencies:
"@tailwindcss/vite": "npm:^4.1.11"
"@tsconfig/node22": "npm:^22.0.2"