anurag-deo commited on
Commit
ee2ca90
Β·
1 Parent(s): 77099d3

Add configuration tab for adding custom api keys

Browse files
.gitignore ADDED
@@ -0,0 +1,245 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ pip-wheel-metadata/
24
+ share/python-wheels/
25
+ *.egg-info/
26
+ .installed.cfg
27
+ *.egg
28
+ MANIFEST
29
+
30
+ # PyInstaller
31
+ # Usually these files are written by a python script from a template
32
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
33
+ *.manifest
34
+ *.spec
35
+
36
+ # Installer logs
37
+ pip-log.txt
38
+ pip-delete-this-directory.txt
39
+
40
+ # Unit test / coverage reports
41
+ htmlcov/
42
+ .tox/
43
+ .nox/
44
+ .coverage
45
+ .coverage.*
46
+ .cache
47
+ nosetests.xml
48
+ coverage.xml
49
+ *.cover
50
+ *.py,cover
51
+ .hypothesis/
52
+ .pytest_cache/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ target/
76
+
77
+ # Jupyter Notebook
78
+ .ipynb_checkpoints
79
+
80
+ # IPython
81
+ profile_default/
82
+ ipython_config.py
83
+
84
+ # pyenv
85
+ .python-version
86
+
87
+ # pipenv
88
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
90
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
91
+ # install all needed dependencies.
92
+ #Pipfile.lock
93
+
94
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow
95
+ __pypackages__/
96
+
97
+ # Celery stuff
98
+ celerybeat-schedule
99
+ celerybeat.pid
100
+
101
+ # SageMath parsed files
102
+ *.sage.py
103
+
104
+ # Environments
105
+ .env
106
+ .venv
107
+ env/
108
+ venv/
109
+ ENV/
110
+ env.bak/
111
+ venv.bak/
112
+
113
+ # Spyder project settings
114
+ .spyderproject
115
+ .spyproject
116
+
117
+ # Rope project settings
118
+ .ropeproject
119
+
120
+ # mkdocs documentation
121
+ /site
122
+
123
+ # mypy
124
+ .mypy_cache/
125
+ .dmypy.json
126
+ dmypy.json
127
+
128
+ # Pyre type checker
129
+ .pyre/
130
+
131
+ # pytype static type analyzer
132
+ .pytype/
133
+
134
+ # Cython debug symbols
135
+ cython_debug/
136
+
137
+ # IDEs and editors
138
+ .vscode/
139
+ .idea/
140
+ *.swp
141
+ *.swo
142
+ *~
143
+
144
+ # MacOS
145
+ .DS_Store
146
+ .AppleDouble
147
+ .LSOverride
148
+
149
+ # Windows
150
+ Thumbs.db
151
+ ehthumbs.db
152
+ Desktop.ini
153
+ $RECYCLE.BIN/
154
+ *.cab
155
+ *.msi
156
+ *.msix
157
+ *.msm
158
+ *.msp
159
+ *.lnk
160
+
161
+ # Linux
162
+ *~
163
+
164
+ # Docker
165
+ .dockerignore
166
+
167
+ # Project-specific files
168
+ # Temporary files and outputs
169
+ outputs/
170
+ data/temp/
171
+ temp/
172
+ tmp/
173
+
174
+ # Uploaded files
175
+ uploads/
176
+ *.pdf
177
+ *.docx
178
+ *.doc
179
+
180
+ # API keys and secrets
181
+ .env.local
182
+ .env.production
183
+ .env.staging
184
+ config/secrets.py
185
+ secrets.json
186
+
187
+ # Database files
188
+ *.db
189
+ *.sqlite
190
+ *.sqlite3
191
+
192
+ # Log files
193
+ logs/
194
+ *.log
195
+
196
+ # Cache directories
197
+ .cache/
198
+ cache/
199
+
200
+ # Generated LaTeX files
201
+ *.aux
202
+ *.bbl
203
+ *.blg
204
+ *.fdb_latexmk
205
+ *.fls
206
+ *.synctex.gz
207
+ *.out
208
+ *.toc
209
+ *.nav
210
+ *.snm
211
+ *.vrb
212
+
213
+ # uv lock file (optional - remove if you want to track it)
214
+ # uv.lock
215
+
216
+ # Gradio temporary files
217
+ gradio_cached_examples/
218
+ flagged/
219
+
220
+ # Model files (if storing locally)
221
+ models/
222
+ *.pkl
223
+ *.joblib
224
+ *.model
225
+
226
+ # Jupyter notebook checkpoints
227
+ .ipynb_checkpoints/
228
+
229
+ # pytest
230
+ .pytest_cache/
231
+
232
+ # Coverage reports
233
+ htmlcov/
234
+ .coverage
235
+
236
+ # Backup files
237
+ *.bak
238
+ *.backup
239
+ *.old
240
+
241
+ # OS generated files
242
+ .DS_Store?
243
+ ehthumbs.db
244
+ Icon?
245
+ Thumbs.db
app/config/__pycache__/settings.cpython-310.pyc CHANGED
Binary files a/app/config/__pycache__/settings.cpython-310.pyc and b/app/config/__pycache__/settings.cpython-310.pyc differ
 
app/config/settings.py CHANGED
@@ -10,44 +10,118 @@ except ImportError:
10
 
11
 
12
  class Settings:
13
- # LLM Configuration
14
- MISTRAL_API_KEY: str = os.getenv("MISTRAL_API_KEY", "")
15
-
16
- # Model Configuration
17
- HEAVY_MODEL_PROVIDER: str = os.getenv("HEAVY_MODEL_PROVIDER", "openai")
18
- HEAVY_MODEL_NAME: str = os.getenv("HEAVY_MODEL_NAME", "gpt-4-turbo")
19
- HEAVY_MODEL_API_KEY: str = os.getenv("HEAVY_MODEL_API_KEY", "")
20
- HEAVY_MODEL_BASE_URL: str | None = os.getenv("HEAVY_MODEL_BASE_URL")
21
-
22
- LIGHT_MODEL_PROVIDER: str = os.getenv("LIGHT_MODEL_PROVIDER", "openai")
23
- LIGHT_MODEL_NAME: str = os.getenv("LIGHT_MODEL_NAME", "gpt-3.5-turbo")
24
- LIGHT_MODEL_API_KEY: str = os.getenv("LIGHT_MODEL_API_KEY", "")
25
- LIGHT_MODEL_BASE_URL: str | None = os.getenv("LIGHT_MODEL_BASE_URL")
26
-
27
- CODING_MODEL_PROVIDER: str = os.getenv("CODING_MODEL_PROVIDER", "openai")
28
- CODING_MODEL_NAME: str = os.getenv("CODING_MODEL_NAME", "gpt-4-1106-preview")
29
- CODING_MODEL_API_KEY: str = os.getenv("CODING_MODEL_API_KEY", "")
30
- CODING_MODEL_BASE_URL: str | None = os.getenv(
31
- "CODING_MODEL_BASE_URL",
32
- "https://api.openai.com/v1/chat/completions",
33
- )
34
-
35
- # Image Generation
36
- IMAGE_GEN_API_KEY: str = os.getenv("IMAGE_GEN_API_KEY", "")
37
- IMAGE_GEN_BASE_URL: str | None = os.getenv(
38
- "IMAGE_GEN_BASE_URL",
39
- "https://api.openai.com/v1/images/generations",
40
- )
41
- IMAGE_GEN_MODEL: str = os.getenv("IMAGE_GEN_MODEL", "dall-e-3")
42
- IMAGE_GEN_IMAGE_SIZE: str = os.getenv("IMAGE_GEN_IMAGE_SIZE", "1024x1024")
43
- IMAGE_GEN_IMAGE_QUALITY: str = os.getenv("IMAGE_GEN_IMAGE_QUALITY", "standard")
44
- IMAGE_GEN_IMAGE_STYLE: str = os.getenv("IMAGE_GEN_IMAGE_STYLE", "vivid")
45
-
46
- # DeepInfra API for blog images
47
- DEEPINFRA_API_KEY: str = os.getenv("DEEPINFRA_API_KEY", "")
48
-
49
- # API Keys
50
- DEVTO_API_KEY: str = os.getenv("DEVTO_API_KEY", "")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
 
52
  # Database
53
  DATABASE_URL: str = os.getenv("DATABASE_URL", "sqlite:///./scholarshare.db")
 
10
 
11
 
12
  class Settings:
13
+ def __init__(self):
14
+ # Load default values from environment
15
+ self._load_defaults()
16
+ # Runtime overrides - these will be updated from UI
17
+ self._runtime_overrides = {}
18
+
19
+ def _load_defaults(self):
20
+ # LLM Configuration
21
+ self.MISTRAL_API_KEY: str = os.getenv("MISTRAL_API_KEY", "")
22
+
23
+ # Model Configuration
24
+ self.HEAVY_MODEL_PROVIDER: str = os.getenv("HEAVY_MODEL_PROVIDER", "openai")
25
+ self.HEAVY_MODEL_NAME: str = os.getenv("HEAVY_MODEL_NAME", "gpt-4-turbo")
26
+ self.HEAVY_MODEL_API_KEY: str = os.getenv("HEAVY_MODEL_API_KEY", "")
27
+ self.HEAVY_MODEL_BASE_URL: str | None = os.getenv("HEAVY_MODEL_BASE_URL")
28
+
29
+ self.LIGHT_MODEL_PROVIDER: str = os.getenv("LIGHT_MODEL_PROVIDER", "openai")
30
+ self.LIGHT_MODEL_NAME: str = os.getenv("LIGHT_MODEL_NAME", "gpt-3.5-turbo")
31
+ self.LIGHT_MODEL_API_KEY: str = os.getenv("LIGHT_MODEL_API_KEY", "")
32
+ self.LIGHT_MODEL_BASE_URL: str | None = os.getenv("LIGHT_MODEL_BASE_URL")
33
+
34
+ self.CODING_MODEL_PROVIDER: str = os.getenv("CODING_MODEL_PROVIDER", "openai")
35
+ self.CODING_MODEL_NAME: str = os.getenv(
36
+ "CODING_MODEL_NAME", "gpt-4-1106-preview"
37
+ )
38
+ self.CODING_MODEL_API_KEY: str = os.getenv("CODING_MODEL_API_KEY", "")
39
+ self.CODING_MODEL_BASE_URL: str | None = os.getenv(
40
+ "CODING_MODEL_BASE_URL",
41
+ "https://api.openai.com/v1/chat/completions",
42
+ )
43
+
44
+ # Image Generation
45
+ self.IMAGE_GEN_API_KEY: str = os.getenv("IMAGE_GEN_API_KEY", "")
46
+ self.IMAGE_GEN_BASE_URL: str | None = os.getenv(
47
+ "IMAGE_GEN_BASE_URL",
48
+ "https://api.openai.com/v1/images/generations",
49
+ )
50
+ self.IMAGE_GEN_MODEL: str = os.getenv("IMAGE_GEN_MODEL", "dall-e-3")
51
+ self.IMAGE_GEN_IMAGE_SIZE: str = os.getenv("IMAGE_GEN_IMAGE_SIZE", "1024x1024")
52
+ self.IMAGE_GEN_IMAGE_QUALITY: str = os.getenv(
53
+ "IMAGE_GEN_IMAGE_QUALITY", "standard"
54
+ )
55
+ self.IMAGE_GEN_IMAGE_STYLE: str = os.getenv("IMAGE_GEN_IMAGE_STYLE", "vivid")
56
+
57
+ # DeepInfra API for blog images
58
+ self.DEEPINFRA_API_KEY: str = os.getenv("DEEPINFRA_API_KEY", "")
59
+
60
+ # API Keys
61
+ self.DEVTO_API_KEY: str = os.getenv("DEVTO_API_KEY", "")
62
+
63
+ def get_value(self, key: str):
64
+ """Get value with runtime override support"""
65
+ return self._runtime_overrides.get(key, getattr(self, key, ""))
66
+
67
+ def set_override(self, key: str, value: str):
68
+ """Set runtime override for a setting"""
69
+ if value and value.strip():
70
+ self._runtime_overrides[key] = value.strip()
71
+ elif key in self._runtime_overrides:
72
+ # Remove override if empty value provided
73
+ del self._runtime_overrides[key]
74
+
75
+ def clear_overrides(self):
76
+ """Clear all runtime overrides"""
77
+ self._runtime_overrides.clear()
78
+
79
+ def get_overrides_status(self) -> dict:
80
+ """Get status of which settings have been overridden"""
81
+ return {
82
+ "HEAVY_MODEL_API_KEY": bool(
83
+ self._runtime_overrides.get("HEAVY_MODEL_API_KEY")
84
+ ),
85
+ "LIGHT_MODEL_API_KEY": bool(
86
+ self._runtime_overrides.get("LIGHT_MODEL_API_KEY")
87
+ ),
88
+ "CODING_MODEL_API_KEY": bool(
89
+ self._runtime_overrides.get("CODING_MODEL_API_KEY")
90
+ ),
91
+ "IMAGE_GEN_API_KEY": bool(self._runtime_overrides.get("IMAGE_GEN_API_KEY")),
92
+ "DEEPINFRA_API_KEY": bool(self._runtime_overrides.get("DEEPINFRA_API_KEY")),
93
+ "DEVTO_API_KEY": bool(self._runtime_overrides.get("DEVTO_API_KEY")),
94
+ "MISTRAL_API_KEY": bool(self._runtime_overrides.get("MISTRAL_API_KEY")),
95
+ }
96
+
97
+ # Properties to maintain compatibility
98
+ @property
99
+ def HEAVY_MODEL_API_KEY_CURRENT(self) -> str:
100
+ return self.get_value("HEAVY_MODEL_API_KEY")
101
+
102
+ @property
103
+ def LIGHT_MODEL_API_KEY_CURRENT(self) -> str:
104
+ return self.get_value("LIGHT_MODEL_API_KEY")
105
+
106
+ @property
107
+ def CODING_MODEL_API_KEY_CURRENT(self) -> str:
108
+ return self.get_value("CODING_MODEL_API_KEY")
109
+
110
+ @property
111
+ def IMAGE_GEN_API_KEY_CURRENT(self) -> str:
112
+ return self.get_value("IMAGE_GEN_API_KEY")
113
+
114
+ @property
115
+ def DEEPINFRA_API_KEY_CURRENT(self) -> str:
116
+ return self.get_value("DEEPINFRA_API_KEY")
117
+
118
+ @property
119
+ def DEVTO_API_KEY_CURRENT(self) -> str:
120
+ return self.get_value("DEVTO_API_KEY")
121
+
122
+ @property
123
+ def MISTRAL_API_KEY_CURRENT(self) -> str:
124
+ return self.get_value("MISTRAL_API_KEY")
125
 
126
  # Database
127
  DATABASE_URL: str = os.getenv("DATABASE_URL", "sqlite:///./scholarshare.db")
app/services/__pycache__/blog_image_service.cpython-310.pyc CHANGED
Binary files a/app/services/__pycache__/blog_image_service.cpython-310.pyc and b/app/services/__pycache__/blog_image_service.cpython-310.pyc differ
 
app/services/__pycache__/devto_service.cpython-310.pyc CHANGED
Binary files a/app/services/__pycache__/devto_service.cpython-310.pyc and b/app/services/__pycache__/devto_service.cpython-310.pyc differ
 
app/services/__pycache__/image_service.cpython-310.pyc CHANGED
Binary files a/app/services/__pycache__/image_service.cpython-310.pyc and b/app/services/__pycache__/image_service.cpython-310.pyc differ
 
app/services/__pycache__/llm_service.cpython-310.pyc CHANGED
Binary files a/app/services/__pycache__/llm_service.cpython-310.pyc and b/app/services/__pycache__/llm_service.cpython-310.pyc differ
 
app/services/blog_image_service.py CHANGED
@@ -1,12 +1,12 @@
1
  import asyncio
2
  import base64
3
- import os
4
  import re
5
  from concurrent.futures import ThreadPoolExecutor
6
  from pathlib import Path
7
 
8
  import requests
9
 
 
10
  from app.models.schemas import PaperAnalysis
11
 
12
 
@@ -59,22 +59,22 @@ class BlogImageService:
59
 
60
  prompt_generation_text = f"""
61
  You are an expert in creating visual prompts for AI image generation to enhance blog posts about research papers.
62
-
63
  Research Paper Details:
64
  Title: {analysis.title}
65
  Abstract: {analysis.abstract[:300]}...
66
  Key Findings: {", ".join(analysis.key_findings[:3])}
67
  Methodology: {analysis.methodology[:200]}...
68
-
69
  Blog Content Preview: {content[:500]}...
70
-
71
- Create 2-3 detailed visual prompts for FLUX image generation that would enhance this blog post.
72
  Each prompt should:
73
  1. Be scientifically accurate and relevant to the research
74
  2. Create visually appealing, professional diagrams or illustrations
75
  3. Help explain complex concepts through visual metaphors
76
  4. Be suitable for a blog audience (clear, engaging, informative)
77
-
78
  Generate prompts for:
79
  1. A main concept illustration (abstract/conceptual)
80
  2. A methodology visualization (process/workflow)
@@ -152,7 +152,7 @@ class BlogImageService:
152
  url = f"https://api.deepinfra.com/v1/inference/{self.deepinfra_model}"
153
  headers = {
154
  "Content-Type": "application/json",
155
- "Authorization": f"bearer {os.getenv('DEEPINFRA_API_KEY')}",
156
  }
157
  payload = {
158
  "prompt": prompt,
@@ -175,7 +175,8 @@ class BlogImageService:
175
  b64_json = data[0].get("b64_json")
176
  if b64_json:
177
  return self._save_and_return_base64_image(
178
- b64_json, "temp_image.png"
 
179
  )
180
 
181
  # Handle response format with direct image_url
 
1
  import asyncio
2
  import base64
 
3
  import re
4
  from concurrent.futures import ThreadPoolExecutor
5
  from pathlib import Path
6
 
7
  import requests
8
 
9
+ from app.config.settings import settings
10
  from app.models.schemas import PaperAnalysis
11
 
12
 
 
59
 
60
  prompt_generation_text = f"""
61
  You are an expert in creating visual prompts for AI image generation to enhance blog posts about research papers.
62
+
63
  Research Paper Details:
64
  Title: {analysis.title}
65
  Abstract: {analysis.abstract[:300]}...
66
  Key Findings: {", ".join(analysis.key_findings[:3])}
67
  Methodology: {analysis.methodology[:200]}...
68
+
69
  Blog Content Preview: {content[:500]}...
70
+
71
+ Create 2-3 detailed visual prompts for FLUX image generation that would enhance this blog post.
72
  Each prompt should:
73
  1. Be scientifically accurate and relevant to the research
74
  2. Create visually appealing, professional diagrams or illustrations
75
  3. Help explain complex concepts through visual metaphors
76
  4. Be suitable for a blog audience (clear, engaging, informative)
77
+
78
  Generate prompts for:
79
  1. A main concept illustration (abstract/conceptual)
80
  2. A methodology visualization (process/workflow)
 
152
  url = f"https://api.deepinfra.com/v1/inference/{self.deepinfra_model}"
153
  headers = {
154
  "Content-Type": "application/json",
155
+ "Authorization": f"bearer {settings.DEEPINFRA_API_KEY_CURRENT}",
156
  }
157
  payload = {
158
  "prompt": prompt,
 
175
  b64_json = data[0].get("b64_json")
176
  if b64_json:
177
  return self._save_and_return_base64_image(
178
+ b64_json,
179
+ "temp_image.png",
180
  )
181
 
182
  # Handle response format with direct image_url
app/services/devto_service.py CHANGED
@@ -1,5 +1,5 @@
1
  import asyncio
2
- from typing import Any, Dict
3
 
4
  import requests
5
 
@@ -9,27 +9,32 @@ from app.models.schemas import BlogContent
9
 
10
  class DevToService:
11
  def __init__(self):
12
- self.api_key = settings.DEVTO_API_KEY
13
  self.base_url = "https://dev.to/api"
14
 
 
 
 
 
15
  async def publish_article(
16
  self,
17
  blog_content: BlogContent,
18
  publish_now: bool = False,
19
- ) -> Dict[str, Any]:
20
  """Publish article to DEV.to"""
21
 
22
  def _sync_publish():
23
  try:
24
  # Check if API key is configured
25
- if not self.api_key:
 
26
  return {
27
  "success": False,
28
- "error": "DEV.to API key is not configured. Please set the DEVTO_API_KEY environment variable.",
29
  }
30
 
31
  headers = {
32
- "api-key": self.api_key,
33
  "Content-Type": "application/json",
34
  }
35
 
@@ -105,7 +110,7 @@ class DevToService:
105
  loop = asyncio.get_event_loop()
106
  return await loop.run_in_executor(None, _sync_publish)
107
 
108
- async def get_my_articles(self, per_page: int = 10) -> Dict[str, Any]:
109
  """Get user's published articles"""
110
 
111
  def _sync_get_articles():
 
1
  import asyncio
2
+ from typing import Any
3
 
4
  import requests
5
 
 
9
 
10
  class DevToService:
11
  def __init__(self):
12
+ # Don't store the API key at init, get it dynamically
13
  self.base_url = "https://dev.to/api"
14
 
15
+ def get_api_key(self):
16
+ """Get the current API key (with runtime override support)"""
17
+ return settings.DEVTO_API_KEY_CURRENT
18
+
19
  async def publish_article(
20
  self,
21
  blog_content: BlogContent,
22
  publish_now: bool = False,
23
+ ) -> dict[str, Any]:
24
  """Publish article to DEV.to"""
25
 
26
  def _sync_publish():
27
  try:
28
  # Check if API key is configured
29
+ api_key = self.get_api_key()
30
+ if not api_key:
31
  return {
32
  "success": False,
33
+ "error": "DEV.to API key is not configured. Please set the DEVTO_API_KEY environment variable or override it in the Configuration tab.",
34
  }
35
 
36
  headers = {
37
+ "api-key": api_key,
38
  "Content-Type": "application/json",
39
  }
40
 
 
110
  loop = asyncio.get_event_loop()
111
  return await loop.run_in_executor(None, _sync_publish)
112
 
113
+ async def get_my_articles(self, per_page: int = 10) -> dict[str, Any]:
114
  """Get user's published articles"""
115
 
116
  def _sync_get_articles():
app/services/image_service.py CHANGED
@@ -1,8 +1,6 @@
1
  import asyncio
2
  import base64
3
- from email.mime import base
4
  from pathlib import Path
5
- from typing import Optional
6
 
7
  import aiofiles
8
  from openai import AsyncOpenAI
@@ -13,12 +11,17 @@ from app.models.schemas import PaperAnalysis
13
 
14
  class ImageGenerationService:
15
  def __init__(self):
16
- self.client = AsyncOpenAI(
17
- api_key=settings.IMAGE_GEN_API_KEY, base_url=settings.IMAGE_GEN_BASE_URL
18
- )
19
  self.output_dir = Path("outputs/images")
20
  self.output_dir.mkdir(parents=True, exist_ok=True)
21
 
 
 
 
 
 
 
 
22
  async def generate_image_prompt(
23
  self,
24
  analysis: PaperAnalysis,
@@ -94,7 +97,7 @@ class ImageGenerationService:
94
  analysis: PaperAnalysis,
95
  platform: str,
96
  style: str = settings.IMAGE_GEN_IMAGE_STYLE,
97
- ) -> Optional[str]:
98
  """Generate an image for social media post using Image Generation model"""
99
  try:
100
  # Generate the image prompt using LLM
@@ -104,7 +107,8 @@ class ImageGenerationService:
104
  styled_prompt = f"{image_prompt}, {platform} social media style, professional, high quality, digital art"
105
 
106
  # Generate image using Image Generation model
107
- response = await self.client.images.generate(
 
108
  model=settings.IMAGE_GEN_MODEL,
109
  prompt=styled_prompt,
110
  size=settings.IMAGE_GEN_IMAGE_SIZE,
@@ -165,7 +169,7 @@ class ImageGenerationService:
165
 
166
  images = await asyncio.gather(*tasks, return_exceptions=True)
167
 
168
- for platform, image_path in zip(platforms, images):
169
  if isinstance(image_path, Exception):
170
  print(f"Failed to generate image for {platform}: {image_path!s}")
171
  results[platform] = None
 
1
  import asyncio
2
  import base64
 
3
  from pathlib import Path
 
4
 
5
  import aiofiles
6
  from openai import AsyncOpenAI
 
11
 
12
  class ImageGenerationService:
13
  def __init__(self):
14
+ # Don't initialize client here, create it on-demand to get current API key
 
 
15
  self.output_dir = Path("outputs/images")
16
  self.output_dir.mkdir(parents=True, exist_ok=True)
17
 
18
+ def get_client(self):
19
+ """Get OpenAI client with current API key (supports runtime overrides)"""
20
+ api_key = settings.IMAGE_GEN_API_KEY_CURRENT
21
+ if not api_key:
22
+ raise ValueError("Image generation API key not configured")
23
+ return AsyncOpenAI(api_key=api_key, base_url=settings.IMAGE_GEN_BASE_URL)
24
+
25
  async def generate_image_prompt(
26
  self,
27
  analysis: PaperAnalysis,
 
97
  analysis: PaperAnalysis,
98
  platform: str,
99
  style: str = settings.IMAGE_GEN_IMAGE_STYLE,
100
+ ) -> str | None:
101
  """Generate an image for social media post using Image Generation model"""
102
  try:
103
  # Generate the image prompt using LLM
 
107
  styled_prompt = f"{image_prompt}, {platform} social media style, professional, high quality, digital art"
108
 
109
  # Generate image using Image Generation model
110
+ client = self.get_client()
111
+ response = await client.images.generate(
112
  model=settings.IMAGE_GEN_MODEL,
113
  prompt=styled_prompt,
114
  size=settings.IMAGE_GEN_IMAGE_SIZE,
 
169
 
170
  images = await asyncio.gather(*tasks, return_exceptions=True)
171
 
172
+ for platform, image_path in zip(platforms, images, strict=False):
173
  if isinstance(image_path, Exception):
174
  print(f"Failed to generate image for {platform}: {image_path!s}")
175
  results[platform] = None
app/services/llm_service.py CHANGED
@@ -13,17 +13,17 @@ class LLMService:
13
  if model_type == "heavy":
14
  provider = settings.HEAVY_MODEL_PROVIDER
15
  model_name = settings.HEAVY_MODEL_NAME
16
- api_key = settings.HEAVY_MODEL_API_KEY
17
  base_url = settings.HEAVY_MODEL_BASE_URL
18
  elif model_type == "light":
19
  provider = settings.LIGHT_MODEL_PROVIDER
20
  model_name = settings.LIGHT_MODEL_NAME
21
- api_key = settings.LIGHT_MODEL_API_KEY
22
  base_url = settings.LIGHT_MODEL_BASE_URL
23
  elif model_type == "coding":
24
  provider = settings.CODING_MODEL_PROVIDER
25
  model_name = settings.CODING_MODEL_NAME
26
- api_key = settings.CODING_MODEL_API_KEY
27
  base_url = settings.CODING_MODEL_BASE_URL
28
 
29
  if not api_key:
 
13
  if model_type == "heavy":
14
  provider = settings.HEAVY_MODEL_PROVIDER
15
  model_name = settings.HEAVY_MODEL_NAME
16
+ api_key = settings.HEAVY_MODEL_API_KEY_CURRENT
17
  base_url = settings.HEAVY_MODEL_BASE_URL
18
  elif model_type == "light":
19
  provider = settings.LIGHT_MODEL_PROVIDER
20
  model_name = settings.LIGHT_MODEL_NAME
21
+ api_key = settings.LIGHT_MODEL_API_KEY_CURRENT
22
  base_url = settings.LIGHT_MODEL_BASE_URL
23
  elif model_type == "coding":
24
  provider = settings.CODING_MODEL_PROVIDER
25
  model_name = settings.CODING_MODEL_NAME
26
+ api_key = settings.CODING_MODEL_API_KEY_CURRENT
27
  base_url = settings.CODING_MODEL_BASE_URL
28
 
29
  if not api_key:
main.py CHANGED
@@ -1,8 +1,9 @@
1
  import asyncio
2
  from pathlib import Path
3
- from typing import Optional
4
 
5
  import gradio as gr
 
 
6
  from app.agents.blog_generator import BlogGeneratorAgent
7
  from app.agents.paper_analyzer import PaperAnalyzerAgent
8
  from app.agents.poster_generator import PosterGeneratorAgent
@@ -12,7 +13,6 @@ from app.config.settings import settings
12
  from app.models.schemas import PaperInput
13
  from app.services.devto_service import devto_service
14
  from app.services.pdf_service import pdf_service
15
- from gradio_pdf import PDF
16
 
17
  # Initialize agents
18
  paper_analyzer = PaperAnalyzerAgent()
@@ -22,14 +22,14 @@ poster_generator = PosterGeneratorAgent()
22
  presentation_generator = PresentationGeneratorAgent()
23
 
24
  # Global state - Consider refactoring to avoid globals if possible
25
- current_analysis: Optional[dict] = None
26
- current_blog: Optional[dict] = None
27
- current_tldr: Optional[dict] = None
28
- current_poster: Optional[dict] = None
29
- current_presentation: Optional[dict] = None
30
 
31
 
32
- async def process_paper(pdf_file, url_input, text_input, progress=None):
33
  """Process paper from various input sources."""
34
  global current_analysis
35
  if progress is None:
@@ -555,6 +555,56 @@ async def download_presentation_beamer():
555
  return gr.DownloadButton(visible=False)
556
 
557
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
558
  # Create Gradio Interface
559
  def create_interface():
560
  with gr.Blocks(
@@ -563,7 +613,7 @@ def create_interface():
563
  ) as app:
564
  gr.Markdown("""
565
  # πŸŽ“ ScholarShare - AI-Powered Research Dissemination Platform
566
-
567
  Transform complex research papers into accessible, multi-format content for broader audience engagement.
568
  """)
569
 
@@ -804,6 +854,69 @@ def create_interface():
804
  interactive=False,
805
  )
806
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
807
  # Event handlers
808
  process_btn.click(
809
  fn=process_paper,
@@ -880,6 +993,23 @@ def create_interface():
880
  outputs=[publish_status],
881
  )
882
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
883
  return app
884
 
885
 
 
1
  import asyncio
2
  from pathlib import Path
 
3
 
4
  import gradio as gr
5
+ from gradio_pdf import PDF
6
+
7
  from app.agents.blog_generator import BlogGeneratorAgent
8
  from app.agents.paper_analyzer import PaperAnalyzerAgent
9
  from app.agents.poster_generator import PosterGeneratorAgent
 
13
  from app.models.schemas import PaperInput
14
  from app.services.devto_service import devto_service
15
  from app.services.pdf_service import pdf_service
 
16
 
17
  # Initialize agents
18
  paper_analyzer = PaperAnalyzerAgent()
 
22
  presentation_generator = PresentationGeneratorAgent()
23
 
24
  # Global state - Consider refactoring to avoid globals if possible
25
+ current_analysis: dict | None = None
26
+ current_blog: dict | None = None
27
+ current_tldr: dict | None = None
28
+ current_poster: dict | None = None
29
+ current_presentation: dict | None = None
30
 
31
 
32
+ async def process_paper(pdf_file, url_input, progress=None):
33
  """Process paper from various input sources."""
34
  global current_analysis
35
  if progress is None:
 
555
  return gr.DownloadButton(visible=False)
556
 
557
 
558
+ # Configuration functions
559
+ def update_api_keys(
560
+ heavy_model_key,
561
+ light_model_key,
562
+ coding_model_key,
563
+ devto_key,
564
+ mistral_key,
565
+ ):
566
+ """Update API keys with runtime overrides."""
567
+ # Update settings with new keys
568
+ settings.set_override("HEAVY_MODEL_API_KEY", heavy_model_key)
569
+ settings.set_override("LIGHT_MODEL_API_KEY", light_model_key)
570
+ settings.set_override("CODING_MODEL_API_KEY", coding_model_key)
571
+ settings.set_override("IMAGE_GEN_API_KEY", image_gen_key)
572
+ settings.set_override("DEEPINFRA_API_KEY", deepinfra_key)
573
+ settings.set_override("DEVTO_API_KEY", devto_key)
574
+ settings.set_override("MISTRAL_API_KEY", mistral_key)
575
+
576
+ # Get status of overrides
577
+ status = settings.get_overrides_status()
578
+
579
+ # Create status message
580
+ overridden_keys = [key for key, is_overridden in status.items() if is_overridden]
581
+
582
+ if overridden_keys:
583
+ status_msg = (
584
+ f"βœ… Configuration updated! Overridden keys: {', '.join(overridden_keys)}"
585
+ )
586
+ else:
587
+ status_msg = "β„Ή Configuration cleared. Using environment variables."
588
+
589
+ return status_msg
590
+
591
+
592
+ def clear_api_keys():
593
+ """Clear all API key overrides."""
594
+ settings.clear_overrides()
595
+ return "πŸ”„ All API key overrides cleared. Using environment variables."
596
+
597
+
598
+ def get_current_config_status():
599
+ """Get current configuration status."""
600
+ status = settings.get_overrides_status()
601
+ overridden_keys = [key for key, is_overridden in status.items() if is_overridden]
602
+
603
+ if overridden_keys:
604
+ return f"πŸ”§ Active overrides: {', '.join(overridden_keys)}"
605
+ return "πŸ“‹ Using environment variables (no overrides active)"
606
+
607
+
608
  # Create Gradio Interface
609
  def create_interface():
610
  with gr.Blocks(
 
613
  ) as app:
614
  gr.Markdown("""
615
  # πŸŽ“ ScholarShare - AI-Powered Research Dissemination Platform
616
+
617
  Transform complex research papers into accessible, multi-format content for broader audience engagement.
618
  """)
619
 
 
854
  interactive=False,
855
  )
856
 
857
+ with gr.Tab("βš™οΈ Configuration"):
858
+ gr.Markdown("## API Key Configuration")
859
+ gr.Markdown("""
860
+ Override API keys from environment variables. This is useful when your environment keys expire or you want to use different keys temporarily.
861
+ Leave fields empty to use environment variables.
862
+ """)
863
+
864
+ with gr.Row():
865
+ with gr.Column():
866
+ gr.Markdown("### Language Models")
867
+ heavy_model_key_input = gr.Textbox(
868
+ label="Heavy Model API Key (GPT-4, etc.)",
869
+ type="password",
870
+ placeholder="Enter key to override environment variable...",
871
+ )
872
+ light_model_key_input = gr.Textbox(
873
+ label="Light Model API Key (GPT-4-mini/nano, etc.)",
874
+ type="password",
875
+ placeholder="Enter key to override environment variable...",
876
+ )
877
+ coding_model_key_input = gr.Textbox(
878
+ label="Coding Model API Key (Claude)",
879
+ type="password",
880
+ placeholder="Enter key to override environment variable...",
881
+ )
882
+ mistral_key_input = gr.Textbox(
883
+ label="Mistral API Key",
884
+ type="password",
885
+ placeholder="Enter key to override environment variable...",
886
+ )
887
+
888
+ with gr.Column():
889
+ gr.Markdown("### Other Services")
890
+ # image_gen_key_input = gr.Textbox(
891
+ # label="Image Generation API Key (DALL-E, etc.)",
892
+ # type="password",
893
+ # placeholder="Enter key to override environment variable...",
894
+ # )
895
+ # deepinfra_key_input = gr.Textbox(
896
+ # label="DeepInfra API Key",
897
+ # type="password",
898
+ # placeholder="Enter key to override environment variable...",
899
+ # )
900
+ devto_key_input = gr.Textbox(
901
+ label="DEV.to API Key",
902
+ type="password",
903
+ placeholder="Enter key to override environment variable...",
904
+ )
905
+
906
+ with gr.Row():
907
+ update_config_btn = gr.Button(
908
+ "πŸ’Ύ Update Configuration", variant="primary"
909
+ )
910
+ clear_config_btn = gr.Button(
911
+ "πŸ”„ Clear All Overrides", variant="secondary"
912
+ )
913
+
914
+ config_status = gr.Textbox(
915
+ label="Configuration Status",
916
+ interactive=False,
917
+ value=get_current_config_status(),
918
+ )
919
+
920
  # Event handlers
921
  process_btn.click(
922
  fn=process_paper,
 
993
  outputs=[publish_status],
994
  )
995
 
996
+ update_config_btn.click(
997
+ fn=update_api_keys,
998
+ inputs=[
999
+ heavy_model_key_input,
1000
+ light_model_key_input,
1001
+ coding_model_key_input,
1002
+ devto_key_input,
1003
+ mistral_key_input,
1004
+ ],
1005
+ outputs=[config_status],
1006
+ )
1007
+
1008
+ clear_config_btn.click(
1009
+ fn=clear_api_keys,
1010
+ outputs=[config_status],
1011
+ )
1012
+
1013
  return app
1014
 
1015