rsm-roguchi commited on
Commit
a36cd3b
·
1 Parent(s): ca5301a

pytrends working

Browse files
Files changed (6) hide show
  1. app.py +4 -1
  2. pyproject.toml +1 -0
  3. server/blog.py +72 -19
  4. ui/blog.py +6 -1
  5. uv.lock +52 -0
  6. www/style.css +26 -0
app.py CHANGED
@@ -14,7 +14,10 @@ from server import (
14
  ui = ui.page_fluid(
15
  ui.page_navbar(
16
  blog.ui,
17
- title="SEO Blog Writer"
 
 
 
18
  )
19
  )
20
 
 
14
  ui = ui.page_fluid(
15
  ui.page_navbar(
16
  blog.ui,
17
+ title="SEO Blog Writer",
18
+ header=ui.tags.head(
19
+ ui.tags.link(rel='stylesheet', type='text/css', href='style.css')
20
+ )
21
  )
22
  )
23
 
pyproject.toml CHANGED
@@ -18,5 +18,6 @@ dependencies = [
18
  "pydantic-ai>=0.3.5",
19
  "pyrsm>=1.6.0",
20
  "python-dotenv>=1.1.1",
 
21
  "requests>=2.32.4",
22
  ]
 
18
  "pydantic-ai>=0.3.5",
19
  "pyrsm>=1.6.0",
20
  "python-dotenv>=1.1.1",
21
+ "pytrends>=4.9.2",
22
  "requests>=2.32.4",
23
  ]
server/blog.py CHANGED
@@ -1,28 +1,81 @@
1
- from shiny import reactive, render
 
 
 
 
 
 
 
2
  from llm_connect import get_response
3
 
4
- def draft_blog(topic):
5
- prompt = (
6
- f"You are a blog writer for a hobby company called 'Ultima Supply'.\n"
7
- f"Write me a SEO optimized blog with a title and subsections for the following topic in great detail:\n"
8
- f"{topic}\n"
9
- f"Make sure the blog that you create has SEO keywords throughout so it can be found via search engines."
10
- )
 
 
 
 
11
 
12
- return get_response(
13
- input=prompt,
14
- template=lambda x: x,
15
- llm='llama',
16
- md=False,
17
- temperature=0.8,
18
- max_tokens=1000
 
 
19
  )
20
 
 
 
21
  def server(input, output, session):
 
 
 
22
  @output
23
- @render.text
24
- def blog_output():
 
25
  topic = input.topic()
 
 
 
 
26
  if not topic:
27
- return "Enter a topic to generate a blog."
28
- return draft_blog(topic)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from shiny import reactive, render, ui
2
+ import sys, os
3
+ from dotenv import load_dotenv
4
+ from pytrends.request import TrendReq
5
+
6
+ load_dotenv()
7
+
8
+ sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "code")))
9
  from llm_connect import get_response
10
 
11
+ import warnings
12
+ warnings.filterwarnings("ignore", category=FutureWarning)
13
+
14
+
15
+ def get_related_keywords(topic: str, region="US", top_n=5) -> list:
16
+ pytrends = TrendReq(hl="en-US", tz=360)
17
+ suggestions = pytrends.suggestions(keyword=topic)
18
+ keywords = [s["title"] for s in suggestions if topic.lower() not in s["title"].lower()]
19
+
20
+ if not keywords:
21
+ return []
22
 
23
+ pytrends.build_payload(keywords[:10], timeframe='now 7-d', geo=region)
24
+ df = pytrends.interest_over_time()
25
+
26
+ top_keywords = (
27
+ df.drop(columns=["isPartial"])
28
+ .mean()
29
+ .sort_values(ascending=False)
30
+ .head(top_n)
31
+ .index.tolist()
32
  )
33
 
34
+ return top_keywords
35
+
36
  def server(input, output, session):
37
+ # Reactive store for extracted keywords
38
+ related_keywords = reactive.Value([])
39
+
40
  @output
41
+ @render.ui
42
+ @reactive.event(input.generate_btn)
43
+ def blog_result():
44
  topic = input.topic()
45
+
46
+ print("[DEBUG] Button clicked.")
47
+ print(f"[DEBUG] Topic: {topic}")
48
+
49
  if not topic:
50
+ return ui.HTML("<p><strong>⚠️ Please enter a topic before generating.</strong></p>")
51
+
52
+ # Get SEO keywords and store
53
+ keywords = get_related_keywords(topic)
54
+ related_keywords.set(keywords)
55
+
56
+ keyword_str = ", ".join(keywords) if keywords else topic
57
+
58
+ prompt = (
59
+ f"You are a blog writer for a hobby company called 'Ultima Supply'.\n"
60
+ f"Write an SEO-optimized blog in HTML format using the following keywords throughout: {keyword_str}.\n"
61
+ f"Make sure to use proper <h1>, <h2>, and <p> tags. Topic: {topic}"
62
+ )
63
+
64
+ blog_output = get_response(
65
+ input=prompt,
66
+ template=lambda x: x,
67
+ llm='llama',
68
+ md=False,
69
+ temperature=0.8,
70
+ max_tokens=1000
71
+ )
72
+
73
+ return ui.HTML(blog_output)
74
+
75
+ @output
76
+ @render.text
77
+ def keywords_used():
78
+ kws = related_keywords()
79
+ if not kws:
80
+ return "No SEO keywords retrieved yet."
81
+ return f"SEO Keywords Injected: {', '.join(kws)}"
ui/blog.py CHANGED
@@ -3,5 +3,10 @@ from shiny import ui
3
  ui = ui.nav_panel(
4
  "Blog Writer",
5
  ui.input_text("topic", "Enter Blog Topic", placeholder="e.g. Best One Piece Cards in 2025"),
6
- ui.output_text_verbatim("blog_output"),
 
 
 
 
 
7
  )
 
3
  ui = ui.nav_panel(
4
  "Blog Writer",
5
  ui.input_text("topic", "Enter Blog Topic", placeholder="e.g. Best One Piece Cards in 2025"),
6
+ ui.div(
7
+ ui.input_action_button("generate_btn", "Generate Blog"),
8
+ class_="mt-3 mb-3"
9
+ ),
10
+ ui.output_ui("blog_result"),
11
+ ui.output_text('keywords_used')
12
  )
uv.lock CHANGED
@@ -1465,6 +1465,42 @@ wheels = [
1465
  { url = "https://files.pythonhosted.org/packages/8a/56/c12e4222b312146e8a0979f59ac326980b46bb356292145f2e08a47bb907/logfire_api-3.21.2-py3-none-any.whl", hash = "sha256:14ae7689be6ad51ce68f2009bade1e4c804f01e17e435ab9ecb11c799ff027a2", size = 82623 },
1466
  ]
1467
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1468
  [[package]]
1469
  name = "markdown-it-py"
1470
  version = "3.0.0"
@@ -2415,6 +2451,20 @@ wheels = [
2415
  { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546 },
2416
  ]
2417
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2418
  [[package]]
2419
  name = "pytz"
2420
  version = "2025.2"
@@ -3158,6 +3208,7 @@ dependencies = [
3158
  { name = "pydantic-ai" },
3159
  { name = "pyrsm" },
3160
  { name = "python-dotenv" },
 
3161
  { name = "requests" },
3162
  ]
3163
 
@@ -3176,6 +3227,7 @@ requires-dist = [
3176
  { name = "pydantic-ai", specifier = ">=0.3.5" },
3177
  { name = "pyrsm", specifier = ">=1.6.0" },
3178
  { name = "python-dotenv", specifier = ">=1.1.1" },
 
3179
  { name = "requests", specifier = ">=2.32.4" },
3180
  ]
3181
 
 
1465
  { url = "https://files.pythonhosted.org/packages/8a/56/c12e4222b312146e8a0979f59ac326980b46bb356292145f2e08a47bb907/logfire_api-3.21.2-py3-none-any.whl", hash = "sha256:14ae7689be6ad51ce68f2009bade1e4c804f01e17e435ab9ecb11c799ff027a2", size = 82623 },
1466
  ]
1467
 
1468
+ [[package]]
1469
+ name = "lxml"
1470
+ version = "6.0.0"
1471
+ source = { registry = "https://pypi.org/simple" }
1472
+ sdist = { url = "https://files.pythonhosted.org/packages/c5/ed/60eb6fa2923602fba988d9ca7c5cdbd7cf25faa795162ed538b527a35411/lxml-6.0.0.tar.gz", hash = "sha256:032e65120339d44cdc3efc326c9f660f5f7205f3a535c1fdbf898b29ea01fb72", size = 4096938 }
1473
+ wheels = [
1474
+ { url = "https://files.pythonhosted.org/packages/89/c3/d01d735c298d7e0ddcedf6f028bf556577e5ab4f4da45175ecd909c79378/lxml-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78718d8454a6e928470d511bf8ac93f469283a45c354995f7d19e77292f26108", size = 8429515 },
1475
+ { url = "https://files.pythonhosted.org/packages/06/37/0e3eae3043d366b73da55a86274a590bae76dc45aa004b7042e6f97803b1/lxml-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:84ef591495ffd3f9dcabffd6391db7bb70d7230b5c35ef5148354a134f56f2be", size = 4601387 },
1476
+ { url = "https://files.pythonhosted.org/packages/a3/28/e1a9a881e6d6e29dda13d633885d13acb0058f65e95da67841c8dd02b4a8/lxml-6.0.0-cp312-cp312-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:2930aa001a3776c3e2601cb8e0a15d21b8270528d89cc308be4843ade546b9ab", size = 5228928 },
1477
+ { url = "https://files.pythonhosted.org/packages/9a/55/2cb24ea48aa30c99f805921c1c7860c1f45c0e811e44ee4e6a155668de06/lxml-6.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:219e0431ea8006e15005767f0351e3f7f9143e793e58519dc97fe9e07fae5563", size = 4952289 },
1478
+ { url = "https://files.pythonhosted.org/packages/31/c0/b25d9528df296b9a3306ba21ff982fc5b698c45ab78b94d18c2d6ae71fd9/lxml-6.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bd5913b4972681ffc9718bc2d4c53cde39ef81415e1671ff93e9aa30b46595e7", size = 5111310 },
1479
+ { url = "https://files.pythonhosted.org/packages/e9/af/681a8b3e4f668bea6e6514cbcb297beb6de2b641e70f09d3d78655f4f44c/lxml-6.0.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:390240baeb9f415a82eefc2e13285016f9c8b5ad71ec80574ae8fa9605093cd7", size = 5025457 },
1480
+ { url = "https://files.pythonhosted.org/packages/69/f8/693b1a10a891197143c0673fcce5b75fc69132afa81a36e4568c12c8faba/lxml-6.0.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ca50bd612438258a91b5b3788c6621c1f05c8c478e7951899f492be42defc0da", size = 5257565 },
1481
+ { url = "https://files.pythonhosted.org/packages/a8/96/e08ff98f2c6426c98c8964513c5dab8d6eb81dadcd0af6f0c538ada78d33/lxml-6.0.0-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:c24b8efd9c0f62bad0439283c2c795ef916c5a6b75f03c17799775c7ae3c0c9e", size = 4713390 },
1482
+ { url = "https://files.pythonhosted.org/packages/a8/83/6184aba6cc94d7413959f6f8f54807dc318fdcd4985c347fe3ea6937f772/lxml-6.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:afd27d8629ae94c5d863e32ab0e1d5590371d296b87dae0a751fb22bf3685741", size = 5066103 },
1483
+ { url = "https://files.pythonhosted.org/packages/ee/01/8bf1f4035852d0ff2e36a4d9aacdbcc57e93a6cd35a54e05fa984cdf73ab/lxml-6.0.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:54c4855eabd9fc29707d30141be99e5cd1102e7d2258d2892314cf4c110726c3", size = 4791428 },
1484
+ { url = "https://files.pythonhosted.org/packages/5c/f7/5495829a864bc5f8b0798d2b52a807c89966523140f3d6fa3a58ab6720ea/lxml-6.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:36531f81c8214e293097cd2b7873f178997dae33d3667caaae8bdfb9666b76c0", size = 5281290 },
1485
+ { url = "https://files.pythonhosted.org/packages/79/56/6b8edb79d9ed294ccc4e881f4db1023af56ba451909b9ce79f2a2cd7c532/lxml-6.0.0-cp312-cp312-win32.whl", hash = "sha256:690b20e3388a7ec98e899fd54c924e50ba6693874aa65ef9cb53de7f7de9d64a", size = 3613495 },
1486
+ { url = "https://files.pythonhosted.org/packages/0b/1e/cc32034b40ad6af80b6fd9b66301fc0f180f300002e5c3eb5a6110a93317/lxml-6.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:310b719b695b3dd442cdfbbe64936b2f2e231bb91d998e99e6f0daf991a3eba3", size = 4014711 },
1487
+ { url = "https://files.pythonhosted.org/packages/55/10/dc8e5290ae4c94bdc1a4c55865be7e1f31dfd857a88b21cbba68b5fea61b/lxml-6.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:8cb26f51c82d77483cdcd2b4a53cda55bbee29b3c2f3ddeb47182a2a9064e4eb", size = 3674431 },
1488
+ { url = "https://files.pythonhosted.org/packages/79/21/6e7c060822a3c954ff085e5e1b94b4a25757c06529eac91e550f3f5cd8b8/lxml-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6da7cd4f405fd7db56e51e96bff0865b9853ae70df0e6720624049da76bde2da", size = 8414372 },
1489
+ { url = "https://files.pythonhosted.org/packages/a4/f6/051b1607a459db670fc3a244fa4f06f101a8adf86cda263d1a56b3a4f9d5/lxml-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b34339898bb556a2351a1830f88f751679f343eabf9cf05841c95b165152c9e7", size = 4593940 },
1490
+ { url = "https://files.pythonhosted.org/packages/8e/74/dd595d92a40bda3c687d70d4487b2c7eff93fd63b568acd64fedd2ba00fe/lxml-6.0.0-cp313-cp313-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:51a5e4c61a4541bd1cd3ba74766d0c9b6c12d6a1a4964ef60026832aac8e79b3", size = 5214329 },
1491
+ { url = "https://files.pythonhosted.org/packages/52/46/3572761efc1bd45fcafb44a63b3b0feeb5b3f0066886821e94b0254f9253/lxml-6.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d18a25b19ca7307045581b18b3ec9ead2b1db5ccd8719c291f0cd0a5cec6cb81", size = 4947559 },
1492
+ { url = "https://files.pythonhosted.org/packages/94/8a/5e40de920e67c4f2eef9151097deb9b52d86c95762d8ee238134aff2125d/lxml-6.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d4f0c66df4386b75d2ab1e20a489f30dc7fd9a06a896d64980541506086be1f1", size = 5102143 },
1493
+ { url = "https://files.pythonhosted.org/packages/7c/4b/20555bdd75d57945bdabfbc45fdb1a36a1a0ff9eae4653e951b2b79c9209/lxml-6.0.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f4b481b6cc3a897adb4279216695150bbe7a44c03daba3c894f49d2037e0a24", size = 5021931 },
1494
+ { url = "https://files.pythonhosted.org/packages/d4/dd/39c8507c16db6031f8c1ddf70ed95dbb0a6d466a40002a3522c128aba472/lxml-6.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ae06fbab4f1bb7db4f7c8ca9897dc8db4447d1a2b9bee78474ad403437bcc29", size = 5247467 },
1495
+ { url = "https://files.pythonhosted.org/packages/4d/56/732d49def0631ad633844cfb2664563c830173a98d5efd9b172e89a4800d/lxml-6.0.0-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:1fa377b827ca2023244a06554c6e7dc6828a10aaf74ca41965c5d8a4925aebb4", size = 4720601 },
1496
+ { url = "https://files.pythonhosted.org/packages/8f/7f/6b956fab95fa73462bca25d1ea7fc8274ddf68fb8e60b78d56c03b65278e/lxml-6.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1676b56d48048a62ef77a250428d1f31f610763636e0784ba67a9740823988ca", size = 5060227 },
1497
+ { url = "https://files.pythonhosted.org/packages/97/06/e851ac2924447e8b15a294855caf3d543424364a143c001014d22c8ca94c/lxml-6.0.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:0e32698462aacc5c1cf6bdfebc9c781821b7e74c79f13e5ffc8bfe27c42b1abf", size = 4790637 },
1498
+ { url = "https://files.pythonhosted.org/packages/52/03/0e764ce00b95e008d76b99d432f1807f3574fb2945b496a17807a1645dbd/lxml-6.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7488a43033c958637b1a08cddc9188eb06d3ad36582cebc7d4815980b47e27ef", size = 5272430 },
1499
+ { url = "https://files.pythonhosted.org/packages/5f/01/d48cc141bc47bc1644d20fe97bbd5e8afb30415ec94f146f2f76d0d9d098/lxml-6.0.0-cp313-cp313-win32.whl", hash = "sha256:5fcd7d3b1d8ecb91445bd71b9c88bdbeae528fefee4f379895becfc72298d181", size = 3612896 },
1500
+ { url = "https://files.pythonhosted.org/packages/f4/87/6456b9541d186ee7d4cb53bf1b9a0d7f3b1068532676940fdd594ac90865/lxml-6.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:2f34687222b78fff795feeb799a7d44eca2477c3d9d3a46ce17d51a4f383e32e", size = 4013132 },
1501
+ { url = "https://files.pythonhosted.org/packages/b7/42/85b3aa8f06ca0d24962f8100f001828e1f1f1a38c954c16e71154ed7d53a/lxml-6.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:21db1ec5525780fd07251636eb5f7acb84003e9382c72c18c542a87c416ade03", size = 3672642 },
1502
+ ]
1503
+
1504
  [[package]]
1505
  name = "markdown-it-py"
1506
  version = "3.0.0"
 
2451
  { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546 },
2452
  ]
2453
 
2454
+ [[package]]
2455
+ name = "pytrends"
2456
+ version = "4.9.2"
2457
+ source = { registry = "https://pypi.org/simple" }
2458
+ dependencies = [
2459
+ { name = "lxml" },
2460
+ { name = "pandas" },
2461
+ { name = "requests" },
2462
+ ]
2463
+ sdist = { url = "https://files.pythonhosted.org/packages/e9/65/a242fd8fbe98c11bd51f0b57d4752905396a99c42c91c3213c3f44e741c8/pytrends-4.9.2.tar.gz", hash = "sha256:691c6e36b1aeaa4754f3692bdbad0dff446e528ffb052eee2e7f139aaa2c6989", size = 247162 }
2464
+ wheels = [
2465
+ { url = "https://files.pythonhosted.org/packages/68/ba/7a24a3723c790000faf880505ff1cc46f4d29f46dd353037938a070c4d23/pytrends-4.9.2-py3-none-any.whl", hash = "sha256:d7d0ee956be2f6e3e9fc09376c4615efb9347039d6d1f46e6f4326a5b7a14f67", size = 15352 },
2466
+ ]
2467
+
2468
  [[package]]
2469
  name = "pytz"
2470
  version = "2025.2"
 
3208
  { name = "pydantic-ai" },
3209
  { name = "pyrsm" },
3210
  { name = "python-dotenv" },
3211
+ { name = "pytrends" },
3212
  { name = "requests" },
3213
  ]
3214
 
 
3227
  { name = "pydantic-ai", specifier = ">=0.3.5" },
3228
  { name = "pyrsm", specifier = ">=1.6.0" },
3229
  { name = "python-dotenv", specifier = ">=1.1.1" },
3230
+ { name = "pytrends", specifier = ">=4.9.2" },
3231
  { name = "requests", specifier = ">=2.32.4" },
3232
  ]
3233
 
www/style.css ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #blog_result {
2
+ font-family: system-ui, sans-serif;
3
+ line-height: 1.6;
4
+ background-color: #fdfdfd;
5
+ border: 1px solid #ddd;
6
+ padding: 1.5rem;
7
+ margin-top: 1rem;
8
+ border-radius: 8px;
9
+ max-width: 800px;
10
+ white-space: normal;
11
+ }
12
+
13
+ #blog_result h1,
14
+ #blog_result h2,
15
+ #blog_result h3 {
16
+ margin-top: 1.5em;
17
+ margin-bottom: 0.5em;
18
+ }
19
+
20
+ #blog_result p {
21
+ margin-bottom: 1em;
22
+ }
23
+
24
+ #blog_result ul {
25
+ padding-left: 1.2rem;
26
+ }