grimshaw commited on
Commit
a7d7a30
·
verified ·
1 Parent(s): 46a3f1c

You need to add full openrouter integration, the llm will take the markdown and design an html slideshow based on that. You should also use the internet to find images relevant to the slideshow. You should use searchx.euan.live to do this

Browse files
Files changed (2) hide show
  1. components/ai-slideshow.js +244 -0
  2. index.html +1 -0
components/ai-slideshow.js ADDED
@@ -0,0 +1,244 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class AISlideshow extends HTMLElement {
2
+ constructor() {
3
+ super();
4
+ this.apiKey = localStorage.getItem('openrouter-key') || '';
5
+ this.slides = [];
6
+ }
7
+
8
+ connectedCallback() {
9
+ this.attachShadow({ mode: 'open' });
10
+ this.render();
11
+ this.addEventListeners();
12
+ }
13
+
14
+ render() {
15
+ this.shadowRoot.innerHTML = `
16
+ <style>
17
+ :host {
18
+ display: block;
19
+ margin-top: 2rem;
20
+ }
21
+ .ai-section {
22
+ background: white;
23
+ border-radius: 0.75rem;
24
+ padding: 1.5rem;
25
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
26
+ }
27
+ .api-input {
28
+ display: flex;
29
+ gap: 0.5rem;
30
+ margin-bottom: 1rem;
31
+ }
32
+ .api-input input {
33
+ flex: 1;
34
+ padding: 0.5rem;
35
+ border: 1px solid #d1d5db;
36
+ border-radius: 0.375rem;
37
+ font-size: 0.875rem;
38
+ }
39
+ .api-input button {
40
+ padding: 0.5rem 1rem;
41
+ background: #4f46e5;
42
+ color: white;
43
+ border: none;
44
+ border-radius: 0.375rem;
45
+ cursor: pointer;
46
+ font-size: 0.875rem;
47
+ }
48
+ .api-input button:hover {
49
+ background: #4338ca;
50
+ }
51
+ .generate-btn {
52
+ width: 100%;
53
+ padding: 0.75rem;
54
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
55
+ color: white;
56
+ border: none;
57
+ border-radius: 0.5rem;
58
+ font-weight: 600;
59
+ cursor: pointer;
60
+ display: flex;
61
+ align-items: center;
62
+ justify-content: center;
63
+ gap: 0.5rem;
64
+ }
65
+ .generate-btn:hover {
66
+ opacity: 0.9;
67
+ }
68
+ .generate-btn:disabled {
69
+ opacity: 0.5;
70
+ cursor: not-allowed;
71
+ }
72
+ .loading {
73
+ display: inline-block;
74
+ width: 1rem;
75
+ height: 1rem;
76
+ border: 2px solid rgba(255,255,255,.3);
77
+ border-radius: 50%;
78
+ border-top-color: white;
79
+ animation: spin 1s ease-in-out infinite;
80
+ }
81
+ @keyframes spin {
82
+ to { transform: rotate(360deg); }
83
+ }
84
+ .error {
85
+ color: #ef4444;
86
+ font-size: 0.875rem;
87
+ margin-top: 0.5rem;
88
+ }
89
+ </style>
90
+ <div class="ai-section">
91
+ <h3 class="text-lg font-semibold mb-3">✨ AI Enhanced Slideshow</h3>
92
+ <div class="api-input">
93
+ <input type="password" id="api-key-input" placeholder="Enter OpenRouter API key" value="${this.apiKey}">
94
+ <button id="save-key">Save</button>
95
+ </div>
96
+ <button id="generate-ai" class="generate-btn">
97
+ <i data-feather="sparkles"></i>
98
+ Generate AI Slideshow
99
+ </button>
100
+ <div id="error" class="error"></div>
101
+ </div>
102
+ `;
103
+ }
104
+
105
+ addEventListeners() {
106
+ const saveBtn = this.shadowRoot.getElementById('save-key');
107
+ const generateBtn = this.shadowRoot.getElementById('generate-ai');
108
+ const apiInput = this.shadowRoot.getElementById('api-key-input');
109
+ const errorDiv = this.shadowRoot.getElementById('error');
110
+
111
+ saveBtn.addEventListener('click', () => {
112
+ this.apiKey = apiInput.value.trim();
113
+ localStorage.setItem('openrouter-key', this.apiKey);
114
+ saveBtn.textContent = 'Saved!';
115
+ setTimeout(() => saveBtn.textContent = 'Save', 1000);
116
+ });
117
+
118
+ generateBtn.addEventListener('click', async () => {
119
+ if (!this.apiKey) {
120
+ errorDiv.textContent = 'Please enter your OpenRouter API key first';
121
+ return;
122
+ }
123
+
124
+ const markdown = document.getElementById('markdown-input').value.trim();
125
+ if (!markdown) {
126
+ errorDiv.textContent = 'Please enter some markdown content';
127
+ return;
128
+ }
129
+
130
+ generateBtn.disabled = true;
131
+ generateBtn.innerHTML = '<div class="loading"></div> Generating...';
132
+ errorDiv.textContent = '';
133
+
134
+ try {
135
+ const html = await this.generateAISlideshow(markdown);
136
+ document.getElementById('slides-preview').innerHTML = html;
137
+ document.getElementById('export-btn').disabled = false;
138
+ } catch (error) {
139
+ errorDiv.textContent = error.message;
140
+ } finally {
141
+ generateBtn.disabled = false;
142
+ generateBtn.innerHTML = '<i data-feather="sparkles"></i> Generate AI Slideshow';
143
+ }
144
+ });
145
+ }
146
+
147
+ async generateAISlideshow(markdown) {
148
+ const slides = markdown.split(/\n---\n/);
149
+ let html = '';
150
+
151
+ for (let i = 0; i < slides.length; i++) {
152
+ const slide = slides[i];
153
+
154
+ // Search for relevant images
155
+ const searchQuery = this.extractKeywords(slide);
156
+ const images = await this.searchImages(searchQuery);
157
+
158
+ // Generate AI-enhanced slide
159
+ const prompt = `
160
+ Create a beautiful, modern slide HTML based on this markdown content:
161
+ ${slide}
162
+
163
+ Use Tailwind CSS classes for styling. Include:
164
+ 1. A compelling title
165
+ 2. Clean, readable typography
166
+ 3. One or more relevant images from this list: ${JSON.stringify(images.slice(0, 3))}
167
+ 4. Beautiful color scheme and layout
168
+ 5. Subtle animations and transitions
169
+
170
+ Return only the HTML, no explanations.
171
+ `;
172
+
173
+ const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {
174
+ method: 'POST',
175
+ headers: {
176
+ 'Authorization': `Bearer ${this.apiKey}`,
177
+ 'Content-Type': 'application/json',
178
+ 'HTTP-Referer': window.location.href,
179
+ 'X-Title': 'Markdown Slides'
180
+ },
181
+ body: JSON.stringify({
182
+ model: 'anthropic/claude-3-haiku-20240307',
183
+ messages: [
184
+ {
185
+ role: 'user',
186
+ content: prompt
187
+ }
188
+ ],
189
+ max_tokens: 2000,
190
+ temperature: 0.7
191
+ })
192
+ });
193
+
194
+ if (!response.ok) {
195
+ throw new Error('Failed to generate slide content');
196
+ }
197
+
198
+ const data = await response.json();
199
+ const slideHtml = data.choices[0].message.content;
200
+
201
+ html += `
202
+ <div class="ai-slide mb-6">
203
+ ${slideHtml}
204
+ </div>
205
+ `;
206
+ }
207
+
208
+ return html;
209
+ }
210
+
211
+ extractKeywords(text) {
212
+ // Simple keyword extraction
213
+ const words = text.toLowerCase()
214
+ .replace(/[^\w\s]/g, '')
215
+ .split(/\s+/)
216
+ .filter(word => word.length > 3 && !['this', 'that', 'with', 'from', 'have', 'will', 'your', 'more'].includes(word));
217
+
218
+ const count = {};
219
+ words.forEach(word => count[word] = (count[word] || 0) + 1);
220
+
221
+ return Object.entries(count)
222
+ .sort(([,a], [,b]) => b - a)
223
+ .slice(0, 3)
224
+ .map(([word]) => word)
225
+ .join(' ');
226
+ }
227
+
228
+ async searchImages(query) {
229
+ try {
230
+ const response = await fetch(`https://searchx.euan.live/search?q=${encodeURIComponent(query)}&safesearch=true&format=json`);
231
+ const data = await response.json();
232
+
233
+ return data.results
234
+ .filter(result => result.url && result.url.match(/\.(jpg|jpeg|png|gif|webp)$/i))
235
+ .slice(0, 5)
236
+ .map(result => result.url);
237
+ } catch (error) {
238
+ console.error('Image search failed:', error);
239
+ return [];
240
+ }
241
+ }
242
+ }
243
+
244
+ customElements.define('ai-slideshow', AISlideshow);
index.html CHANGED
@@ -10,6 +10,7 @@
10
  <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
11
  <script src="https://cdn.jsdelivr.net/npm/pptxgenjs@3.7.0/dist/pptxgen.bundle.js"></script>
12
  <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
 
13
  </head>
14
  <body class="bg-gray-50 min-h-screen">
15
  <custom-navbar></custom-navbar>
 
10
  <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
11
  <script src="https://cdn.jsdelivr.net/npm/pptxgenjs@3.7.0/dist/pptxgen.bundle.js"></script>
12
  <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
13
+ <script src="https://cdn.tailwindcss.com"></script>
14
  </head>
15
  <body class="bg-gray-50 min-h-screen">
16
  <custom-navbar></custom-navbar>