Skip to content

Commit ccd331b

Browse files
committed
ai mode updates
1 parent c5f856b commit ccd331b

5 files changed

Lines changed: 335 additions & 39 deletions

File tree

_posts/2026-02-24-product_principles.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,14 @@ Building often requires more _doing_ than _thinking_ (although for highly techni
186186

187187
Launching is inherently a _communicating_ activity--effectively you're communicating your solution to the market--however, it does require _thinking_ about your marketing strategy and some _doing_ to prepare materials, etc.
188188

189+
**Speed**
190+
191+
Don't forget about speed!
192+
193+
Speed will help you compensate for any imperfection in the steps above, so don't get wrapped up in the details.
194+
195+
Speed is possibly the only path to winning in today's AI-powered/ accelerated world.
196+
189197
---
190198

191199
I'm sure this will keep evolving. The frameworks that stick around are the ones that prove useful when you're actually in the weeds — making real tradeoffs under pressure. I'll update this as my thinking develops.

ai-mode.html

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
<a href="{{ '/' | relative_url }}" class="back-button">← Back</a>
1010
</div>
1111

12+
<button id="reset-counters-btn" class="reset-counters-btn hidden">Reset Limits</button>
13+
<button id="near-limit-btn" class="reset-counters-btn hidden">Near Limit</button>
14+
1215
<div class="search-container" style="margin-top: 50px;">
1316
<h1 style="font-weight: 300; margin-bottom: 25px;">AI Mode</h1>
1417
<!--<p class="search-description">Ask about any products I've worked on or things I've written</p>-->
@@ -61,16 +64,15 @@ <h1 style="font-weight: 300; margin-bottom: 25px;">AI Mode</h1>
6164
</div>
6265
</div>
6366

64-
<div class="mode-toggle hidden">
65-
<button id="mode-search" class="mode-btn active">Search</button>
67+
<div class="mode-toggle">
68+
<button id="mode-search" class="mode-btn">Search</button>
6669
<span class="mode-divider">|</span>
67-
<button id="mode-chat" class="mode-btn">Chat</button>
70+
<button id="mode-chat" class="mode-btn active" title="Switch to chat mode">Chat</button>
6871
</div>
6972

7073
<div id="search-results" class="search-results hidden" style="margin-top: 25px;"></div>
7174

72-
<div id="query-counter" class="query-counter hidden"></div>
73-
<div id="chat-counter" class="query-counter hidden"></div>
75+
<div id="usage-counter" class="usage-counter"></div>
7476
</div>
7577
</div>
7678

assets/css/custom.css

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1226,6 +1226,57 @@ ul.post-list,
12261226
font-weight: 500;
12271227
}
12281228

1229+
.usage-counter {
1230+
font-size: 0.9rem;
1231+
color: #666;
1232+
margin-top: 1rem;
1233+
margin-bottom: 0;
1234+
}
1235+
1236+
.usage-counter.limit-reached {
1237+
color: #ac4142;
1238+
font-weight: 500;
1239+
}
1240+
1241+
.mode-btn.disabled {
1242+
opacity: 0.5;
1243+
cursor: not-allowed;
1244+
pointer-events: auto;
1245+
}
1246+
1247+
.mode-btn.disabled:hover {
1248+
opacity: 0.7;
1249+
}
1250+
1251+
.reset-counters-btn {
1252+
position: fixed;
1253+
top: 10px;
1254+
right: 10px;
1255+
padding: 8px 12px;
1256+
background: white;
1257+
color: #ac4142;
1258+
border: 1px solid #ac4142;
1259+
border-radius: 6px;
1260+
font-size: 0.8rem;
1261+
cursor: pointer;
1262+
z-index: 1000;
1263+
opacity: 0.8;
1264+
transition: opacity 0.2s, background-color 0.2s;
1265+
}
1266+
1267+
.reset-counters-btn:hover {
1268+
opacity: 1;
1269+
background-color: #fff5f5;
1270+
}
1271+
1272+
.reset-counters-btn.hidden {
1273+
display: none;
1274+
}
1275+
1276+
#near-limit-btn {
1277+
top: 52px;
1278+
}
1279+
12291280
/* Results Styles */
12301281
.search-results {
12311282
text-align: left;
@@ -1348,17 +1399,24 @@ ul.post-list,
13481399
/* Error and No Results */
13491400
.error-message,
13501401
.no-results {
1351-
text-align: center;
1352-
padding: 3rem 1rem;
1353-
background: #f8f9fa;
1354-
border-radius: 10px;
1355-
margin-top: 2rem;
1402+
text-align: left;
1403+
padding: 1rem;
1404+
background: #fff5f5;
1405+
border: 1px solid #fee;
1406+
border-radius: 12px;
1407+
margin: 1rem 0;
13561408
}
13571409

13581410
.error-message h3,
13591411
.no-results h3 {
13601412
color: #ac4142;
1361-
margin-bottom: 1rem;
1413+
margin-bottom: 0.5rem;
1414+
}
1415+
1416+
.error-message p,
1417+
.no-results p {
1418+
margin: 0;
1419+
line-height: 1.6;
13621420
}
13631421

13641422
/* Mobile Responsive */

assets/js/chat.js

Lines changed: 141 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const AI_MODE_SUGGESTIONS = [
77

88
class AIChat {
99
constructor() {
10-
this.maxChatsPerDay = 3;
10+
this.maxChatsPerDay = 5;
1111
this.isLocalhost = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1';
1212
this.useMockData = new URLSearchParams(window.location.search).get('mock') === 'true';
1313
this.useLocal = new URLSearchParams(window.location.search).get('local') === 'true';
@@ -16,6 +16,7 @@ class AIChat {
1616
: 'https://api-tmfarrell.netlify.app';
1717
this.messages = [];
1818
this.isStreaming = false;
19+
this.hasUsedAI = false;
1920
this.init();
2021
}
2122

@@ -26,7 +27,113 @@ class AIChat {
2627
this.bindSuggestionsToggle();
2728
this.showSuggestions();
2829
this.showSuggestionsToggle();
29-
this.updateChatCounter();
30+
this.checkChatAvailability();
31+
this.updateUsageCounter();
32+
}
33+
34+
checkChatAvailability() {
35+
const { count } = this.getQueryCount();
36+
if (count >= this.maxChatsPerDay) {
37+
const chatBtn = document.getElementById('mode-chat');
38+
39+
if (chatBtn) {
40+
chatBtn.disabled = true;
41+
chatBtn.classList.add('disabled');
42+
chatBtn.title = "You've hit your chat limit for the day";
43+
}
44+
45+
this.activateSearchMode();
46+
} else {
47+
this.activateChatMode();
48+
}
49+
}
50+
51+
activateChatMode() {
52+
const searchBtn = document.getElementById('mode-search');
53+
const chatBtn = document.getElementById('mode-chat');
54+
const searchForm = document.getElementById('search-form');
55+
const chatForm = document.getElementById('chat-form');
56+
const searchSuggestions = document.getElementById('search-suggestions');
57+
const chatSuggestions = document.getElementById('chat-suggestions');
58+
const chatMessages = document.getElementById('chat-messages');
59+
const searchResults = document.getElementById('search-results');
60+
61+
chatBtn?.classList.add('active');
62+
searchBtn?.classList.remove('active');
63+
chatForm?.classList.remove('hidden');
64+
searchForm?.classList.add('hidden');
65+
chatSuggestions?.classList.remove('hidden');
66+
searchSuggestions?.classList.add('hidden');
67+
chatMessages?.classList.remove('hidden');
68+
searchResults?.classList.add('hidden');
69+
}
70+
71+
activateSearchMode() {
72+
const searchBtn = document.getElementById('mode-search');
73+
const chatBtn = document.getElementById('mode-chat');
74+
const searchForm = document.getElementById('search-form');
75+
const chatForm = document.getElementById('chat-form');
76+
const searchResults = document.getElementById('search-results');
77+
const searchSuggestions = document.getElementById('search-suggestions');
78+
const chatMessages = document.getElementById('chat-messages');
79+
const chatSuggestions = document.getElementById('chat-suggestions');
80+
const chatCounter = document.getElementById('chat-counter');
81+
const usageCounter = document.getElementById('usage-counter');
82+
83+
searchBtn?.classList.add('active');
84+
chatBtn?.classList.remove('active');
85+
searchForm?.classList.remove('hidden');
86+
chatForm?.classList.add('hidden');
87+
searchSuggestions?.classList.remove('hidden');
88+
chatSuggestions?.classList.add('hidden');
89+
searchResults?.classList.add('hidden');
90+
chatMessages?.classList.add('hidden');
91+
if (chatCounter) chatCounter.classList.add('hidden');
92+
if (usageCounter) usageCounter.classList.remove('hidden');
93+
}
94+
95+
updateUsageCounter() {
96+
const { count } = this.getQueryCount();
97+
const searchRemaining = this.getSearchRemaining();
98+
const remaining = Math.max(0, this.maxChatsPerDay - count);
99+
const usageCounter = document.getElementById('usage-counter');
100+
101+
if (!usageCounter) return;
102+
103+
if (this.hasUsedAI) {
104+
if (searchRemaining > 0 && remaining > 0) {
105+
usageCounter.textContent = `${searchRemaining} search${searchRemaining === 1 ? '' : 'es'}, ${remaining} chat${remaining === 1 ? '' : 's'} remaining today`;
106+
usageCounter.className = 'usage-counter';
107+
} else if (searchRemaining > 0) {
108+
usageCounter.textContent = `${searchRemaining} search${searchRemaining === 1 ? '' : 'es'} remaining today`;
109+
usageCounter.className = 'usage-counter';
110+
} else if (remaining > 0) {
111+
usageCounter.textContent = `${remaining} chat${remaining === 1 ? '' : 's'} remaining today`;
112+
usageCounter.className = 'usage-counter';
113+
} else {
114+
usageCounter.textContent = 'Daily limits reached';
115+
usageCounter.className = 'usage-counter limit-reached';
116+
}
117+
} else {
118+
usageCounter.textContent = '';
119+
}
120+
}
121+
122+
getSearchRemaining() {
123+
const today = new Date().toDateString();
124+
const stored = localStorage.getItem('search_queries');
125+
let searchCount = 0;
126+
127+
if (stored) {
128+
try {
129+
const data = JSON.parse(stored);
130+
if (data.date === today) {
131+
searchCount = data.count;
132+
}
133+
} catch (error) {}
134+
}
135+
136+
return Math.max(0, 20 - searchCount);
30137
}
31138

32139
bindModeToggle() {
@@ -55,6 +162,11 @@ class AIChat {
55162
searchResults?.classList.add('hidden');
56163
chatMessages?.classList.add('hidden');
57164
chatCounter?.classList.add('hidden');
165+
const usageCounter = document.getElementById('usage-counter');
166+
if (usageCounter && chatInstance?.hasUsedAI) {
167+
usageCounter.classList.remove('hidden');
168+
chatInstance.updateUsageCounter();
169+
}
58170
};
59171

60172
const activateChat = () => {
@@ -71,6 +183,8 @@ class AIChat {
71183
chatMessages?.classList.remove('hidden');
72184
searchResults?.classList.add('hidden');
73185
queryCounter?.classList.add('hidden');
186+
const usageCounter = document.getElementById('usage-counter');
187+
if (usageCounter) usageCounter.classList.remove('hidden');
74188
this.updateChatCounter();
75189
};
76190

@@ -139,12 +253,15 @@ class AIChat {
139253
queryData.count += 1;
140254
localStorage.setItem('chat_queries', JSON.stringify(queryData));
141255

256+
this.hasUsedAI = true;
257+
142258
const counterEl = document.getElementById('chat-counter');
143259
if (counterEl) {
144260
counterEl.classList.remove('hidden');
145261
}
146262

147263
this.updateChatCounter();
264+
this.updateUsageCounter();
148265
}
149266

150267
updateChatCounter() {
@@ -160,9 +277,16 @@ class AIChat {
160277
} else {
161278
counterEl.textContent = 'Daily chat limit reached';
162279
counterEl.className = 'query-counter limit-reached';
280+
281+
const chatBtn = document.getElementById('mode-chat');
282+
if (chatBtn) {
283+
chatBtn.disabled = true;
284+
chatBtn.classList.add('disabled');
285+
chatBtn.title = "You've hit your chat limit for the day";
286+
}
163287
}
164288

165-
counterEl.classList.remove('hidden');
289+
this.updateUsageCounter();
166290
}
167291

168292
showSuggestions() {
@@ -245,17 +369,17 @@ class AIChat {
245369

246370
if (!message) return;
247371

248-
const isUnlimited = this.useMockData || this.useLocal;
249372
const { count } = this.getQueryCount();
250-
if (count >= this.maxChatsPerDay && !isUnlimited) {
251-
this.showError("You've hit your limit for the day.<br>Feel free to email tfarrell01@gmail.com with any other questions!");
373+
if (count >= this.maxChatsPerDay) {
374+
this.showError("You've hit your chat limit for the day.<br>Feel free to email tfarrell01@gmail.com with any other questions!");
252375
return;
253376
}
254377

255378
this.hideSuggestions();
256-
if (!isUnlimited) {
257-
this.incrementQueryCount();
258-
}
379+
this.incrementQueryCount();
380+
381+
const newCount = this.getQueryCount().count;
382+
const atLimit = newCount >= this.maxChatsPerDay;
259383

260384
this.addMessage('user', message);
261385
input.value = '';
@@ -300,8 +424,16 @@ class AIChat {
300424
throw new Error('Empty response from chat service');
301425
}
302426
}
427+
428+
if (atLimit) {
429+
responseText += '\n\n---\n\nP.S. You\'ve hit your chat limit for the day. Feel free to use search mode or email tfarrell01@gmail.com with any other questions!';
430+
}
303431

304432
await this.streamResponse(responseText);
433+
434+
if (atLimit) {
435+
this.updateChatCounter();
436+
}
305437

306438
} catch (error) {
307439
console.error('Chat error:', error);

0 commit comments

Comments
 (0)