pimenta-marcus commited on
Commit
178b685
·
verified ·
1 Parent(s): 2f8ac01

Avalie o código do site e o aprimore visualmente e funcionalmente

Browse files
Files changed (4) hide show
  1. README.md +7 -4
  2. components/footer.js +63 -0
  3. components/navbar.js +98 -0
  4. index.html +501 -18
README.md CHANGED
@@ -1,10 +1,13 @@
1
  ---
2
- title: Nexus Explorer Pro
3
- emoji: 👀
4
- colorFrom: gray
5
  colorTo: purple
 
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
1
  ---
2
+ title: 🧠 Nexus Explorer Pro 🚀
3
+ colorFrom: green
 
4
  colorTo: purple
5
+ emoji: 🐳
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite-v3
10
  ---
11
 
12
+ # Welcome to your new DeepSite project!
13
+ This project was created with [DeepSite](https://huggingface.co/deepsite).
components/footer.js ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>About - Nexus Explorer Pro</title>
7
+ <link rel="icon" type="image/x-icon" href="/static/favicon.ico">
8
+ <link rel="stylesheet" href="style.css">
9
+ <script src="https://cdn.tailwindcss.com"></script>
10
+ </head>
11
+ <body>
12
+ <custom-navbar></custom-navbar>
13
+
14
+ <main class="max-w-4xl mx-auto px-4 py-12">
15
+ <div class="bg-white dark:bg-gray-800 rounded-2xl shadow-xl p-8">
16
+ <h1 class="text-4xl font-bold text-center mb-8 text-gray-900 dark:text-white">
17
+ About Nexus Explorer Pro
18
+ </h1>
19
+
20
+ <div class="prose prose-lg dark:prose-invert max-w-none">
21
+ <p class="text-xl text-gray-600 dark:text-gray-300 mb-6">
22
+ Welcome to the future of knowledge exploration! Nexus Explorer Pro is an intelligent
23
+ visual learning platform that transforms complex topics into interactive knowledge graphs.
24
+ </p>
25
+
26
+ <div class="grid md:grid-cols-2 gap-8 my-12">
27
+ <div class="bg-gradient-to-br from-blue-50 to-indigo-100 dark:from-blue-900/20 dark:to-indigo-900/20 p-6 rounded-xl">
28
+ <h3 class="text-xl font-semibold mb-3 text-blue-900 dark:text-blue-200">🎯 How it Works</h3>
29
+ <p class="text-gray-700 dark:text-gray-300">
30
+ Simply enter any topic and watch as our AI-powered engine creates a beautiful
31
+ network of interconnected concepts, facts, and ideas.
32
+ </p>
33
+ </div>
34
+
35
+ <div class="bg-gradient-to-br from-purple-50 to-pink-100 dark:from-purple-900/20 dark:to-pink-900/20 p-6 rounded-xl">
36
+ <h3 class="text-xl font-semibold mb-3 text-purple-900 dark:text-purple-200">🚀 Key Features</h3>
37
+ <ul class="text-gray-700 dark:text-gray-300 space-y-2">
38
+ <li>• Interactive knowledge graphs</li>
39
+ <li>• AI-powered concept discovery</li>
40
+ <li>• Real-time graph expansion</li>
41
+ <li>• Data persistence across sessions</li>
42
+ </ul>
43
+ </div>
44
+ </div>
45
+
46
+ <div class="bg-gradient-to-r from-green-400 to-blue-500 text-white p-8 rounded-2xl text-center">
47
+ <h2 class="text-2xl font-bold mb-4">Ready to Explore?</h2>
48
+ <p class="mb-6">Start building your knowledge network today!</p>
49
+ <a href="/" class="inline-block bg-white text-gray-900 px-6 py-3 rounded-lg font-semibold hover:bg-gray-100 transition">
50
+ Launch Explorer →
51
+ </a>
52
+ </div>
53
+ </div>
54
+ </div>
55
+ </main>
56
+
57
+ <custom-footer></custom-footer>
58
+
59
+ <script src="components/navbar.js"></script>
60
+ <script src="components/footer.js"></script>
61
+ <script src="script.js"></script>
62
+ </body>
63
+ </html>
components/navbar.js ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class CustomNavbar extends HTMLElement {
2
+ connectedCallback() {
3
+ this.attachShadow({ mode: 'open' });
4
+ this.shadowRoot.innerHTML = `
5
+ <style>
6
+ :host {
7
+ --nav-bg: light-dark(#ffffff, #2d2e30);
8
+ --nav-border: light-dark(#dadce0, #5f6368);
9
+ --nav-text: light-dark(#202124, #e8eaed);
10
+ --nav-hover: light-dark(#f8f9fa, #3c4043);
11
+ --primary: light-dark(#1a73e8, #8ab4f8);
12
+ }
13
+
14
+ nav {
15
+ display: flex;
16
+ align-items: center;
17
+ justify-content: space-between;
18
+ padding: 1rem 2rem;
19
+ background: var(--nav-bg);
20
+ border-bottom: 1px solid var(--nav-border);
21
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
22
+ }
23
+
24
+ .brand {
25
+ font-family: 'Google Sans', sans-serif;
26
+ font-size: 1.5rem;
27
+ font-weight: 700;
28
+ color: var(--primary);
29
+ text-decoration: none;
30
+ }
31
+
32
+ .nav-links {
33
+ display: flex;
34
+ gap: 2rem;
35
+ align-items: center;
36
+ }
37
+
38
+ a {
39
+ color: var(--nav-text);
40
+ text-decoration: none;
41
+ font-weight: 500;
42
+ padding: 0.5rem 1rem;
43
+ border-radius: 6px;
44
+ transition: all 0.2s;
45
+ }
46
+
47
+ a:hover {
48
+ background: var(--nav-hover);
49
+ color: var(--primary);
50
+ }
51
+
52
+ .theme-toggle {
53
+ background: none;
54
+ border: none;
55
+ cursor: pointer;
56
+ padding: 0.5rem;
57
+ border-radius: 6px;
58
+ transition: background 0.2s;
59
+ }
60
+
61
+ .theme-toggle:hover {
62
+ background: var(--nav-hover);
63
+ }
64
+
65
+ .theme-toggle svg {
66
+ width: 20px;
67
+ height: 20px;
68
+ fill: var(--nav-text);
69
+ }
70
+ </style>
71
+
72
+ <nav>
73
+ <a href="/" class="brand">🧠 Nexus</a>
74
+ <div class="nav-links">
75
+ <a href="/">Home</a>
76
+ <a href="/about.html">About</a>
77
+ <a href="/gallery.html">Gallery</a>
78
+ <button class="theme-toggle" id="themeToggle" title="Toggle theme">
79
+ <svg viewBox="0 0 24 24">
80
+ <path d="M12 2.25a.75.75 0 01.75.75v2.25a.75.75 0 01-1.5 0V3a.75.75 0 01.75-.75zM7.5 12a4.5 4.5 0 119 0 4.5 4.5 0 01-9 0zM18.894 6.166a.75.75 0 00-1.06-1.06l-1.591 1.59a.75.75 0 101.06 1.061l1.591-1.59zM21.75 12a.75.75 0 01-.75.75h-2.25a.75.75 0 010-1.5H21a.75.75 0 01.75.75zM17.834 18.894a.75.75 0 001.06-1.06l-1.59-1.591a.75.75 0 10-1.061 1.06l1.59 1.591zM12 18a.75.75 0 01.75.75V21a.75.75 0 01-1.5 0v-2.25A.75.75 0 0112 18zM7.758 17.303a.75.75 0 00-1.061-1.06l-1.591 1.59a.75.75 0 001.06 1.061l1.591-1.59zM6 12a.75.75 0 01-.75.75H3a.75.75 0 010-1.5h2.25A.75.75 0 016 12zM6.697 7.757a.75.75 0 001.06-1.06l-1.59-1.591a.75.75 0 00-1.061 1.06l1.59 1.591z"/>
81
+ </svg>
82
+ </button>
83
+ </div>
84
+ </nav>
85
+ `;
86
+
87
+ // Theme toggle functionality
88
+ const themeToggle = this.shadowRoot.getElementById('themeToggle');
89
+ themeToggle.addEventListener('click', () => {
90
+ const html = document.documentElement;
91
+ const current = html.getAttribute('data-theme') || 'auto';
92
+ const next = current === 'auto' ? 'dark' : current === 'dark' ? 'light' : 'auto';
93
+ html.setAttribute('data-theme', next);
94
+ localStorage.setItem('theme-preference', next);
95
+ });
96
+ }
97
+ }
98
+ customElements.define('custom-navbar', CustomNavbar);
index.html CHANGED
@@ -1,19 +1,502 @@
1
- <!doctype html>
2
  <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
  <html>
3
+
4
+ <head>
5
+ <meta charset="utf-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Explorador de Conhecimentos</title>
8
+ <script type="importmap">
9
+ {
10
+ "imports": {
11
+ "@google/genai": "https://esm.sh/@google/genai@^0.7.0",
12
+ "vis-network/standalone": "https://esm.sh/vis-network@9.1.9/standalone/esm/vis-network.min.js"
13
+ }
14
+ }
15
+ </script>
16
+ <!-- Add Google Fonts Link -->
17
+ <link rel="preconnect" href="https://fonts.googleapis.com">
18
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
19
+ <link
20
+ href="https://fonts.googleapis.com/css2?family=Google+Sans:wght@400;500;700&family=Roboto:wght@400;500&display=swap"
21
+ rel="stylesheet">
22
+
23
+ <style>
24
+ :root {
25
+ --light-bg: #f8f9fa;
26
+ --dark-bg: #202124;
27
+ --light-surface: #ffffff;
28
+ --dark-surface: #2d2e30;
29
+ --light-text-primary: #202124;
30
+ --dark-text-primary: #e8eaed;
31
+ --light-text-secondary: #5f6368;
32
+ --dark-text-secondary: #bdc1c6;
33
+ --light-primary: #1a73e8;
34
+ --dark-primary: #8ab4f8;
35
+ --light-primary-hover: #185abc;
36
+ --dark-primary-hover: #aecbfa;
37
+ --light-border: #dadce0;
38
+ --dark-border: #5f6368;
39
+ --light-error: #d93025;
40
+ --dark-error: #f28b82;
41
+ --delete-red: #d93025;
42
+ }
43
+
44
+ body {
45
+ font-family: 'Google Sans', Roboto, Arial, sans-serif;
46
+ margin: 0;
47
+ background-color: light-dark(var(--light-bg), var(--dark-bg));
48
+ color: light-dark(var(--light-text-primary), var(--dark-text-primary));
49
+ display: flex;
50
+ flex-direction: column;
51
+ height: 100vh;
52
+ overflow: hidden;
53
+ }
54
+
55
+ .main-layout {
56
+ display: flex;
57
+ flex-direction: column;
58
+ height: 100%;
59
+ padding: 20px;
60
+ box-sizing: border-box;
61
+ gap: 20px;
62
+ }
63
+
64
+ .controls-container {
65
+ background-color: light-dark(var(--light-surface), var(--dark-surface));
66
+ padding: 20px 30px;
67
+ border-radius: 12px;
68
+ box-shadow: 0 1px 3px light-dark(rgba(60, 64, 67, 0.15), rgba(0, 0, 0, 0.3));
69
+ flex-shrink: 0;
70
+ }
71
+
72
+ h1 {
73
+ margin: 0 0 10px 0;
74
+ color: light-dark(var(--light-primary), var(--dark-primary));
75
+ font-size: 1.8em;
76
+ }
77
+
78
+ p {
79
+ margin: 0 0 20px 0;
80
+ color: light-dark(var(--light-text-secondary), var(--dark-text-secondary));
81
+ }
82
+
83
+ .input-group {
84
+ display: flex;
85
+ gap: 15px;
86
+ align-items: center;
87
+ flex-wrap: wrap;
88
+ }
89
+
90
+ input[type="text"] {
91
+ flex-grow: 1;
92
+ padding: 12px 15px;
93
+ border: 1px solid light-dark(var(--light-border), var(--dark-border));
94
+ border-radius: 8px;
95
+ font-size: 16px;
96
+ background-color: light-dark(var(--light-bg), var(--dark-surface));
97
+ color: light-dark(var(--light-text-primary), var(--dark-text-primary));
98
+ min-width: 250px;
99
+ }
100
+
101
+ input[type="text"]:focus {
102
+ outline: none;
103
+ border-color: light-dark(var(--light-primary), var(--dark-primary));
104
+ box-shadow: 0 0 0 2px light-dark(rgba(26, 115, 232, 0.2), rgba(138, 180, 248, 0.3));
105
+ }
106
+
107
+ button {
108
+ background-color: light-dark(var(--light-primary), var(--dark-primary));
109
+ color: #fff;
110
+ padding: 12px 24px;
111
+ border: none;
112
+ border-radius: 8px;
113
+ cursor: pointer;
114
+ font-size: 15px;
115
+ font-weight: 500;
116
+ transition: opacity 0.2s;
117
+ white-space: nowrap;
118
+ }
119
+
120
+ @media (prefers-color-scheme: dark) {
121
+ button {
122
+ color: var(--dark-bg);
123
+ }
124
+ }
125
+
126
+ button:hover {
127
+ opacity: 0.9;
128
+ }
129
+
130
+ button:disabled {
131
+ background-color: light-dark(#e0e0e0, #3c4043);
132
+ color: light-dark(#a0a0a0, #7f8184);
133
+ cursor: not-allowed;
134
+ }
135
+
136
+ button.secondary-button {
137
+ background-color: transparent;
138
+ border: 1px solid light-dark(var(--light-border), var(--dark-border));
139
+ color: light-dark(var(--light-text-primary), var(--dark-text-primary));
140
+ }
141
+
142
+ button.secondary-button:hover {
143
+ background-color: light-dark(rgba(0, 0, 0, 0.05), rgba(255, 255, 255, 0.05));
144
+ }
145
+
146
+ .status-message {
147
+ margin-top: 10px;
148
+ min-height: 20px;
149
+ font-size: 14px;
150
+ color: light-dark(var(--light-text-secondary), var(--dark-text-secondary));
151
+ font-style: italic;
152
+ }
153
+
154
+ .network-container {
155
+ flex-grow: 1;
156
+ background-color: light-dark(var(--light-surface), var(--dark-surface));
157
+ border-radius: 12px;
158
+ border: 1px solid light-dark(var(--light-border), var(--dark-border));
159
+ box-shadow: 0 1px 3px light-dark(rgba(60, 64, 67, 0.15), rgba(0, 0, 0, 0.3));
160
+ position: relative;
161
+ overflow: hidden;
162
+ }
163
+
164
+ div.vis-tooltip {
165
+ background-color: light-dark(var(--light-surface), var(--dark-surface));
166
+ color: light-dark(var(--light-text-primary), var(--dark-text-primary));
167
+ border: 1px solid light-dark(var(--light-border), var(--dark-border));
168
+ border-radius: 6px;
169
+ padding: 10px;
170
+ font-family: 'Google Sans', sans-serif;
171
+ font-size: 14px;
172
+ max-width: 300px;
173
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
174
+ z-index: 100;
175
+ white-space: normal;
176
+ word-wrap: break-word;
177
+ }
178
+ </style>
179
+ </head>
180
+
181
+ <body>
182
+
183
+ <div class="main-layout">
184
+ <div class="controls-container">
185
+ <h1>Explorador de Conhecimentos</h1>
186
+ <div class="input-group">
187
+ <input type="password" id="apiKeyInput" placeholder="Insira sua API Key">
188
+ <input type="text" id="topicInput" placeholder="Insira um tópico (ex: Física Quântica)">
189
+ <button id="generateButton">Iniciar Exploração</button>
190
+ <button id="resetButton" class="secondary-button">Limpar Gráfico</button>
191
+ </div>
192
+ <div id="statusMessage" class="status-message"></div>
193
+ </div>
194
+
195
+ <div id="network" class="network-container"></div>
196
+ </div>
197
+
198
+ <script type="module">
199
+ import { GoogleGenAI } from '@google/genai';
200
+ import { Network, DataSet } from 'vis-network/standalone';
201
+
202
+ // --- DOM Elements ---
203
+ const topicInput = document.getElementById('topicInput');
204
+ const apiKeyInput = document.getElementById('apiKeyInput');
205
+ const generateButton = document.getElementById('generateButton');
206
+ const resetButton = document.getElementById('resetButton');
207
+ const networkContainer = document.getElementById('network');
208
+ const statusMessage = document.getElementById('statusMessage');
209
+
210
+ // --- Initialization ---
211
+ let ai = null;
212
+ let network = null;
213
+ let nodes = new DataSet([]);
214
+ let edges = new DataSet([]);
215
+ const LOCAL_STORAGE_KEY = 'gemini_knowledge_graph_v1';
216
+ const API_KEY_STORAGE_KEY = 'gemini_api_key';
217
+
218
+ // Initialize network options
219
+ const options = {
220
+ nodes: {
221
+ shape: 'dot',
222
+ size: 25,
223
+ font: {
224
+ size: 16,
225
+ face: 'Google Sans',
226
+ },
227
+ borderWidth: 2,
228
+ shadow: true,
229
+ },
230
+ edges: {
231
+ width: 2,
232
+ color: { inherit: 'from' },
233
+ smooth: {
234
+ type: 'continuous',
235
+ },
236
+ },
237
+ physics: {
238
+ stabilization: false,
239
+ barnesHut: {
240
+ gravitationalConstant: -8000,
241
+ springConstant: 0.04,
242
+ springLength: 95,
243
+ },
244
+ },
245
+ interaction: {
246
+ hover: true,
247
+ tooltipDelay: 200,
248
+ },
249
+ };
250
+
251
+ // --- Logic ---
252
+
253
+ function initNetwork() {
254
+ const data = {
255
+ nodes: nodes,
256
+ edges: edges,
257
+ };
258
+ network = new Network(networkContainer, data, options);
259
+
260
+ // Event Listener: Click on Node
261
+ network.on('click', async (params) => {
262
+ if (params.nodes.length > 0) {
263
+ const nodeId = params.nodes[0];
264
+ const clickedNode = nodes.get(nodeId);
265
+ await expandNode(clickedNode);
266
+ }
267
+ });
268
+
269
+ // Handle Resize
270
+ window.addEventListener('resize', () => {
271
+ if (network) network.fit();
272
+ });
273
+ }
274
+
275
+ function saveGraph() {
276
+ const data = {
277
+ nodes: nodes.get(),
278
+ edges: edges.get(),
279
+ };
280
+ localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(data));
281
+ }
282
+
283
+ function loadGraph() {
284
+ const saved = localStorage.getItem(LOCAL_STORAGE_KEY);
285
+ if (saved) {
286
+ try {
287
+ const parsed = JSON.parse(saved);
288
+ nodes.clear();
289
+ edges.clear();
290
+ nodes.add(parsed.nodes);
291
+ edges.add(parsed.edges);
292
+
293
+ // If network doesn't exist yet, init it
294
+ if (!network) {
295
+ initNetwork();
296
+ } else {
297
+ network.fit();
298
+ }
299
+ return true;
300
+ } catch (e) {
301
+ console.error('Failed to parse saved graph', e);
302
+ return false;
303
+ }
304
+ }
305
+ return false;
306
+ }
307
+
308
+ // Generate Prompt Helper
309
+ async function fetchRelatedConcepts(topic) {
310
+ if (!ai) {
311
+ throw new Error('API Key not set. Please enter your Gemini API Key.');
312
+ }
313
+
314
+ const systemInstruction = `You are a knowledge graph generator.
315
+ Your task is to identify key sub-concepts, facts, or related entities for a given topic.
316
+ Return the output strictly as a JSON array of objects.
317
+ Each object must have a "label" (short name, max 3-4 words) and a "description" (2-3 concise sentences explaining the connection to the topic).
318
+ Do not include Markdown formatting or code blocks. Just the raw JSON.
319
+ Generate between 5 and 7 items.`;
320
+
321
+ const prompt = `Topic: "${topic}"`;
322
+
323
+ try {
324
+ const response = await ai.models.generateContent({
325
+ model: 'gemini-2.5-flash',
326
+ contents: prompt,
327
+ config: {
328
+ responseMimeType: 'application/json',
329
+ systemInstruction: systemInstruction,
330
+ },
331
+ });
332
+
333
+ const text = response.text || '[]';
334
+ // Basic cleanup in case model adds markdown despite instructions
335
+ const jsonStr = text.replace(/```json/g, '').replace(/```/g, '').trim();
336
+ return JSON.parse(jsonStr);
337
+ } catch (error) {
338
+ console.error('API Error:', error);
339
+ throw new Error('Falha ao buscar conceitos do Gemini.');
340
+ }
341
+ }
342
+
343
+ // Initial Graph Generation
344
+ async function startNewGraph() {
345
+ const topic = topicInput.value.trim();
346
+ const apiKey = apiKeyInput.value.trim();
347
+
348
+ if (!apiKey) {
349
+ statusMessage.textContent = 'Insira sua API Key.';
350
+ return;
351
+ }
352
+
353
+ if (!topic) {
354
+ statusMessage.textContent = 'Insira um tópico.';
355
+ return;
356
+ }
357
+
358
+ // Save API Key
359
+ localStorage.setItem(API_KEY_STORAGE_KEY, apiKey);
360
+
361
+ // Initialize AI with the provided key
362
+ ai = new GoogleGenAI({ apiKey: apiKey });
363
+
364
+ statusMessage.textContent = 'Gerando gráfico de conhecimentos...';
365
+ generateButton.disabled = true;
366
+
367
+ try {
368
+ // Clear current data
369
+ nodes.clear();
370
+ edges.clear();
371
+ localStorage.removeItem(LOCAL_STORAGE_KEY);
372
+
373
+ // Create Root Node
374
+ const rootId = crypto.randomUUID();
375
+ nodes.add({
376
+ id: rootId,
377
+ label: topic,
378
+ title: `Root Topic: ${topic}`,
379
+ color: { background: '#1a73e8', border: '#0d47a1' },
380
+ font: { color: '#ffffff' },
381
+ size: 40,
382
+ });
383
+
384
+ if (!network) initNetwork();
385
+
386
+ // Fetch children
387
+ const concepts = await fetchRelatedConcepts(topic);
388
+
389
+ // Add children
390
+ const newNodes = [];
391
+ const newEdges = [];
392
+
393
+ concepts.forEach((c) => {
394
+ const id = crypto.randomUUID();
395
+ newNodes.push({
396
+ id: id,
397
+ label: c.label,
398
+ title: c.description,
399
+ color: { background: '#e8f0fe', border: '#1a73e8' }, // Light blue
400
+ });
401
+ newEdges.push({ from: rootId, to: id });
402
+ });
403
+
404
+ nodes.add(newNodes);
405
+ edges.add(newEdges);
406
+ saveGraph();
407
+ statusMessage.textContent = 'Gráfico gerado! Clique nos nós para expandir.';
408
+
409
+ } catch (err) {
410
+ statusMessage.textContent = 'Erro ao gerar gráfico. Tente novamente.';
411
+ } finally {
412
+ generateButton.disabled = false;
413
+ }
414
+ }
415
+
416
+ // Expand existing node
417
+ async function expandNode(parentNode) {
418
+ if (statusMessage.textContent?.includes('Expandindo')) return;
419
+
420
+ const topic = parentNode.label;
421
+ statusMessage.textContent = `Expandindo "${topic}"...`;
422
+ networkContainer.style.cursor = 'aguarde';
423
+
424
+ try {
425
+ const concepts = await fetchRelatedConcepts(topic);
426
+
427
+ const newNodes = [];
428
+ const newEdges = [];
429
+
430
+ // Get existing connected nodes to avoid direct duplicates on this specific branch
431
+ const connectedNodeIds = network.getConnectedNodes(parentNode.id);
432
+ const existingLabels = new Set(connectedNodeIds.map((id) => nodes.get(id).label.toLowerCase()));
433
+
434
+ let addedCount = 0;
435
+ concepts.forEach((c) => {
436
+ if (existingLabels.has(c.label.toLowerCase())) return;
437
+
438
+ const id = crypto.randomUUID();
439
+ newNodes.push({
440
+ id: id,
441
+ label: c.label,
442
+ title: c.description,
443
+ color: { background: '#fce8e6', border: '#c5221f' }, // Light red/pinkish for sub-branches
444
+ });
445
+ newEdges.push({ from: parentNode.id, to: id });
446
+ addedCount++;
447
+ });
448
+
449
+ if (addedCount === 0) {
450
+ statusMessage.textContent = `Nenhum novo conceito distinto encontrado para "${topic}".`;
451
+ } else {
452
+ nodes.add(newNodes);
453
+ edges.add(newEdges);
454
+ saveGraph();
455
+ statusMessage.textContent = `Adicionado ${addedCount} novos conceitos conectados a "${topic}".`;
456
+ }
457
+
458
+ } catch (err) {
459
+ console.error(err);
460
+ statusMessage.textContent = `Error expanding "${topic}".`;
461
+ } finally {
462
+ networkContainer.style.cursor = 'default';
463
+ }
464
+ }
465
+
466
+ // --- Event Handlers ---
467
+ generateButton.addEventListener('click', startNewGraph);
468
+
469
+ resetButton.addEventListener('click', () => {
470
+ nodes.clear();
471
+ edges.clear();
472
+ localStorage.removeItem(LOCAL_STORAGE_KEY);
473
+ topicInput.value = '';
474
+ statusMessage.textContent = 'Graph cleared.';
475
+ });
476
+
477
+ topicInput.addEventListener('keypress', (e) => {
478
+ if (e.key === 'Enter') {
479
+ startNewGraph();
480
+ }
481
+ });
482
+
483
+ // Load on startup
484
+ const savedKey = localStorage.getItem(API_KEY_STORAGE_KEY);
485
+ if (savedKey) {
486
+ apiKeyInput.value = savedKey;
487
+ }
488
+
489
+ if (loadGraph()) {
490
+ statusMessage.textContent = 'Restaurado sessão anterior.';
491
+ // Re-init AI if we have a key, so expansion works immediately
492
+ if (savedKey) {
493
+ ai = new GoogleGenAI({ apiKey: savedKey });
494
+ }
495
+ } else {
496
+ initNetwork();
497
+ }
498
+ </script>
499
+ <script src="https://huggingface.co/deepsite/deepsite-badge.js"></script>
500
+ </body>
501
+
502
+ </html>