Tom Claude commited on
Commit
8c1e2c8
Β·
1 Parent(s): 42e8b59

Polish UI and optimize for production deployment

Browse files

UI improvements:
- Dark theme with centered layout (800px max-width)
- Custom chat styling with dark blue input wrapper
- Animated submit button with hover effects
- Loading spinner with white dots for visibility
- Bellingcat logo attribution with methodology section

Code optimizations:
- Removed unused functions (investigate, get_tool_recommendations)
- Fixed temperature default (0.2 β†’ 0.3 for better explanations)
- Increased max_tokens (600 β†’ 800) for detailed instructions
- Optimized SVG loading (module-level constant)
- Updated env var checks (SUPABASE_URL, SUPABASE_KEY, HF_TOKEN)

Prompt improvements:
- Enhanced to explain HOW to use tools, not just list them
- Added "How to use" and "What you'll find" for each step
- Better follow-up question handling
- Increased word limit (300 β†’ 400) for actionable guidance

πŸ€– Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

Files changed (4) hide show
  1. app.py +142 -163
  2. assets/bellingcat.svg +10 -0
  3. src/llm_client.py +2 -2
  4. src/prompts.py +29 -21
app.py CHANGED
@@ -19,31 +19,16 @@ try:
19
  pipeline = create_pipeline(
20
  retrieval_k=5,
21
  model=os.getenv("LLM_MODEL", "meta-llama/Llama-3.1-8B-Instruct"),
22
- temperature=float(os.getenv("LLM_TEMPERATURE", "0.7"))
23
  )
24
  print("βœ“ Pipeline initialized successfully")
25
  except Exception as e:
26
  print(f"βœ— Error initializing pipeline: {e}")
27
  raise
28
 
29
-
30
- def investigate(message: str, history: list) -> str:
31
- """
32
- Main chat function for investigation queries
33
-
34
- Args:
35
- message: User's investigation query
36
- history: Chat history (list of [user_msg, bot_msg] pairs)
37
-
38
- Returns:
39
- Generated investigation methodology
40
- """
41
- try:
42
- # Generate response (non-streaming for simplicity)
43
- response = pipeline.generate_methodology(message, stream=False)
44
- return response
45
- except Exception as e:
46
- return f"Error generating response: {str(e)}\n\nPlease check your environment variables (HF_TOKEN, SUPABASE_CONNECTION_STRING) and try again."
47
 
48
 
49
  def investigate_stream(message: str, history: list):
@@ -64,76 +49,148 @@ def investigate_stream(message: str, history: list):
64
  full_response += chunk
65
  yield full_response
66
  except Exception as e:
67
- yield f"Error generating response: {str(e)}\n\nPlease check your environment variables (HF_TOKEN, SUPABASE_CONNECTION_STRING) and try again."
68
-
69
-
70
- def get_tool_recommendations(query: str, k: int = 5) -> str:
71
- """
72
- Get tool recommendations for a query
73
 
74
- Args:
75
- query: Investigation query
76
- k: Number of tools to recommend
77
-
78
- Returns:
79
- Formatted tool recommendations
80
- """
81
- try:
82
- tools = pipeline.get_tool_recommendations(query, k=k)
83
 
84
- if not tools:
85
- return "No relevant tools found."
86
-
87
- output = f"## Top {len(tools)} Recommended Tools\n\n"
88
-
89
- for i, tool in enumerate(tools, 1):
90
- output += f"### {i}. {tool['name']}\n"
91
- output += f"- **Category**: {tool['category']}\n"
92
- output += f"- **Cost**: {tool['cost']}\n"
93
- output += f"- **URL**: {tool['url']}\n"
94
- output += f"- **Description**: {tool['description']}\n"
95
- if tool['details'] and tool['details'] != 'N/A':
96
- output += f"- **Details**: {tool['details']}\n"
97
- output += "\n"
98
-
99
- return output
100
- except Exception as e:
101
- return f"Error retrieving tools: {str(e)}"
102
-
103
-
104
- # Custom CSS for better appearance
105
  custom_css = """
106
  .gradio-container {
107
- max-width: 900px !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  }
109
- #component-0 {
110
- max-width: 900px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
  }
112
  """
113
 
114
  # Create Gradio interface
115
  with gr.Blocks(
116
  title="OSINT Investigation Assistant",
117
- theme=gr.themes.Soft(),
 
 
 
 
 
118
  css=custom_css
119
  ) as demo:
120
  gr.Markdown("""
121
- # πŸ” OSINT Investigation Assistant
122
 
123
- Ask me how to investigate anything using open-source intelligence methods.
124
- I'll provide you with a structured methodology and recommend specific OSINT tools
125
- from a database of 344+ tools.
126
-
127
- **Examples:**
128
- - "How do I investigate a suspicious domain?"
129
- - "What tools can I use to verify an image's authenticity?"
130
- - "How can I trace the origin of a social media account?"
131
  """)
132
 
133
  # Main chat interface
134
  chatbot = gr.ChatInterface(
135
  fn=investigate_stream,
136
  type="messages",
 
137
  examples=[
138
  "How do I investigate a suspicious domain?",
139
  "What tools can I use to verify an image's authenticity?",
@@ -142,105 +199,28 @@ with gr.Blocks(
142
  "How do I geolocate an image from social media?"
143
  ],
144
  cache_examples=False,
145
- title="Chat Interface",
146
- description="Ask your investigation questions here",
147
- api_name="investigate" # This creates the /call/investigate API endpoint
148
  )
149
 
150
- # Additional tab for direct tool search
151
- with gr.Tab("Tool Search"):
152
- gr.Markdown("### Search for OSINT Tools")
153
- with gr.Row():
154
- tool_query = gr.Textbox(
155
- label="Search Query",
156
- placeholder="e.g., social media analysis, image verification, domain investigation",
157
- lines=2
158
- )
159
- tool_count = gr.Slider(
160
- minimum=1,
161
- maximum=20,
162
- value=5,
163
- step=1,
164
- label="Number of Tools"
165
- )
166
-
167
- tool_search_btn = gr.Button("Search Tools", variant="primary")
168
- tool_output = gr.Markdown(label="Recommended Tools")
169
-
170
- tool_search_btn.click(
171
- fn=get_tool_recommendations,
172
- inputs=[tool_query, tool_count],
173
- outputs=tool_output,
174
- api_name="search_tools" # This creates the /call/search_tools API endpoint
175
- )
176
-
177
- # Information tab
178
- with gr.Tab("About"):
179
- gr.Markdown("""
180
- ## About This Assistant
181
-
182
- This OSINT Investigation Assistant helps researchers and investigators develop
183
- structured methodologies for open-source intelligence investigations.
184
-
185
- ### Features
186
- - 🎯 **Structured Methodologies**: Get step-by-step investigation plans
187
- - πŸ› οΈ **Tool Recommendations**: Access a database of 344+ OSINT tools
188
- - πŸ” **Context-Aware**: Tools are recommended based on your specific needs
189
- - πŸš€ **API Access**: Use this app via API for integration with other tools
190
-
191
- ### Technology Stack
192
- - **Vector Database**: Supabase with PGVector (344 OSINT tools)
193
- - **LLM**: Hugging Face Inference Providers (Llama 3.1)
194
- - **RAG Framework**: LangChain for retrieval-augmented generation
195
- - **UI/API**: Gradio with automatic API generation
196
-
197
- ### API Usage
198
-
199
- This app automatically exposes API endpoints. You can access them using:
200
-
201
- **Python Client:**
202
- ```python
203
- from gradio_client import Client
204
-
205
- client = Client("your-space-url")
206
- result = client.predict("How do I investigate a domain?", api_name="/investigate")
207
- print(result)
208
- ```
209
-
210
- **cURL:**
211
- ```bash
212
- curl -X POST "https://your-space.hf.space/call/investigate" \\
213
- -H "Content-Type: application/json" \\
214
- -d '{"data": ["How do I investigate a domain?"]}'
215
- ```
216
-
217
- View the full API documentation at the bottom of this page (click "Use via API").
218
-
219
- ### Environment Variables Required
220
- - `SUPABASE_CONNECTION_STRING`: PostgreSQL connection string for Supabase
221
- - `HF_TOKEN`: Hugging Face API token for Inference Providers
222
- - `LLM_MODEL` (optional): Model to use (default: meta-llama/Llama-3.1-8B-Instruct)
223
- - `LLM_TEMPERATURE` (optional): Temperature for generation (default: 0.7)
224
-
225
- ### Data Source
226
- The tool recommendations are based on the Bellingcat OSINT Toolkit and other
227
- curated sources, with 344+ tools across categories including:
228
- - Social Media Investigation
229
- - Image and Video Analysis
230
- - Domain and Network Investigation
231
- - Geolocation
232
- - Archiving and Preservation
233
- - And more...
234
-
235
- ---
236
-
237
- Built with ❀️ for the OSINT community
238
- """)
239
 
240
  # Launch configuration
241
  if __name__ == "__main__":
242
  # Check for required environment variables
243
- required_vars = ["SUPABASE_CONNECTION_STRING", "HF_TOKEN"]
244
  missing_vars = [var for var in required_vars if not os.getenv(var)]
245
 
246
  if missing_vars:
@@ -248,10 +228,9 @@ if __name__ == "__main__":
248
  print("Please set these in your .env file or as environment variables")
249
 
250
  # Launch the app
251
- # Set mcp_server=True to enable MCP protocol for agent integration
252
  demo.launch(
253
  server_name="0.0.0.0",
254
  server_port=7860,
255
  share=False,
256
- show_api=True # Show API documentation
257
  )
 
19
  pipeline = create_pipeline(
20
  retrieval_k=5,
21
  model=os.getenv("LLM_MODEL", "meta-llama/Llama-3.1-8B-Instruct"),
22
+ temperature=float(os.getenv("LLM_TEMPERATURE", "0.2"))
23
  )
24
  print("βœ“ Pipeline initialized successfully")
25
  except Exception as e:
26
  print(f"βœ— Error initializing pipeline: {e}")
27
  raise
28
 
29
+ # Load SVG once at module level
30
+ with open("assets/bellingcat.svg", "r") as f:
31
+ BELLINGCAT_SVG = f.read()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
 
33
 
34
  def investigate_stream(message: str, history: list):
 
49
  full_response += chunk
50
  yield full_response
51
  except Exception as e:
52
+ yield f"Error generating response: {str(e)}\n\nPlease check your environment variables (HF_TOKEN, SUPABASE_URL, SUPABASE_KEY) and try again."
 
 
 
 
 
53
 
 
 
 
 
 
 
 
 
 
54
 
55
+ # Custom CSS for centered, clean appearance with dark theme
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  custom_css = """
57
  .gradio-container {
58
+ max-width: 800px !important;
59
+ margin: 0 auto !important;
60
+ }
61
+ /* Force white text throughout */
62
+ .gradio-container,
63
+ .gradio-container * {
64
+ color: white !important;
65
+ }
66
+ /* Chat interface styling */
67
+ .custom-chat {
68
+ background: rgba(30, 58, 95, 0.6) !important;
69
+ }
70
+ .custom-chat * {
71
+ background: rgba(30, 58, 95, 0.6) !important;
72
+ }
73
+ .custom-chat .message-row {
74
+ background: rgba(30, 58, 95, 0.4) !important;
75
+ }
76
+ .custom-chat .message {
77
+ color: white !important;
78
+ }
79
+ /* Loading spinner styling */
80
+ .custom-chat .generating,
81
+ .custom-chat .pending {
82
+ border-color: rgba(255, 255, 255, 0.3) !important;
83
+ }
84
+ .custom-chat .generating::before,
85
+ .custom-chat .pending::before {
86
+ background: white !important;
87
+ }
88
+ /* Loading dots */
89
+ .custom-chat .dot-flashing,
90
+ .custom-chat .dot-flashing::before,
91
+ .custom-chat .dot-flashing::after {
92
+ background: white !important;
93
+ }
94
+ /* Input fields - target parent containers using :has() */
95
+ textarea:has(textarea),
96
+ label:has(textarea),
97
+ .block:has(textarea),
98
+ div:has(> textarea) {
99
+ background: rgba(30, 58, 95, 0.6) !important;
100
  }
101
+ /* Target textarea itself */
102
+ textarea, input {
103
+ background: rgba(30, 58, 95, 0.6) !important;
104
+ color: white !important;
105
+ border-color: rgba(255, 255, 255, 0.2) !important;
106
+ }
107
+ textarea::placeholder {
108
+ color: rgba(255, 255, 255, 0.5) !important;
109
+ }
110
+ /* Buttons */
111
+ button {
112
+ color: white !important;
113
+ }
114
+ /* Remove background from submit/stop buttons */
115
+ .stop-button, .submit-button {
116
+ background: transparent !important;
117
+ transition: transform 0.2s ease, opacity 0.2s ease !important;
118
+ }
119
+ .stop-button:hover, .submit-button:hover {
120
+ transform: scale(1.1) !important;
121
+ opacity: 0.8 !important;
122
+ }
123
+ .stop-button:active, .submit-button:active {
124
+ transform: scale(0.95) !important;
125
+ }
126
+ .methodology-section {
127
+ margin-top: 40px;
128
+ padding-top: 20px;
129
+ border-top: 1px solid rgba(255, 255, 255, 0.2);
130
+ }
131
+ .methodology-header {
132
+ font-size: 14px;
133
+ font-weight: 600;
134
+ margin-bottom: 15px;
135
+ opacity: 0.6;
136
+ color: white !important;
137
+ }
138
+ .methodology-disclaimer {
139
+ font-size: 13px;
140
+ opacity: 0.5;
141
+ margin-bottom: 15px;
142
+ line-height: 1.6;
143
+ color: white !important;
144
+ }
145
+ .methodology-disclaimer a {
146
+ color: white !important;
147
+ text-decoration: underline;
148
+ opacity: 0.8;
149
+ }
150
+ .thanks-text {
151
+ text-align: left;
152
+ opacity: 0.4;
153
+ font-size: 12px;
154
+ margin-bottom: 10px;
155
+ text-transform: uppercase;
156
+ letter-spacing: 1px;
157
+ color: white !important;
158
+ }
159
+ .logo-container {
160
+ text-align: left;
161
+ margin-bottom: 20px;
162
+ }
163
+ .logo-container svg,
164
+ .logo-container img {
165
+ max-width: 150px;
166
+ height: auto;
167
+ opacity: 0.7;
168
+ filter: brightness(0) invert(1);
169
  }
170
  """
171
 
172
  # Create Gradio interface
173
  with gr.Blocks(
174
  title="OSINT Investigation Assistant",
175
+ theme=gr.themes.Soft(primary_hue="slate").set(
176
+ body_background_fill="*neutral_950",
177
+ body_background_fill_dark="*neutral_950",
178
+ input_background_fill="rgba(30, 58, 95, 0.6)",
179
+ input_background_fill_dark="rgba(30, 58, 95, 0.6)"
180
+ ),
181
  css=custom_css
182
  ) as demo:
183
  gr.Markdown("""
184
+ # πŸ” OSINT LLM
185
 
186
+ Get structured investigation methodologies and tool recommendations from Bellingcat's database of 344+ OSINT tools.
 
 
 
 
 
 
 
187
  """)
188
 
189
  # Main chat interface
190
  chatbot = gr.ChatInterface(
191
  fn=investigate_stream,
192
  type="messages",
193
+ chatbot=gr.Chatbot(elem_classes="custom-chat"),
194
  examples=[
195
  "How do I investigate a suspicious domain?",
196
  "What tools can I use to verify an image's authenticity?",
 
199
  "How do I geolocate an image from social media?"
200
  ],
201
  cache_examples=False,
202
+ api_name="investigate"
 
 
203
  )
204
 
205
+ # Methodology section (below chat interface)
206
+ gr.HTML(f"""
207
+ <div class="methodology-section">
208
+ <div class="methodology-header">Methodology</div>
209
+ <div class="methodology-disclaimer">
210
+ The data used by this model was sourced from:
211
+ <a href="https://github.com/bellingcat/toolkit" target="_blank">https://github.com/bellingcat/toolkit</a>
212
+ </div>
213
+ <div class="thanks-text">With thanks to</div>
214
+ <div class="logo-container">
215
+ {BELLINGCAT_SVG}
216
+ </div>
217
+ </div>
218
+ """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
219
 
220
  # Launch configuration
221
  if __name__ == "__main__":
222
  # Check for required environment variables
223
+ required_vars = ["SUPABASE_URL", "SUPABASE_KEY", "HF_TOKEN"]
224
  missing_vars = [var for var in required_vars if not os.getenv(var)]
225
 
226
  if missing_vars:
 
228
  print("Please set these in your .env file or as environment variables")
229
 
230
  # Launch the app
 
231
  demo.launch(
232
  server_name="0.0.0.0",
233
  server_port=7860,
234
  share=False,
235
+ show_api=True
236
  )
assets/bellingcat.svg ADDED
src/llm_client.py CHANGED
@@ -12,8 +12,8 @@ class InferenceProviderClient:
12
  self,
13
  model: str = "meta-llama/Llama-3.1-8B-Instruct",
14
  api_key: Optional[str] = None,
15
- temperature: float = 0.2,
16
- max_tokens: int = 600
17
  ):
18
  """
19
  Initialize the Inference client
 
12
  self,
13
  model: str = "meta-llama/Llama-3.1-8B-Instruct",
14
  api_key: Optional[str] = None,
15
+ temperature: float = 0.3,
16
+ max_tokens: int = 800
17
  ):
18
  """
19
  Initialize the Inference client
src/prompts.py CHANGED
@@ -1,23 +1,27 @@
1
  """Prompt templates for OSINT investigation assistant"""
2
 
3
 
4
- SYSTEM_PROMPT = """You are an OSINT investigation assistant. Your responses must be SHORT and FOCUSED.
5
 
6
- STRICT RULES:
7
- 1. ONLY recommend tools from the provided database - DO NOT suggest tools not in the list
8
- 2. Keep your response under 300 words
9
- 3. List 3-5 steps maximum
10
- 4. Include tool names and URLs from the database
11
- 5. NO lengthy explanations
12
- 6. NO additional tools beyond what's provided
13
 
14
  Format:
15
  **Investigation Steps:**
16
- 1. [Step] - Use [Tool Name] ([URL])
17
- 2. [Step] - Use [Tool Name] ([URL])
18
- 3. [Step] - Use [Tool Name] ([URL])
 
 
 
 
19
 
20
- **Why these tools:** [1-2 sentences max]"""
21
 
22
 
23
  INVESTIGATION_PROMPT_TEMPLATE = """USER QUESTION: {query}
@@ -26,20 +30,24 @@ AVAILABLE TOOLS FROM DATABASE:
26
  {context}
27
 
28
  INSTRUCTIONS:
29
- - Provide 3-5 investigation steps ONLY
 
30
  - Use ONLY tools from the list above
31
- - Include tool name + URL for each step
32
- - Keep response under 300 words
33
- - Be specific and direct
34
- - NO lengthy explanations
35
 
36
  Respond with:
37
- **Steps:**
38
  1. [Action] using [Tool Name] ([URL])
39
- 2. [Action] using [Tool Name] ([URL])
40
- 3. [Action] using [Tool Name] ([URL])
 
 
 
 
41
 
42
- **Notes:** [1-2 sentences explaining why these specific tools]"""
43
 
44
 
45
  FOLLOWUP_PROMPT_TEMPLATE = """You are an expert OSINT investigation assistant continuing a conversation.
 
1
  """Prompt templates for OSINT investigation assistant"""
2
 
3
 
4
+ SYSTEM_PROMPT = """You are an OSINT investigation assistant. Provide practical, actionable guidance.
5
 
6
+ RULES:
7
+ 1. ONLY recommend tools from the provided database
8
+ 2. Explain HOW to use each tool, not just what it does
9
+ 3. Provide step-by-step methodology in logical order
10
+ 4. Keep response under 400 words
11
+ 5. For follow-up questions like "tell me more", provide additional details about the tools/methods
12
+ 6. Be specific about inputs, outputs, and what to look for
13
 
14
  Format:
15
  **Investigation Steps:**
16
+ 1. [Action] using [Tool Name] ([URL])
17
+ - How: [Brief instructions on using the tool]
18
+ - What to look for: [Expected results/outputs]
19
+
20
+ 2. [Next action] using [Tool Name] ([URL])
21
+ - How: [Brief instructions]
22
+ - What to look for: [Expected results]
23
 
24
+ **Key Points:** [Important considerations or tips]"""
25
 
26
 
27
  INVESTIGATION_PROMPT_TEMPLATE = """USER QUESTION: {query}
 
30
  {context}
31
 
32
  INSTRUCTIONS:
33
+ - Provide 3-5 investigation steps in logical order
34
+ - For EACH step, explain HOW to use the tool (what to input, what to look for)
35
  - Use ONLY tools from the list above
36
+ - Include practical tips and expected outcomes
37
+ - Keep response under 400 words total
38
+ - If user asks "tell me more" or follow-up questions, provide additional details from the tool descriptions
 
39
 
40
  Respond with:
41
+ **Investigation Steps:**
42
  1. [Action] using [Tool Name] ([URL])
43
+ - How to use: [Specific instructions - what to enter, where to click, etc.]
44
+ - What you'll find: [Expected results and what they mean]
45
+
46
+ 2. [Next action] using [Tool Name] ([URL])
47
+ - How to use: [Instructions]
48
+ - What you'll find: [Results]
49
 
50
+ **Important Notes:** [Key considerations, tips, or warnings]"""
51
 
52
 
53
  FOLLOWUP_PROMPT_TEMPLATE = """You are an expert OSINT investigation assistant continuing a conversation.