I’ve been thinking about AI a lot this week. Since ChatGPT exploded onto the scene, leading the way for a slew of AI-based tools and services, I have been trying to figure out how to use it in my work and deepen my understanding of things. Companies are looking to disrupt all areas of tech with AI tools, and it’s impossible to keep up with the changes.
When it comes to using AI tools like ChatGPT, I find the most value in using them to get me to the rough draft state of an idea. I have yet to have AI generate something so new and profound that my mind is blown. However, if I feed it enough information and a general concept, it can get me past my typical early point of failure. Sometimes, it’s easier to create something if you can get a head start, even if that head start is wrong.
The catalyst for thought this week came about after watching a news report about an AI chatbot site that convinced a boy to take his life. About four months ago, I blocked the same site from my home network because one of my kids found himself exploring the site.
The problem with the race to develop the best AI tools is that little or no attention is being paid to safety measures. I’m pretty sure this is how we end up with a real-life Skynet. Further research about this chatbot site led me to an article from Futurism, where they tested just how far this chatbot site would go before offering help or support to the user (https://futurism.com/suicide-chatbots-character-ai).
As a parent, I feel strongly about protecting my kids from things that can cause them damage. Keeping our kids safe online can be a full-time job. I want my kids to understand how to traverse this landscape safely, knowing they must guard their hearts and minds.
I want to know how you’ve used AI for work or play. If you care to share, use the comment section below this post. I’m also still trying to dial in my voice here in these posts, so if anything strikes you as interesting, I would appreciate the feedback.
Weekly Debrief
Fitness
Cycling: 120 miles
Running: 4 miles
Weights: 120 minutes
Biggest Insight
I have some notes from this week but nothing I can narrow down into a significant insight. Here are some quotes I thought about this week
No one will teach you how to live your own life. ~Insight from Claude.ai
Action completes the cycle of building up and breaking down thoughts. ~Caroline Leaf
I don’t have to be right all of the time; I just need to be right in a big way a few times a year. ~Jack D. Schwager
A crowded mind leaves no space for a peaceful heart. ~Joseph Nguyen
Wins
Lots of miles logged
Good conversations at Bible study this week
Cohen’s Basketball game (7th grade), major comeback in 4th period
Discussion with Josh over coffee
Both of my sons having good friend time over the weekend
A few years ago, I switched from paper books to Kindle but found my way back to books in print. I felt that the Kindle left me disconnected from my reading content. You can’t simply thumb through a Kindle book.
I missed the ability to display my Kindle highlights as a widget on my phone’s home screen. I was using Readwise for that. Readwise makes it easy to view your Kindle highlights in the app and display random highlights in a widget on your phone’s home screen. Readwise allows for manual highlights entry, but if I have to manually enter them, I might as well use Notion.
After a few search requests, I landed on a script written by Mystery123SK. However, the script did not work with the Notion database format that I had created, so I began making changes to the code. I used to manually write code but have not had to exercise that part of my brain much in the last decade. This allowed me to use the latest version of Claude.AI from Anthropic.
I have seen many examples of using AI to generate code, but this was my first attempt. I asked Claude.ai to write a simple script that would bring in database content from Notion and display it in a widget that Scriptable (an iOS app) would generate from the code. It took me a few hours working with Claud to get it right, but I finally got the code to run correctly and display in a Scriptable widget.
Through my years of using Notion, I have always wanted my data to be formatted to make it easy to export into something else. That means using the Name field for the quote itself. I also wanted to use Select fields to sort and filter within Notion.
Here is what you need to create your own Notion Book Quotes Widget
Your unique Secret Integration API Token – See below
The Code
Copy this code into a new script in the scriptable app. It is best to view this post from a mobile browser so you can copy the code and paste it into the app.
// Notion settings
const databaseID = 'YOUR_DATABASE_ID';
const token = 'YOUR_SECRET_TOKEN';
const notionVersion = '2022-06-28';
const notionApi = `https://api.notion.com/v1/databases/${databaseID}/query`;
// Main function
(async () => {
let quote = await readNotionQuote();
if (!quote) {
console.log('No quote found');
return;
}
let widget = await createWidget(quote.text, quote.author, quote.book, quote.tags);
if (config.runsInWidget) {
Script.setWidget(widget);
} else {
await widget.presentMedium();
}
Script.complete();
})();
async function readNotionQuote() {
let req = new Request(notionApi);
req.method = 'POST';
req.headers = {
"Authorization": `Bearer ${token}`,
"Notion-Version": notionVersion,
"Content-Type": "application/json"
};
req.body = JSON.stringify({
page_size: 100
});
let res = await req.loadJSON();
if (!res.results || res.results.length === 0) {
return null;
}
let quotes = res.results.map(page => {
let quoteText = '';
if (page.properties.title && Array.isArray(page.properties.title.title)) {
quoteText = page.properties.title.title.map(t => t.plain_text).join('');
} else if (page.properties.Quote && Array.isArray(page.properties.Quote.title)) {
quoteText = page.properties.Quote.title.map(t => t.plain_text).join('');
}
let authorName = page.properties.Author?.select?.name || '';
let bookTitle = page.properties.Book?.select?.name || '';
let rating = (page.properties.Rating?.select?.name || '').length; // Count stars in the rating
let tags = page.properties.Tags?.multi_select?.map(tag => tag.name) || [];
return {
text: quoteText,
author: authorName,
book: bookTitle,
rating: rating,
tags: tags
};
}).filter(quote => quote.text.trim() !== '' && quote.author.trim() !== '');
if (quotes.length === 0) {
return null;
}
// Weighted random selection based on rating
let totalWeight = quotes.reduce((sum, quote) => sum + Math.pow(2, quote.rating), 0);
let randomWeight = Math.random() * totalWeight;
let weightSum = 0;
for (let quote of quotes) {
weightSum += Math.pow(2, quote.rating);
if (weightSum > randomWeight) {
return quote;
}
}
// Fallback to random selection if something goes wrong
return quotes[Math.floor(Math.random() * quotes.length)];
}
async function createWidget(text, author, book, tags) {
let widget = new ListWidget();
widget.backgroundColor = new Color("#1C1C1E");
let quoteText = widget.addText(`"${text}"`);
quoteText.centerAlignText();
quoteText.font = Font.boldSystemFont(16);
quoteText.textColor = new Color("#FFFFFF");
quoteText.minimumScaleFactor = 0.5;
widget.addSpacer(8);
let authorText = widget.addText(`- ${author}`);
authorText.centerAlignText();
authorText.font = Font.boldSystemFont(14);
authorText.textColor = new Color("#AAAAAA");
authorText.minimumScaleFactor = 0.5;
if (book) {
widget.addSpacer(4);
let bookText = widget.addText(book);
bookText.centerAlignText();
bookText.font = Font.italicSystemFont(12);
bookText.textColor = new Color("#AAAAAA");
bookText.minimumScaleFactor = 0.5;
}
if (tags.length > 0) {
widget.addSpacer(4);
let tagsText = widget.addText(`Tags: ${tags.join(', ')}`);
tagsText.centerAlignText();
tagsText.font = Font.systemFont(12);
tagsText.textColor = new Color("#888888");
tagsText.minimumScaleFactor = 0.5;
}
widget.refreshAfterDate = new Date(Date.now() + 1000 * 60 * 60);
return widget;
}
When I have time, I will likely commit this code to Github, a better platform for sharing code than this post.
Feel free to copy the code into Scriptable to create your own widget. Replace “YOUR_DATABASE_ID” and “YOUR_SECRET_TOKEN” with your database and secret token.
How to find your Notion Database ID
How to create a Notion API Token
You must create a specific Notion API Token for the script to connect to.
Connect the Notion API Token to your Quotes Database
After you create the integration, you must connect the database to it.
Your Own Custom Book Quotes Widget
Congrats, you now have your custom book quotes widget based on quotes saved in a Notion database.
Troubleshooting
The first time I ran my code, nothing happened. I had Claude add in error reporting. That allowed me to identify that the script was looking for an incorrect field name. The error received was: “No Quote Found.” That is the default error, which can mean anything. I went down the rabbit hole of asking Claude to add in more detailed error reporting but that simply added a ton of bloat to the code and didn’t help, so I started over.
Ensure your Database ID, Secret Token, and Database Field Names are correct.
Check that your database is connected to the correct integration.
I will update this post as I have more time. I will also produce a setup tutorial soon.