michaelkri commited on
Commit
74e7eda
·
1 Parent(s): 6d34b7a

Article photos

Browse files
app/database.py CHANGED
@@ -19,6 +19,7 @@ class Article(Base):
19
  content: Mapped[str] = mapped_column(UnicodeText)
20
  date: Mapped[datetime.datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
21
  category: Mapped[str] = mapped_column(UnicodeText, server_default='news')
 
22
 
23
  sources = relationship('Source', backref='article')
24
 
 
19
  content: Mapped[str] = mapped_column(UnicodeText)
20
  date: Mapped[datetime.datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
21
  category: Mapped[str] = mapped_column(UnicodeText, server_default='news')
22
+ image_url: Mapped[str] = mapped_column(UnicodeText, nullable=True, server_default=None)
23
 
24
  sources = relationship('Source', backref='article')
25
 
app/news_fetcher.py CHANGED
@@ -8,15 +8,16 @@ from datetime import datetime
8
 
9
  def topic_summary(summarizer: Summarizer, query: str, max_results: int = 5, min_cluster_size: int = 2) -> str:
10
  logging.debug(f'Beginning search for \'{query}\'...')
11
- articles_and_urls = get_articles(query, max_results=max_results)
12
- logging.debug(f'Retrieved {len(articles_and_urls)} articles')
13
-
14
- articles = [item[0] for item in articles_and_urls]
15
- urls = [item[1] for item in articles_and_urls]
16
 
 
17
  summary = summarizer.multisource_summary(articles, min_cluster_size=min_cluster_size)
18
 
19
- return summary, urls
 
 
 
20
 
21
 
22
  def fetch_headlines(summarizer: Summarizer, rss_feed_urls: list[str], max_articles=50) -> list[str]:
@@ -49,7 +50,7 @@ def news_summary(summarizer: Summarizer, rss_feed_urls: list[str]):
49
 
50
  # summarize each topic and yield articles
51
  for headline in headlines:
52
- summary, source_urls = topic_summary(summarizer, headline)
53
 
54
  # no articles were found
55
  if not summary:
@@ -62,7 +63,8 @@ def news_summary(summarizer: Summarizer, rss_feed_urls: list[str]):
62
  title=headline,
63
  content=summary,
64
  date=datetime.now(),
65
- category=category
 
66
  )
67
 
68
  sources = [Source(url=s_url, article_id=article.id) for s_url in source_urls]
 
8
 
9
  def topic_summary(summarizer: Summarizer, query: str, max_results: int = 5, min_cluster_size: int = 2) -> str:
10
  logging.debug(f'Beginning search for \'{query}\'...')
11
+ articles, urls, images = get_articles(query, max_results=max_results)
12
+ logging.debug(f'Retrieved {len(urls)} articles')
 
 
 
13
 
14
+ # create summary of all articles
15
  summary = summarizer.multisource_summary(articles, min_cluster_size=min_cluster_size)
16
 
17
+ # grab an arbitrary image for article
18
+ image = next((image for image in images if image is not None), None)
19
+
20
+ return summary, urls, image
21
 
22
 
23
  def fetch_headlines(summarizer: Summarizer, rss_feed_urls: list[str], max_articles=50) -> list[str]:
 
50
 
51
  # summarize each topic and yield articles
52
  for headline in headlines:
53
+ summary, source_urls, article_image = topic_summary(summarizer, headline)
54
 
55
  # no articles were found
56
  if not summary:
 
63
  title=headline,
64
  content=summary,
65
  date=datetime.now(),
66
+ category=category,
67
+ image_url=article_image
68
  )
69
 
70
  sources = [Source(url=s_url, article_id=article.id) for s_url in source_urls]
app/scraper.py CHANGED
@@ -25,7 +25,19 @@ def get_articles(query, max_results=5, min_article_length=100) -> list[tuple[str
25
  # get article urls
26
  urls = set([r['url'] for r in search_results])
27
 
28
- # try to retrieve articles (filter inaccessible and short articles)
29
- articles = [(article, url) for url in urls if (article := retrieve_article(url)) is not None and len(article) > min_article_length]
 
 
 
 
 
 
30
 
31
- return articles
 
 
 
 
 
 
 
25
  # get article urls
26
  urls = set([r['url'] for r in search_results])
27
 
28
+ # try to retrieve articles
29
+ texts = []
30
+ urls = []
31
+ images = []
32
+ for result in search_results:
33
+ article_url = result['url']
34
+ article_image = result['image']
35
+ article_text = retrieve_article(article_url)
36
 
37
+ # filter short and inaccessible articles
38
+ if article_text and len(article_text) > min_article_length:
39
+ texts.append(article_text)
40
+ urls.append(article_url)
41
+ images.append(article_image)
42
+
43
+ return texts, urls, images
app/static/style.css CHANGED
@@ -1,4 +1,3 @@
1
- /* Focal Stylesheet */
2
  /*! tailwindcss v4.1.13 | MIT License | https://tailwindcss.com */
3
  @layer properties;
4
  @layer theme, base, components, utilities;
@@ -317,6 +316,9 @@
317
  .block {
318
  display: block;
319
  }
 
 
 
320
  .flex {
321
  display: flex;
322
  }
@@ -344,6 +346,9 @@
344
  .h-8 {
345
  height: calc(var(--spacing) * 8);
346
  }
 
 
 
347
  .h-full {
348
  height: 100%;
349
  }
@@ -535,6 +540,9 @@
535
  .bg-white {
536
  background-color: var(--color-white);
537
  }
 
 
 
538
  .p-2 {
539
  padding: calc(var(--spacing) * 2);
540
  }
@@ -691,6 +699,9 @@
691
  --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
692
  box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
693
  }
 
 
 
694
  .transition {
695
  transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter, display, content-visibility, overlay, pointer-events;
696
  transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
@@ -970,6 +981,59 @@
970
  inherits: false;
971
  initial-value: 0 0 #0000;
972
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
973
  @property --tw-duration {
974
  syntax: "*";
975
  inherits: false;
@@ -1011,6 +1075,19 @@
1011
  --tw-ring-offset-width: 0px;
1012
  --tw-ring-offset-color: #fff;
1013
  --tw-ring-offset-shadow: 0 0 #0000;
 
 
 
 
 
 
 
 
 
 
 
 
 
1014
  --tw-duration: initial;
1015
  }
1016
  }
 
 
1
  /*! tailwindcss v4.1.13 | MIT License | https://tailwindcss.com */
2
  @layer properties;
3
  @layer theme, base, components, utilities;
 
316
  .block {
317
  display: block;
318
  }
319
+ .contents {
320
+ display: contents;
321
+ }
322
  .flex {
323
  display: flex;
324
  }
 
346
  .h-8 {
347
  height: calc(var(--spacing) * 8);
348
  }
349
+ .h-48 {
350
+ height: calc(var(--spacing) * 48);
351
+ }
352
  .h-full {
353
  height: 100%;
354
  }
 
540
  .bg-white {
541
  background-color: var(--color-white);
542
  }
543
+ .object-cover {
544
+ object-fit: cover;
545
+ }
546
  .p-2 {
547
  padding: calc(var(--spacing) * 2);
548
  }
 
699
  --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
700
  box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
701
  }
702
+ .filter {
703
+ filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
704
+ }
705
  .transition {
706
  transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter, display, content-visibility, overlay, pointer-events;
707
  transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
 
981
  inherits: false;
982
  initial-value: 0 0 #0000;
983
  }
984
+ @property --tw-blur {
985
+ syntax: "*";
986
+ inherits: false;
987
+ }
988
+ @property --tw-brightness {
989
+ syntax: "*";
990
+ inherits: false;
991
+ }
992
+ @property --tw-contrast {
993
+ syntax: "*";
994
+ inherits: false;
995
+ }
996
+ @property --tw-grayscale {
997
+ syntax: "*";
998
+ inherits: false;
999
+ }
1000
+ @property --tw-hue-rotate {
1001
+ syntax: "*";
1002
+ inherits: false;
1003
+ }
1004
+ @property --tw-invert {
1005
+ syntax: "*";
1006
+ inherits: false;
1007
+ }
1008
+ @property --tw-opacity {
1009
+ syntax: "*";
1010
+ inherits: false;
1011
+ }
1012
+ @property --tw-saturate {
1013
+ syntax: "*";
1014
+ inherits: false;
1015
+ }
1016
+ @property --tw-sepia {
1017
+ syntax: "*";
1018
+ inherits: false;
1019
+ }
1020
+ @property --tw-drop-shadow {
1021
+ syntax: "*";
1022
+ inherits: false;
1023
+ }
1024
+ @property --tw-drop-shadow-color {
1025
+ syntax: "*";
1026
+ inherits: false;
1027
+ }
1028
+ @property --tw-drop-shadow-alpha {
1029
+ syntax: "<percentage>";
1030
+ inherits: false;
1031
+ initial-value: 100%;
1032
+ }
1033
+ @property --tw-drop-shadow-size {
1034
+ syntax: "*";
1035
+ inherits: false;
1036
+ }
1037
  @property --tw-duration {
1038
  syntax: "*";
1039
  inherits: false;
 
1075
  --tw-ring-offset-width: 0px;
1076
  --tw-ring-offset-color: #fff;
1077
  --tw-ring-offset-shadow: 0 0 #0000;
1078
+ --tw-blur: initial;
1079
+ --tw-brightness: initial;
1080
+ --tw-contrast: initial;
1081
+ --tw-grayscale: initial;
1082
+ --tw-hue-rotate: initial;
1083
+ --tw-invert: initial;
1084
+ --tw-opacity: initial;
1085
+ --tw-saturate: initial;
1086
+ --tw-sepia: initial;
1087
+ --tw-drop-shadow: initial;
1088
+ --tw-drop-shadow-color: initial;
1089
+ --tw-drop-shadow-alpha: 100%;
1090
+ --tw-drop-shadow-size: initial;
1091
  --tw-duration: initial;
1092
  }
1093
  }
app/templates/index.html CHANGED
@@ -5,7 +5,7 @@
5
  <meta charset="UTF-8">
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
  <title>Focal</title>
8
- <link rel="stylesheet" type="text/css" href="static/style.css">
9
  <style>
10
  .collapsible-content {
11
  max-height: 0;
@@ -123,6 +123,9 @@
123
  <hr class="h-px my-8 bg-gray-200 border-0 dark:bg-gray-700">
124
  <span class="text-sm text-gray-500">{{ article.date }}</span>
125
  <h2 class="mb-4 mt-4 text-xl font-semibold tracking-tight text-gray-900">{{ article.title }}</h2>
 
 
 
126
  <p class="text-base mb-4">
127
  {{ article.content }}
128
  </p>
 
5
  <meta charset="UTF-8">
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
  <title>Focal</title>
8
+ <link rel="stylesheet" type="text/css" href="../static/style.css">
9
  <style>
10
  .collapsible-content {
11
  max-height: 0;
 
123
  <hr class="h-px my-8 bg-gray-200 border-0 dark:bg-gray-700">
124
  <span class="text-sm text-gray-500">{{ article.date }}</span>
125
  <h2 class="mb-4 mt-4 text-xl font-semibold tracking-tight text-gray-900">{{ article.title }}</h2>
126
+ {% if article.image_url %}
127
+ <img class="w-full h-48 object-cover rounded-lg mb-4 mt-4" src="{{ article.image_url }}" />
128
+ {% endif %}
129
  <p class="text-base mb-4">
130
  {{ article.content }}
131
  </p>