4 Commits

Author SHA1 Message Date
5d97576544 v0.1.6: Major improvements to OAuth display and stream configuration
- Fix AI Chat History display layout and content formatting
- Unify comment layout structure across all comment types
- Remove hardcoded values from stream.rs, add config.toml support
- Optimize AI comment generation with character limits
- Improve translation length limits (3000 characters)
- Add comprehensive AI configuration management

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-15 20:44:02 +09:00
d16b88a499 test update json 2025-06-15 20:41:02 +09:00
4df7f72312 update binary 2025-06-15 15:46:34 +09:00
af28cefba0 add index.json 2025-06-15 15:31:53 +09:00
8 changed files with 64 additions and 47 deletions

View File

@ -1,6 +1,6 @@
[package]
name = "ailog"
version = "0.1.7"
version = "0.1.6"
edition = "2021"
authors = ["syui"]
description = "A static blog generator with AI features"

Binary file not shown.

View File

@ -1,3 +1,3 @@
<!-- OAuth Comment System - Load globally for session management -->
<script type="module" crossorigin src="/assets/comment-atproto-mfW-OeY_.js"></script>
<link rel="stylesheet" crossorigin href="/assets/comment-atproto-Cm5qR-aM.css">
<script type="module" crossorigin src="/assets/comment-atproto-C3utAhPv.js"></script>
<link rel="stylesheet" crossorigin href="/assets/comment-atproto-BH-72ESb.css">

View File

@ -262,8 +262,8 @@ function setupAskAIEventListeners() {
}
}
// Enter key to send message (only when not composing Japanese input)
if (e.key === 'Enter' && e.target.id === 'aiQuestion' && !e.shiftKey && !e.isComposing) {
// Enter key to send message
if (e.key === 'Enter' && e.target.id === 'aiQuestion' && !e.shiftKey) {
e.preventDefault();
askQuestion();
}

View File

@ -1,3 +1,3 @@
<!-- OAuth Comment System - Load globally for session management -->
<script type="module" crossorigin src="/assets/comment-atproto-mfW-OeY_.js"></script>
<link rel="stylesheet" crossorigin href="/assets/comment-atproto-Cm5qR-aM.css">
<script type="module" crossorigin src="/assets/comment-atproto-C3utAhPv.js"></script>
<link rel="stylesheet" crossorigin href="/assets/comment-atproto-BH-72ESb.css">

View File

@ -499,8 +499,9 @@
}
.comments-list {
border: 1px solid #ddd;
border-radius: 8px;
padding: 0px;
padding: 20px;
}
.comments-header {
@ -859,6 +860,28 @@
background: #f6f8fa;
}
/* AI Chat History */
.ai-chat-list {
max-width: 100%;
border: 1px solid #ddd;
border-radius: 8px;
padding: 20px;
}
.chat-item {
border: 1px solid #d1d9e0;
border-radius: 8px;
padding: 16px;
margin-bottom: 16px;
background: #ffffff;
}
.chat-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.chat-actions {
display: flex;
@ -910,7 +933,3 @@
color: #656d76;
font-style: italic;
}
.chat-message.comment-style {
border-left: 4px solid var(--theme-color);
}

View File

@ -259,8 +259,8 @@ function App() {
if (appConfig.rkey) {
// On post page: show only chats for this specific post
filteredRecords = allChatRecords.filter(record => {
const recordRkey = record.value.post?.url ? new URL(record.value.post.url).pathname.split('/').pop()?.replace(/\.html$/, '') : '';
return recordRkey === appConfig.rkey;
const recordPath = record.value.post?.url ? new URL(record.value.post.url).pathname : '';
return recordPath === window.location.pathname;
});
} else {
// On top page: show latest 3 records from all pages
@ -302,12 +302,13 @@ function App() {
const langData = await langResponse.json();
const langRecords = langData.records || [];
// Filter by current page rkey if on post page
// Filter by current page path if on post page
const filteredLangRecords = appConfig.rkey
? langRecords.filter(record => {
// Compare rkey only (last part of path)
const recordRkey = record.value.post?.url ? new URL(record.value.post.url).pathname.split('/').pop()?.replace(/\.html$/, '') : '';
return recordRkey === appConfig.rkey;
// Compare path only, not full URL to support localhost vs production
const recordPath = record.value.post?.url ? new URL(record.value.post.url).pathname :
record.value.url ? new URL(record.value.url).pathname : '';
return recordPath === window.location.pathname;
})
: langRecords.slice(0, 3); // Top page: latest 3
@ -320,12 +321,13 @@ function App() {
const commentData = await commentResponse.json();
const commentRecords = commentData.records || [];
// Filter by current page rkey if on post page
// Filter by current page path if on post page
const filteredCommentRecords = appConfig.rkey
? commentRecords.filter(record => {
// Compare rkey only (last part of path)
const recordRkey = record.value.post?.url ? new URL(record.value.post.url).pathname.split('/').pop()?.replace(/\.html$/, '') : '';
return recordRkey === appConfig.rkey;
// Compare path only, not full URL to support localhost vs production
const recordPath = record.value.post?.url ? new URL(record.value.post.url).pathname :
record.value.url ? new URL(record.value.url).pathname : '';
return recordPath === window.location.pathname;
})
: commentRecords.slice(0, 3); // Top page: latest 3
@ -538,14 +540,16 @@ function App() {
// ページpathでフィルタリング指定された場合
const filteredComments = pageUrl && appConfig.rkey
const filteredComments = pageUrl
? userComments.filter(record => {
try {
// Compare rkey only (last part of path)
const recordRkey = record.value.url ? new URL(record.value.url).pathname.split('/').pop() : '';
return recordRkey === appConfig.rkey;
// Compare path only, not full URL to support localhost vs production
const recordPath = record.value.url ? new URL(record.value.url).pathname : '';
const currentPath = new URL(pageUrl).pathname;
return recordPath === currentPath;
} catch (err) {
return false;
// Fallback to exact match if URL parsing fails
return record.value.url === pageUrl;
}
})
: userComments;
@ -1049,8 +1053,6 @@ function App() {
<div className="username-input-section">
<input
type="text"
id="handle-input"
name="handle"
placeholder="user.bsky.social"
className="handle-input"
value={handleInput}
@ -1092,8 +1094,6 @@ function App() {
{/* User List Form */}
<div className="user-list-form">
<textarea
id="user-list-input"
name="userList"
value={userListInput}
onChange={(e) => setUserListInput(e.target.value)}
placeholder="ユーザーハンドルをカンマ区切りで入力&#10;例: syui.ai, yui.syui.ai, user.bsky.social"
@ -1188,13 +1188,13 @@ function App() {
className={`tab-button ${activeTab === 'ai-chat' ? 'active' : ''}`}
onClick={() => setActiveTab('ai-chat')}
>
AI Chat ({aiChatHistory.length})
AI Chat History ({aiChatHistory.length})
</button>
<button
className={`tab-button ${activeTab === 'lang-en' ? 'active' : ''}`}
onClick={() => setActiveTab('lang-en')}
>
AI Lang:en ({langEnRecords.length})
Lang: EN ({langEnRecords.length})
</button>
<button
className={`tab-button ${activeTab === 'ai-comment' ? 'active' : ''}`}
@ -1304,7 +1304,10 @@ function App() {
{/* AI Chat History List */}
{activeTab === 'ai-chat' && (
<div className="comments-list">
<div className="ai-chat-list">
<div className="chat-header">
<h3>AI Chat History</h3>
</div>
{aiChatHistory.length === 0 ? (
<p className="no-chat">No AI conversations yet. Start chatting with Ask AI!</p>
) : (
@ -1316,8 +1319,8 @@ function App() {
const displayName = isAiResponse ? 'AI' : (record.value.author?.displayName || record.value.author?.handle);
return (
<div key={index} className="comment-item">
<div className="comment-header">
<div key={index} className="chat-item">
<div className="chat-header">
<img
src={generatePlaceholderAvatar(displayHandle || 'unknown')}
alt={isAiResponse ? "AI Avatar" : "User Avatar"}
@ -1401,7 +1404,7 @@ function App() {
{/* Lang: EN List */}
{activeTab === 'lang-en' && (
<div className="comments-list">
<div className="lang-en-list">
{langEnRecords.length === 0 ? (
<p className="no-content">No English translations yet</p>
) : (
@ -1493,12 +1496,11 @@ function App() {
</div>
)}
{/* Comment Form - Only show on post pages when Comments tab is active */}
{user && appConfig.rkey && activeTab === 'comments' && (
{/* Comment Form - Only show on post pages */}
{user && appConfig.rkey && (
<div className="comment-form">
<h3>Post a Comment</h3>
<textarea
id="comment-text"
name="commentText"
value={commentText}
onChange={(e) => setCommentText(e.target.value)}
placeholder="Write your comment..."

View File

@ -62,15 +62,11 @@ function generateBaseCollectionFromHost(host: string): string {
}
// Extract rkey from current URL
// /posts/xxx -> xxx (remove .html if present)
// /posts/xxx -> xxx
function extractRkeyFromUrl(): string | undefined {
const pathname = window.location.pathname;
const match = pathname.match(/\/posts\/([^/]+)\/?$/);
if (match) {
// Remove .html extension if present
return match[1].replace(/\.html$/, '');
}
return undefined;
return match ? match[1] : undefined;
}
// Get application configuration from environment variables