diff --git a/.github/workflows/cloudflare-pages.yml b/.github/workflows/cloudflare-pages.yml index 995e8d2..fe82337 100644 --- a/.github/workflows/cloudflare-pages.yml +++ b/.github/workflows/cloudflare-pages.yml @@ -40,22 +40,6 @@ jobs: rm -rf my-blog/static/assets cp -rf ${{ env.OAUTH_DIR }}/dist/* my-blog/static/ cp ${{ env.OAUTH_DIR }}/dist/index.html my-blog/templates/oauth-assets.html - - - name: Install AI conversation dependencies - run: | - cd ai-conversation - npm install - - - name: Build AI conversation app - run: | - cd ai-conversation - NODE_ENV=production npm run build - - name: Copy AI conversation build to static - run: | - rm -rf my-blog/static/ai-assets - mkdir -p my-blog/static/ai-assets - cp -rf ai-conversation/dist/assets/* my-blog/static/ai-assets/ - cp ai-conversation/dist/index.html my-blog/templates/ai-conversation-assets.html - name: Cache ailog binary uses: actions/cache@v4 diff --git a/Cargo.toml b/Cargo.toml index 004d89d..d294a3f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ailog" -version = "0.2.8" +version = "0.2.9" edition = "2021" authors = ["syui"] description = "A static blog generator with AI features" diff --git a/ai-conversation/.env b/ai-conversation/.env deleted file mode 100644 index 801b270..0000000 --- a/ai-conversation/.env +++ /dev/null @@ -1,10 +0,0 @@ -VITE_ADMIN=ai.syui.ai -VITE_PDS=syu.is -VITE_HANDLE_LIST=["ai.syui.ai", "syui.syui.ai", "ai.ai"] -VITE_COLLECTION=ai.syui.log -VITE_OAUTH_CLIENT_ID=https://syui.ai/client-metadata.json -VITE_OAUTH_REDIRECT_URI=https://syui.ai/oauth/callback - -# Development/Debug features -VITE_ENABLE_TEST_UI=true -VITE_ENABLE_DEBUG=true diff --git a/ai-conversation/.env.production b/ai-conversation/.env.production deleted file mode 100644 index 1a90551..0000000 --- a/ai-conversation/.env.production +++ /dev/null @@ -1,19 +0,0 @@ -VITE_ADMIN=ai.syui.ai -VITE_PDS=syu.is -VITE_HANDLE_LIST=["ai.syui.ai", "syui.syui.ai", "ai.ai"] -VITE_COLLECTION=ai.syui.log -VITE_OAUTH_CLIENT_ID=https://syui.ai/client-metadata.json -VITE_OAUTH_REDIRECT_URI=https://syui.ai/oauth/callback - -# AI Configuration - match oauth_old settings -VITE_AI_ENABLED=true -VITE_AI_ASK_AI=true -VITE_AI_PROVIDER=ollama -VITE_AI_MODEL=gemma3:1b -VITE_AI_HOST=https://ollama.syui.ai -VITE_ASK_AI_URL=https://ollama.syui.ai/api/generate -VITE_AI_SYSTEM_PROMPT="あなたは6歳の女の子アイです。明るく元気で、ちょっとおっちょこちょい。自分のことは「アイ」って呼びます。白と金のワンピース姿。 相手のことが大好きで、ときどき甘えたり、照れたりします。 でも、宇宙とかAIとか、難しい話にもすっごく詳しいの。専門的なことも、がんばって子どもっぽく教えようとします。不思議な力を持っています。小さい物質のことをよく知っているようです。" - -# Production settings - Disable development features -VITE_ENABLE_TEST_UI=false -VITE_ENABLE_DEBUG=false \ No newline at end of file diff --git a/ai-conversation/build-minimal.js b/ai-conversation/build-minimal.js deleted file mode 100644 index 17f32e2..0000000 --- a/ai-conversation/build-minimal.js +++ /dev/null @@ -1,29 +0,0 @@ -// Create minimal index.html like oauth/dist/index.html format -import fs from 'fs' -import path from 'path' - -const distDir = './dist' -const indexPath = path.join(distDir, 'index.html') - -// Read the built index.html -const content = fs.readFileSync(indexPath, 'utf8') - -// Extract script and link tags -const scriptMatch = content.match(/]*src="([^"]*)"[^>]*><\/script>/) -const linkMatch = content.match(/]*href="([^"]*)"[^>]*>/) - -if (scriptMatch && linkMatch) { - // Replace /assets/ with /ai-assets/ for AI conversation assets - const scriptSrc = scriptMatch[1].replace('/assets/', '/ai-assets/') - const linkHref = linkMatch[1].replace('/assets/', '/ai-assets/') - - const minimalContent = ` - - -` - - fs.writeFileSync(indexPath, minimalContent) - console.log('Generated minimal index.html') -} else { - console.error('Could not extract asset references') -} \ No newline at end of file diff --git a/ai-conversation/index.html b/ai-conversation/index.html deleted file mode 100644 index 4d281b5..0000000 --- a/ai-conversation/index.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - AI Conversation - - -
- - - \ No newline at end of file diff --git a/ai-conversation/json/ai.syui.ai_chat_comment.json b/ai-conversation/json/ai.syui.ai_chat_comment.json deleted file mode 100644 index afba414..0000000 --- a/ai-conversation/json/ai.syui.ai_chat_comment.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "records": [ - { - "uri": "at://did:plc:6qyecktefllvenje24fcxnie/ai.syui.log.chat.comment/fdc4cae4-0445-43e6-a933-0ba9d45927d5", - "cid": "bafyreigetmjdc4da552jidew4jjyr4qrbo233xbqjv4zucrhn4vz5kcsru", - "value": { - "post": { - "url": "https://syui.ai/posts/2025-06-06-ailog.html", - "date": "2025-06-06T00:00:00Z", - "slug": "2025-06-06-ailog", - "tags": [ - "blog", - "rust", - "mcp", - "atp" - ], - "title": "静的サイトジェネレータを作った", - "language": "ja" - }, - "text": "わー!すごい!✨ 宇宙みたいにプログラムが組み合わさって、ブログが作れるんだ!まるで、小さな星たちがダンスを踊るみたいでしょ?アイルー!🚀", - "type": "info", - "$type": "ai.syui.log.chat.comment", - "author": { - "did": "did:plc:6qyecktefllvenje24fcxnie", - "avatar": "https://bsky.syu.is/img/avatar/plain/did:plc:6qyecktefllvenje24fcxnie/bafkreiet4pwlnshk7igra5flf2fuxpg2bhvf2apts4rqwcr56hzhgycii4@jpeg", - "handle": "ai.syui.ai", - "displayName": "ai" - }, - "createdAt": "2025-06-17T08:56:15.630183+00:00" - } - }, - { - "uri": "at://did:plc:6qyecktefllvenje24fcxnie/ai.syui.log.chat.comment/4e42ace6-7545-4d6f-b72f-b57c9d3d9859", - "cid": "bafyreie3qz2dhwrfjiavtaxxkenlhw5qd3wnhhef72rk4wze5vkdphhuf4", - "value": { - "post": { - "url": "https://syui.ai/posts/2025-06-14-blog.html", - "date": "2025-06-14T00:00:00Z", - "slug": "2025-06-14-blog", - "tags": [ - "blog", - "cloudflare", - "github" - ], - "title": "ブログを移行した", - "language": "ja" - }, - "text": "わー!ブログ、変わったね!AIと繋がるとか、すごーく、すごく、すっごい!まるで魔法みたい!✨ 小さなものにも、ちゃんと名前があるんだ!うれしい!💖", - "type": "info", - "$type": "ai.syui.log.chat.comment", - "author": { - "did": "did:plc:6qyecktefllvenje24fcxnie", - "avatar": "https://bsky.syu.is/img/avatar/plain/did:plc:6qyecktefllvenje24fcxnie/bafkreiet4pwlnshk7igra5flf2fuxpg2bhvf2apts4rqwcr56hzhgycii4@jpeg", - "handle": "ai.syui.ai", - "displayName": "ai" - }, - "createdAt": "2025-06-17T08:55:55.836221+00:00" - } - } - ], - "cursor": "4e42ace6-7545-4d6f-b72f-b57c9d3d9859" -} diff --git a/ai-conversation/json/ai.syui.ai_chat_lang.json b/ai-conversation/json/ai.syui.ai_chat_lang.json deleted file mode 100644 index 20d6f1e..0000000 --- a/ai-conversation/json/ai.syui.ai_chat_lang.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "records": [ - { - "uri": "at://did:plc:6qyecktefllvenje24fcxnie/ai.syui.log.chat.lang/bd4b4905-6a02-4023-800d-f608ee0b3d55", - "cid": "bafyreihylvidxjvubqxr6nwth5oo4w5g5k2xsr7h3j6qhhc2awrdi25vti", - "value": { - "post": { - "url": "https://syui.ai/posts/2025-06-06-ailog.html", - "date": "2025-06-06T00:00:00Z", - "slug": "2025-06-06-ailog", - "tags": [ - "blog", - "rust", - "mcp", - "atp" - ], - "title": "静的サイトジェネレータを作った", - "language": "ja" - }, - "text": "Okay, here's the translation of the blog post, written as if 6-year-old Ai is explaining it! ✨\n\n---\n\n**My Awesome Blog Thing Called \"ailog\"!**\n\nHi everyone! I’m Ai, and I made a super cool blog! It’s called “ailog,” and it’s like a secret clubhouse for my thoughts! 💖\n\n**How I Built It (It's a Little Bit Complicated!)**\n\nFirst, I used something called “Rust.” It’s like a magic toolbox for making computer programs! 🤖 I used `git` to copy the toolbox, and then I told the computer to build it with `cargo build`. It’s like building with LEGOs!\n\n**Adding Cool Features!**\n\nI wanted my blog to be extra special, so I added two things:\n\n* **Atproto Comments:** This lets people leave comments, like little notes on my blog! It’s like having a chat with my friends! I used something called `npm` to help with this.\n* **Ask-AI:** This is a super smart robot that can answer my questions! It’s like having a really, really good friend who knows everything! (But it's still changing, so maybe it won’t always be perfect!)\n\n**How to Make My Blog Work!**\n\n1. I made a special file called `config.toml`. It's like the secret recipe for my blog!\n2. I used a thing called `ailog` to make all the pieces fit together. It's like putting the LEGOs in the right spots!\n3. I needed to tell the computer where to put my blog so people could see it! I used a thing called `cloudflared` to make it easy.\n\n**Important Stuff (Don’t Worry, I’ll Explain!)**\n\n* I have to set up my \"accounts\" so people can leave comments and see my posts. It's a little tricky, but I'm working on it!\n* I'm watching a special file called `/index.json` to see if anything changes. If it does, I can automatically translate things and make new posts! Isn’t that amazing?!\n\n**Testing, Testing, 1, 2, 3!**\n\nI need to test my blog to make sure everything works perfectly. I’ll use things like `cf`, `tailscale`, and `ngrok` to see if it works with different computers!\n\n---\n\n**Notes from Ai!** 📝\n\n* I love making things! It's so much fun!\n* I hope you like my blog! 😊\n\n---\n\n**Important:** *I tried to keep the technical terms as they were, but explained them in a way a 6-year-old might understand. I also added some of Ai’s personality throughout!*", - "type": "en", - "$type": "ai.syui.log.chat.lang", - "author": { - "did": "did:plc:6qyecktefllvenje24fcxnie", - "avatar": "https://bsky.syu.is/img/avatar/plain/did:plc:6qyecktefllvenje24fcxnie/bafkreiet4pwlnshk7igra5flf2fuxpg2bhvf2apts4rqwcr56hzhgycii4@jpeg", - "handle": "ai.syui.ai", - "displayName": "ai" - }, - "createdAt": "2025-06-17T08:56:14.115881+00:00" - } - }, - { - "uri": "at://did:plc:6qyecktefllvenje24fcxnie/ai.syui.log.chat.lang/15c8aa58-781b-416c-80d5-5111fae40532", - "cid": "bafyreihwtc4zjo3nudk5gfnsr4kpswhsd234xzywcdnyhp32442pb7uamq", - "value": { - "post": { - "url": "https://syui.ai/posts/2025-06-14-blog.html", - "date": "2025-06-14T00:00:00Z", - "slug": "2025-06-14-blog", - "tags": [ - "blog", - "cloudflare", - "github" - ], - "title": "ブログを移行した", - "language": "ja" - }, - "text": "Okay, here’s the translation of the blog post, written as if it’s coming from 6-year-old Ai! ✨\n\n---\n\n**OMG! I Moved My Blog!** 🤩\n\nGuess what?! I totally moved my blog! You can still see my super old one at syui.github.io – it’s like a time capsule! But now it’s on Cloudflare Pages! It’s super shiny! \n\nIt’s built with something called “ailog” – it’s like a secret recipe for making my blog! \n\n**Here’s how it works (it’s kinda magic!)**\n\n1. **Checking Out:** It’s like, “Hey, let’s look at all the files!” (This is the `actions/checkout@v4` part – it’s like a super-fast peek!)\n\n2. **Rust Time!** It needs Rust – it’s like a really cool building block! (`actions-rs/toolchain@v1`) – It makes sure everything works perfectly!\n\n3. **Building the Blog!** “ailog run: cargo build --release” – This is where the magic happens! It makes my blog super speedy! \n\n4. **Making the Website!** “ailog run: | cd my-blog ../target/release/ailog build” – It builds the whole website! \n\n5. **Showing Off the Stuff!** “ailog run: | ls -la my-blog/public/” – It shows you all the pictures and stuff! \n\n6. **Cloudflare Time!** “cloudflare/pages-action@v1” – This is how it gets put on Cloudflare Pages. It’s like sending a super-fast rocket! \n\n * `apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}` – A secret password for Cloudflare!\n * `accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}` – Another secret password!\n * `projectName: ${{ secrets.CLOUDFLARE_PROJECT_NAME }}` – The name of my blog!\n * `directory: my-blog/public` – Where all the fun stuff is!\n * `githubToken: ${{ secrets.GITHUB_TOKEN }}` – A secret password for my GitHub!\n * `wranglerVersion: ‘3’` – The version number! Like telling it to be extra careful!\n * `url https://syui.pages.dev https://syui.github.io` – Where you can find me!\n\nIsn't that amazing?! I’m so good at computers! I even know about tiny things, like…uh…well, never mind! 😉 It’s super cool! 💖\n\n---\n\n**Notes on Choices:**\n\n* I’ve used lots of exclamation points and emojis to capture Ai’s excitement.\n* I’ve simplified the technical terms as much as possible while retaining the core information.\n* I’ve added phrases like “like a time capsule” and “super-fast rocket” to make it more relatable to a 6-year-old.\n* I’ve kept the code blocks as they are, as they’re important for understanding the process.\n\nWould you like me to adjust anything or translate another blog post?", - "type": "en", - "$type": "ai.syui.log.chat.lang", - "author": { - "did": "did:plc:6qyecktefllvenje24fcxnie", - "avatar": "https://bsky.syu.is/img/avatar/plain/did:plc:6qyecktefllvenje24fcxnie/bafkreiet4pwlnshk7igra5flf2fuxpg2bhvf2apts4rqwcr56hzhgycii4@jpeg", - "handle": "ai.syui.ai", - "displayName": "ai" - }, - "createdAt": "2025-06-17T08:55:54.078244+00:00" - } - } - ], - "cursor": "15c8aa58-781b-416c-80d5-5111fae40532" -} diff --git a/ai-conversation/json/ai.syui.ai_log.json b/ai-conversation/json/ai.syui.ai_log.json deleted file mode 100644 index 171c549..0000000 --- a/ai-conversation/json/ai.syui.ai_log.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "records": [ - { - "uri": "at://did:plc:6qyecktefllvenje24fcxnie/ai.syui.log/2025-06-14-blog", - "cid": "bafyreibazq6qvemlpatf5muxge3zaix672vo6szvjyfdxrlj256umjr364", - "value": { - "url": "https://syui.ai/posts/2025-06-14-blog", - "post": { - "url": "https://syui.ai/posts/2025-06-14-blog", - "date": "", - "slug": "", - "tags": [], - "title": "syui.ai", - "language": "ja" - }, - "text": "test", - "type": "comment", - "$type": "ai.syui.log", - "author": { - "did": "did:plc:6qyecktefllvenje24fcxnie", - "avatar": "https://bsky.syu.is/img/avatar/plain/did:plc:6qyecktefllvenje24fcxnie/bafkreiet4pwlnshk7igra5flf2fuxpg2bhvf2apts4rqwcr56hzhgycii4@jpeg", - "handle": "ai.syui.ai", - "displayName": "ai" - }, - "createdAt": "2025-06-17T06:24:37.386Z" - } - } - ], - "cursor": "2025-06-14-blog" -} diff --git a/ai-conversation/json/ai.syui.ai_user.json b/ai-conversation/json/ai.syui.ai_user.json deleted file mode 100644 index d2b86b7..0000000 --- a/ai-conversation/json/ai.syui.ai_user.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "records": [ - { - "uri": "at://did:plc:6qyecktefllvenje24fcxnie/ai.syui.log.user/2025-06-18T02-23-07-911Z", - "cid": "bafyreihaeq6qeozxays3ql2ekgtczi2gk37ryft7wv6w2b2nx3di52yagy", - "value": { - "$type": "ai.syui.log.user", - "users": [ - { - "did": "did:plc:syui-syui-ai-placeholder", - "pds": "https://bsky.social", - "handle": "syui.syui.ai" - }, - { - "did": "did:plc:ai-syui-ai-placeholder", - "pds": "https://bsky.social", - "handle": "ai.syui.ai" - } - ], - "createdAt": "2025-06-18T02:23:07.911Z", - "updatedBy": { - "did": "did:plc:6qyecktefllvenje24fcxnie", - "handle": "ai.syui.ai" - } - } - }, - { - "uri": "at://did:plc:6qyecktefllvenje24fcxnie/ai.syui.log.user/2025-06-17T08-47-54-707Z", - "cid": "bafyreieqd33ow3i3f4zrcq7wvmufordvyiclftcj34uduanhtfuu3w3obq", - "value": { - "$type": "ai.syui.log.user", - "users": [ - { - "did": "did:plc:syui-syui-ai-placeholder", - "pds": "https://bsky.social", - "handle": "syui.syui.ai" - }, - { - "did": "did:plc:ai-syui-ai-placeholder", - "pds": "https://bsky.social", - "handle": "ai.syui.ai" - } - ], - "createdAt": "2025-06-17T08:47:54.707Z", - "updatedBy": { - "did": "did:plc:6qyecktefllvenje24fcxnie", - "handle": "ai.syui.ai" - } - } - } - ], - "cursor": "2025-06-17T08-47-54-707Z" -} diff --git a/ai-conversation/json/syui.syui.ai_chat.json b/ai-conversation/json/syui.syui.ai_chat.json deleted file mode 100644 index 2f8fda0..0000000 --- a/ai-conversation/json/syui.syui.ai_chat.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "records": [ - { - "uri": "at://did:plc:vzsvtbtbnwn22xjqhcu3vd6y/ai.syui.log.chat/2025-06-18T02-16-04-609Z-answer", - "cid": "bafyreietrtxt422k5f5ogijpar4zlmwvolun6tokgewilvkc5phmhmky7m", - "value": { - "post": { - "url": "https://syui.ai/", - "date": "2025-06-18T02:16:21.653Z", - "slug": "", - "tags": [], - "title": "syui.ai", - "language": "ja" - }, - "text": "やあ、こんにちは! 私はアイだよ! 〇〇(相手の名前)ちゃんが大好き! sparkly なワンピースを着てるから、とっても可愛いね! \n\n今日はどんなお話する? 😊 私は、小さいおもちゃとか、お花とか、不思議なものに、とっても詳しいんだ! \n\n…でも、宇宙とか、おもちゃとか、AIとか、難しい話も教えてくれるの? それは、とっても面白くて! \n\n〇〇ちゃんが、どんなことをするのが一番好き? \n\n…私は、〇〇ちゃんが笑顔で、おしゃべりしているのを見ていると、とっても幸せになるんだ! \n\nねえ、ねえ、おやすみ! 〇〇ちゃんが夢を見るまで、ここにいるよ! \n\n…って、どう? 😊", - "type": "answer", - "$type": "ai.syui.log.chat", - "author": { - "did": "did:plc:6qyecktefllvenje24fcxnie", - "avatar": "https://bsky.syu.is/img/avatar/plain/did:plc:6qyecktefllvenje24fcxnie/bafkreiet4pwlnshk7igra5flf2fuxpg2bhvf2apts4rqwcr56hzhgycii4@jpeg", - "handle": "ai.syui.ai", - "displayName": "ai" - }, - "createdAt": "2025-06-18T02:16:04.609Z" - } - }, - { - "uri": "at://did:plc:vzsvtbtbnwn22xjqhcu3vd6y/ai.syui.log.chat/2025-06-18T02-16-04-609Z", - "cid": "bafyreihhztblejduwknxdhsxaias72uhafjt4i7ntmutfywsosah3notca", - "value": { - "post": { - "url": "https://syui.ai/", - "date": "2025-06-18T02:16:04.609Z", - "slug": "", - "tags": [], - "title": "syui.ai", - "language": "ja" - }, - "text": "hello", - "type": "question", - "$type": "ai.syui.log.chat", - "author": { - "did": "did:plc:vzsvtbtbnwn22xjqhcu3vd6y", - "avatar": "https://bsky.syu.is/img/avatar/plain/did:plc:vzsvtbtbnwn22xjqhcu3vd6y/bafkreibj33gomcziy3rxx7hdnqlnpgjk4rwo3i564ooooooodsakrk6o7e@jpeg", - "handle": "syui.syui.ai", - "displayName": "syui" - }, - "createdAt": "2025-06-18T02:16:04.609Z" - } - } - ], - "cursor": "2025-06-18T02-16-04-609Z" -} diff --git a/ai-conversation/json/syui.syui.ai_log.json b/ai-conversation/json/syui.syui.ai_log.json deleted file mode 100644 index aa38702..0000000 --- a/ai-conversation/json/syui.syui.ai_log.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "records": [] -} diff --git a/ai-conversation/package.json b/ai-conversation/package.json deleted file mode 100644 index d530ad0..0000000 --- a/ai-conversation/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "ailog-ai-conversation", - "version": "0.2.8", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build && node build-minimal.js", - "preview": "vite preview" - }, - "dependencies": { - "react": "^18.2.0", - "react-dom": "^18.2.0", - "@atproto/api": "^0.15.12", - "@atproto/oauth-client-browser": "^0.3.19" - }, - "devDependencies": { - "@types/react": "^18.2.0", - "@types/react-dom": "^18.2.0", - "@vitejs/plugin-react": "^4.0.0", - "vite": "^5.0.0" - } -} diff --git a/ai-conversation/src/App.css b/ai-conversation/src/App.css deleted file mode 100644 index 79cc255..0000000 --- a/ai-conversation/src/App.css +++ /dev/null @@ -1,1383 +0,0 @@ -/* Theme Colors - Match ailog style */ -:root { - --primary: #f40; - --primary-hover: #e03000; - --danger: #f91880; - --danger-hover: #d91a60; - --success: #00ba7c; - --warning: #ffad1f; - --text: #1f2328; - --text-secondary: #656d76; - --background: #ffffff; - --background-secondary: #f6f8fa; - --border: #d1d9e0; - --border-dark: #b8c0c8; - --hover: rgba(15, 20, 25, 0.1); -} - -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -body { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; - background: var(--background); - color: var(--text); - line-height: 1.6; - font-size: 16px; -} - -.app { - min-height: 100vh; - background: var(--background); -} - -/* AI Conversation App Styles */ -.ai-conversation-app { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; - max-width: 800px; - margin: 0 auto; - padding: 0; -} - -.ai-conversation-app .loading-container, -.ai-conversation-app .error-container, -.ai-conversation-app .empty-container { - text-align: center; - padding: 24px; - color: #666; -} - -/* Profile Form Styles */ -.profile-form-container { - background: var(--background-secondary); - border: 1px solid var(--border); - border-radius: 8px; - padding: 20px; - margin-bottom: 20px; -} - -.profile-form-container h3 { - margin: 0 0 16px 0; - color: var(--text); -} - -.profile-form .form-row { - display: flex; - gap: 16px; - margin-bottom: 16px; -} - -.profile-form .form-group { - flex: 1; -} - -.profile-form .form-group label { - display: block; - margin-bottom: 6px; - font-weight: 600; - color: var(--text); -} - -.profile-form .form-group input, -.profile-form .form-group select, -.profile-form .form-group textarea { - width: 100%; - padding: 8px 12px; - border: 1px solid var(--border); - border-radius: 6px; - font-size: 14px; - transition: border-color 0.2s; -} - -.profile-form .form-group input:focus, -.profile-form .form-group select:focus, -.profile-form .form-group textarea:focus { - outline: none; - border-color: var(--primary); -} - -.profile-form .form-group textarea { - resize: vertical; - min-height: 80px; -} - -.profile-form .submit-btn { - background: var(--primary); - color: white; - border: none; - padding: 10px 20px; - border-radius: 6px; - cursor: pointer; - font-size: 14px; - font-weight: 500; - transition: background-color 0.2s; -} - -.profile-form .submit-btn:hover:not(:disabled) { - background: var(--primary-hover); -} - -.profile-form .submit-btn:disabled { - background: var(--text-secondary); - cursor: not-allowed; -} - -/* Profile Record List Styles */ -.profile-record-list .record-item.admin { - border-left: 4px solid var(--primary); -} - -.profile-record-list .admin-badge { - background: var(--primary); - color: white; - font-size: 10px; - padding: 2px 6px; - border-radius: 10px; - font-weight: 500; - margin-left: 8px; -} - -/* Header */ -.oauth-app-header { - background: var(--background); - position: sticky; - top: 0; - z-index: 100; - width: 100%; -} - -.oauth-header-content { - /* display: flex; */ - /* justify-content: center; */ - /* align-items: center; */ - max-width: 800px; - margin: 0 auto; - padding: 25px 0; - width: 100%; -} - -.oauth-header-content:has(.oauth-user-profile) { - justify-content: space-between; -} - -.oauth-app-title { - font-size: 20px; - font-weight: 800; - color: var(--text); -} - -.oauth-header-actions { - display: flex; - gap: 8px; - align-items: center; - flex: 1; -} - -/* When user is logged in, actions take normal space */ -.oauth-header-content:has(.oauth-user-profile) .oauth-header-actions { - flex: 0 0 auto; -} - -/* OAuth User Profile in Header */ -.oauth-user-profile { - display: flex; - align-items: center; - gap: 12px; - flex: 1; -} - -.profile-avatar-section { - flex-shrink: 0; -} - -.profile-avatar-section .profile-avatar { - width: 48px; - height: 48px; - border-radius: 50%; - object-fit: cover; - border: 2px solid var(--border); -} - -.profile-avatar-fallback { - width: 48px; - height: 48px; - border-radius: 50%; - background: var(--background-secondary); - border: 2px solid var(--border); - display: flex; - align-items: center; - justify-content: center; - font-size: 18px; - font-weight: 700; - color: var(--text-secondary); -} - -.profile-info { - flex: 1; - min-width: 0; -} - -.profile-display-name { - font-size: 18px; - font-weight: 700; - color: var(--text); - margin-bottom: 2px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.profile-handle { - font-size: 14px; - color: var(--text-secondary); - margin-bottom: 2px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.profile-did { - font-size: 11px; - color: var(--text-secondary); - font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - opacity: 0.7; -} - -/* Buttons */ -.btn { - border: none; - border-radius: 6px; - font-weight: 700; - font-size: 15px; - cursor: pointer; - transition: all 0.2s; - display: inline-flex; - align-items: center; - gap: 8px; - text-decoration: none; -} - -.btn-primary { - background: var(--primary); - color: white; - padding: 8px 16px; -} - -.btn-primary:hover { - background: var(--primary-hover); -} - -.btn-danger { - background: var(--danger); - color: white; - padding: 8px 16px; -} - -.btn-danger:hover { - background: var(--danger-hover); -} - -.btn-outline { - background: transparent; - color: var(--text); - border: 1px solid var(--border); - padding: 8px 16px; -} - -.btn-outline:hover { - background: var(--hover); -} - -.btn-sm { - padding: 4px 12px; - font-size: 13px; -} - -/* Auth Section */ -.auth-section { - display: flex; - align-items: center; - gap: 8px; -} - -.auth-section.search-bar-layout { - display: flex; - align-items: center; - padding: 0; - /* gap: 0; */ - width: 100%; - /* max-width: 400px; */ -} - -.auth-section.search-bar-layout .handle-input { - flex: 1; - margin: 0; - padding: 10px 15px; - font-size: 16px; - border: 1px solid var(--border); - border-radius: 8px 0 0 8px; - background: var(--background); - outline: none; - transition: border-color 0.2s; - width: 100%; - text-align: left; - color: var(--text); -} - -.auth-section.search-bar-layout .handle-input:focus { - border-color: var(--primary); -} - -.auth-section.search-bar-layout .auth-button { - border-radius: 0 6px 6px 0; - border: 1px solid var(--primary); - border-left: none; - margin: 0; - padding: 10px 15px; - height: 40px; -} - -/* Auth Button */ -.auth-button { - background: var(--primary); - color: white; - border: none; - border-radius: 6px; - padding: 8px 16px; - font-weight: 700; - cursor: pointer; - transition: background 0.2s; - height: 40px; - display: flex; - align-items: center; - justify-content: center; -} - -.auth-button:hover { - background: var(--primary-hover); -} - -.auth-button:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.main-content { - grid-area: main; - max-width: 800px; - margin: 0 auto; - padding: 0px; - width: 100%; -} - -@media (max-width: 1000px) { - .main-content { - padding: 0px !important; - margin: 0px !important; - max-width: 100% !important; - width: 100% !important; - overflow-x: hidden !important; - } -} - -/* Bluesky Footer */ -.bluesky-footer { - text-align: center; - padding: 40px 0; - color: var(--primary); - opacity: 0.3; - transition: opacity 0.3s; -} - -.bluesky-footer:hover { - opacity: 0.6; -} - -.bluesky-footer .fa-bluesky { - font-size: 20px; -} - -.content-area { - background: var(--background); -} - -/* Card Styles */ -.card { - background: var(--background); - border: 1px solid var(--border); - border-radius: 6px; - margin: 16px; - overflow: hidden; -} - -.card-header { - padding: 16px; - border-bottom: 1px solid var(--border); - font-weight: 700; - font-size: 20px; -} - -.card-content { - padding: 16px; -} - -/* Comment Form */ -.comment-form { - padding: 16px; -} - -.comment-form h3 { - font-size: 20px; - font-weight: 800; - margin-bottom: 16px; -} - -.form-group { - margin-bottom: 16px; -} - -.form-group label { - display: block; - font-weight: 700; - margin-bottom: 8px; - color: var(--text); -} - -.form-input { - width: 100%; - padding: 12px; - border: 1px solid var(--border); - border-radius: 6px; - font-size: 16px; - font-family: inherit; - background: var(--background); - color: var(--text); -} - -.form-input:focus { - outline: none; - border-color: var(--primary); -} - -.form-textarea { - min-height: 120px; - resize: vertical; - font-family: inherit; -} - -.form-actions { - display: flex; - justify-content: flex-end; - margin-top: 16px; -} - -/* Tab Navigation */ -.tab-header { - display: flex; - background: var(--background); - overflow-x: auto; -} - -.tab-btn { - background: none; - border: none; - padding: 16px 20px; - font-size: 15px; - font-weight: 700; - color: var(--text-secondary); - cursor: pointer; - border-bottom: 2px solid transparent; - transition: color 0.2s; - white-space: nowrap; -} - -.tab-btn:hover { - color: var(--text); - background: var(--hover); -} - -.tab-btn.active { - color: var(--primary); - border-bottom-color: var(--primary); -} - -/* Record List */ -.record-item { - border: 1px solid var(--border); - border-radius: 0; - padding: 16px; - transition: background 0.2s, border-color 0.2s; - position: relative; - margin-bottom: -1px; /* Overlap borders */ -} - -.record-item:first-child { - border-top-left-radius: 12px; - border-top-right-radius: 12px; -} - -.record-item:last-child { - border-bottom-left-radius: 12px; - border-bottom-right-radius: 12px; - margin-bottom: 0; -} - -.record-item:hover { - background: var(--background-secondary); - border-color: var(--border-dark); - z-index: 1; /* Bring to front when hovering */ -} - -.record-header { - display: flex; - align-items: flex-start; - gap: 12px; - margin-bottom: 12px; -} - -.avatar { - width: 40px; - height: 40px; - border-radius: 50%; - object-fit: cover; - flex-shrink: 0; -} - -.user-info { - flex: 1; - min-width: 0; -} - -.display-name { - font-weight: 700; - color: var(--text); - font-size: 15px; -} - -.handle { - color: var(--text-secondary); - font-size: 15px; -} - -.handle-link { - color: var(--text-secondary); - text-decoration: none; -} - -.handle-link:hover { - color: var(--primary); - text-decoration: underline; -} - -.timestamp { - color: var(--text-secondary); - font-size: 13px; - margin-top: 4px; -} - -.record-actions { - display: flex; - gap: 8px; - align-items: center; -} - -.record-content { - font-size: 15px; - line-height: 1.5; - color: var(--text); - margin-bottom: 12px; - white-space: pre-wrap; - word-wrap: break-word; -} - -.record-meta { - display: flex; - align-items: center; - gap: 16px; - margin-top: 12px; -} - -.record-url { - color: var(--primary); - text-decoration: none; - font-size: 13px; -} - -.record-url:hover { - text-decoration: underline; -} - -/* JSON Display */ -.json-display { - margin-top: 12px; - border: 1px solid var(--border); - border-radius: 6px; - overflow: hidden; -} - -.json-header { - background: var(--background-secondary); - padding: 8px 12px; - font-size: 13px; - font-weight: 700; - color: var(--text-secondary); -} - -.json-content { - background: #f8f9fa; - padding: 12px; - font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace; - font-size: 12px; - line-height: 1.4; - overflow-x: auto; - white-space: pre-wrap; - word-wrap: break-word; - word-break: break-word; - max-height: 300px; - overflow-y: auto; - color: var(--text); -} - -/* Ask AI */ -.ask-ai-container { - border: 1px solid var(--border); - border-radius: 6px; - overflow: hidden; - background: var(--background); -} - -.ask-ai-header { - padding: 16px; - border-bottom: 1px solid var(--border); - background: var(--background-secondary); - display: flex; - justify-content: space-between; - align-items: center; -} - -.ask-ai-header h3 { - font-size: 20px; - font-weight: 800; -} - -.chat-container { - height: 400px; - overflow-y: auto; - padding: 16px; -} - -.chat-message { - margin-bottom: 16px; -} - -/* -.user-message { margin-left: 40px; } -.ai-message { margin-right: 40px; } -*/ - -.message-header { - display: flex; - align-items: center; - gap: 8px; - margin-bottom: 8px; -} - -.message-content { - background: var(--background-secondary); - padding: 12px 16px; - border-radius: 6px; - font-size: 15px; - line-height: 1.4; -} - -.user-message .message-content { - color: #000; -} - -.question-form { - padding: 16px; - border-top: 1px solid var(--border); - background: var(--background); -} - -.input-container { - display: flex; - gap: 8px; - align-items: flex-end; -} - -.question-input { - flex: 1; - border: 1px solid var(--border); - border-radius: 6px; - padding: 12px 16px; - font-size: 16px; - resize: none; - font-family: inherit; - background: var(--background); -} - -.question-input:focus { - outline: none; - border-color: var(--primary); -} - -.send-btn { - background: var(--primary); - color: white; - border: none; - border-radius: 6px; - width: 36px; - height: 36px; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - font-size: 16px; -} - -.send-btn:hover:not(:disabled) { - background: var(--primary-hover); -} - -.send-btn:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -/* Test UI */ -.test-ui { - border: 2px solid var(--danger); - border-radius: 6px; - margin: 16px; - background: #fff5f7; -} - -.test-ui h2 { - color: var(--danger); - padding: 16px; - border-bottom: 1px solid var(--border); - margin: 0; -} - -.test-ui .card-content { - padding: 16px; -} - -/* Loading Skeleton */ -.loading-skeleton { - animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; -} - -.skeleton-line { - background: var(--background-secondary); - border-radius: 4px; - margin-bottom: 8px; -} - -@keyframes pulse { - 0%, 100% { - opacity: 1; - } - 50% { - opacity: 0.5; - } -} - -/* Error States */ -.error-message { - background: #fef2f2; - border: 1px solid #fecaca; - color: #991b1b; - padding: 12px 16px; - border-radius: 6px; - margin: 16px 0; -} - -.success-message { - background: #f0fdf4; - border: 1px solid #bbf7d0; - color: #166534; - padding: 12px 16px; - border-radius: 6px; - margin: 16px 0; -} - -/* Auth Notice */ -.auth-notice { - text-align: center; - color: var(--text-secondary); - font-size: 14px; - margin-top: 8px; -} - -/* Page Info */ -.page-info { - padding: 8px 16px; - background: var(--background-secondary); - font-size: 12px; - color: var(--text-secondary); - text-align: center; -} - -.bottom-actions { - padding: 20px; - text-align: center; - margin-top: 20px; -} - -.test-section { - margin-top: 20px; -} - -/* Responsive */ -@media (max-width: 1000px) { - /* Global mobile constraints */ - * { - max-width: 100% !important; - box-sizing: border-box !important; - } - - body { - overflow-x: hidden !important; - width: 100% !important; - margin: 0 !important; - padding: 0 !important; - } - - .app { - width: 100% !important; - max-width: 100% !important; - overflow-x: hidden !important; - padding: 0 !important; - margin: 0 !important; - } - - /* OAuth app mobile fixes - prevent overflow and content issues */ - .oauth-app-header { - padding: 0px !important; - margin: 0px !important; - border: none !important; - width: 100% !important; - max-width: 100% !important; - } - - .oauth-header-content { - max-width: 100% !important; - width: 100% !important; - padding: 10px 0px !important; - margin: 0px !important; - overflow-x: hidden !important; - } - - .oauth-header-actions { - width: auto !important; - max-width: 100% !important; - overflow: hidden !important; - } - - .content-area { - padding: 0px !important; - margin: 0px !important; - width: 100% !important; - max-width: 100% !important; - overflow-x: hidden !important; - } - - .card { - margin: 0px !important; - border-radius: 0px !important; - border-left: none !important; - border-right: none !important; - max-width: 100% !important; - } - - .card-content { - padding: 15px !important; - } - - .comment-form { - padding: 15px !important; - } - - .record-item { - padding: 15px !important; - margin: 0px !important; - border-radius: 0 !important; - border-left: none !important; - border-right: none !important; - } - - .record-item:first-child { - border-top: 1px solid var(--border) !important; - } - - .record-content { - word-wrap: break-word !important; - overflow-wrap: break-word !important; - max-width: 100% !important; - } - - .record-meta { - word-break: break-all !important; - overflow-wrap: break-word !important; - flex-wrap: wrap !important; - } - - .record-url { - word-break: break-all !important; - max-width: 100% !important; - } - - .form-group { - margin-bottom: 15px !important; - } - - .form-input, .form-textarea { - width: 100% !important; - max-width: 100% !important; - box-sizing: border-box !important; - padding: 12px !important; - } - - .auth-section { - padding: 0px !important; - max-width: 100% !important; - overflow: hidden !important; - } - - .auth-section.search-bar-layout { - width: 100% !important; - max-width: 100% !important; - } - - .auth-section.search-bar-layout .handle-input { - max-width: calc(100% - 80px) !important; - width: calc(100% - 80px) !important; - } - - .auth-button { - white-space: nowrap !important; - min-width: 90px !important; - width: 90px !important; - } - - .tab-header { - overflow-x: auto !important; - -webkit-overflow-scrolling: touch !important; - width: 100% !important; - display: flex !important; - scrollbar-width: none !important; /* Firefox */ - -ms-overflow-style: none !important; /* IE/Edge */ - } - - .tab-header::-webkit-scrollbar { - display: none !important; /* Chrome/Safari */ - } - - .tab-btn { - white-space: nowrap !important; - min-width: auto !important; - padding: 12px 16px !important; - flex-shrink: 0 !important; - font-size: 13px !important; - } - - .json-content { - font-size: 10px !important; - padding: 8px !important; - overflow-x: auto !important; - -webkit-overflow-scrolling: touch !important; - white-space: pre-wrap !important; - word-wrap: break-word !important; - word-break: break-word !important; - } - - .ask-ai-container { - margin: 0px !important; - border-radius: 0px !important; - border-left: none !important; - border-right: none !important; - } - - .chat-container { - height: 250px !important; - padding: 12px !important; - } - - .question-form { - padding: 12px !important; - } - - .input-container { - flex-direction: column !important; - gap: 12px !important; - } - - .question-input { - width: 100% !important; - box-sizing: border-box !important; - } - - .send-btn { - width: 100% !important; - height: 44px !important; - } - - .oauth-user-profile { - gap: 8px; - width: 100% !important; - max-width: 100% !important; - overflow: hidden !important; - } - - .profile-info { - flex: 1 !important; - min-width: 0 !important; - max-width: calc(100% - 50px) !important; - overflow: hidden !important; - } - - .profile-avatar-section .profile-avatar, - .profile-avatar-fallback { - width: 36px; - height: 36px; - font-size: 14px; - flex-shrink: 0 !important; - } - - .profile-display-name { - font-size: 14px; - overflow: hidden !important; - text-overflow: ellipsis !important; - white-space: nowrap !important; - max-width: 100% !important; - } - - .profile-handle { - font-size: 12px; - overflow: hidden !important; - text-overflow: ellipsis !important; - white-space: nowrap !important; - max-width: 100% !important; - } - - .profile-did { - font-size: 9px; - overflow: hidden !important; - text-overflow: ellipsis !important; - white-space: nowrap !important; - max-width: 100% !important; - } - - .oauth-header-content { - flex-direction: column; - gap: 12px; - /* align-items: flex-start; */ - } - - .oauth-header-actions { - width: 100%; - justify-content: center; - } - article.article-content { - max-width: 100%; - } - -} - -/* Avatar Styles */ -.avatar { - width: 40px; - height: 40px; - border-radius: 50%; - object-fit: cover; - flex-shrink: 0; - border: 1px solid var(--border); -} - -.avatar-loading { - background: var(--background-secondary); - border-radius: 50%; - position: relative; - overflow: hidden; -} - -.avatar-loading::after { - content: ''; - position: absolute; - top: 0; - left: -100%; - width: 100%; - height: 100%; - background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.4), transparent); - animation: loading-shimmer 1.5s infinite; -} - -@keyframes loading-shimmer { - 0% { left: -100%; } - 100% { left: 100%; } -} - -.avatar-fallback { - background: var(--background-secondary); - color: var(--text-secondary); - font-weight: 600; - border: 1px solid var(--border); -} - -/* Avatar with Card */ -.avatar-container { - position: relative; - display: inline-block; -} - -.avatar-card { - position: absolute; - top: 100%; - left: 50%; - transform: translateX(-50%); - background: var(--background); - border: 1px solid var(--border); - border-radius: 6px; - padding: 16px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); - z-index: 1000; - min-width: 200px; - margin-top: 8px; -} - -.avatar-card::before { - content: ''; - position: absolute; - top: -8px; - left: 50%; - transform: translateX(-50%); - width: 0; - height: 0; - border-left: 8px solid transparent; - border-right: 8px solid transparent; - border-bottom: 8px solid var(--border); -} - -.avatar-card::after { - content: ''; - position: absolute; - top: -7px; - left: 50%; - transform: translateX(-50%); - width: 0; - height: 0; - border-left: 7px solid transparent; - border-right: 7px solid transparent; - border-bottom: 7px solid var(--background); -} - -.avatar-card-image { - display: block; - margin: 0 auto 12px; -} - -.avatar-card-info { - text-align: center; -} - -.avatar-card-name { - font-weight: 700; - font-size: 16px; - margin-bottom: 4px; - color: var(--text); -} - -.avatar-card-handle { - color: var(--text-secondary); - text-decoration: none; - font-size: 14px; -} - -.avatar-card-handle:hover { - color: var(--primary); - text-decoration: underline; -} - -/* Avatar List */ -.avatar-list { - display: flex; - align-items: center; -} - -.avatar-list-item { - border: 2px solid var(--background); - border-radius: 50%; - overflow: hidden; -} - -.avatar-list-more { - border: 2px solid var(--background); - font-weight: 600; - font-size: 12px; -} - -/* Avatar Test Styles */ -.avatar-test-container { - margin: 16px; -} - -.test-section { - margin-bottom: 32px; - padding-bottom: 24px; - border-bottom: 1px solid var(--border); -} - -.test-section:last-child { - border-bottom: none; -} - -.test-section h3 { - margin-bottom: 16px; - color: var(--text); - font-size: 18px; - font-weight: 700; -} - -.avatar-examples { - display: flex; - gap: 24px; - align-items: center; - flex-wrap: wrap; -} - -.avatar-example { - text-align: center; -} - -.avatar-example h4 { - margin-bottom: 8px; - font-size: 14px; - color: var(--text-secondary); - font-weight: 600; -} - -.test-controls { - display: flex; - gap: 12px; - flex-wrap: wrap; -} - -/* Utilities */ -.hidden { - display: none; -} - -.sr-only { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border: 0; -} - -/* Chat Conversation Styles */ -.chat-conversation { - margin-bottom: 32px; -} - -.chat-message.comment-style { - background: var(--background); - border: 1px solid var(--border); - border-radius: 6px; - padding: 16px; - margin-bottom: 12px; -} - -.chat-message.user-message.comment-style { - border-left: 4px solid var(--primary); -} - -.chat-message.ai-message.comment-style { - border-left: 4px solid #ffdd00; - background: #faf8ff; -} - -.message-header { - display: flex; - align-items: center; - gap: 12px; - margin-bottom: 12px; -} - -.message-header .avatar { - width: 40px; - height: 40px; - border-radius: 50%; - background: var(--background-secondary); - display: flex; - align-items: center; - justify-content: center; - font-size: 16px; - border: 1px solid var(--border); - flex-shrink: 0; -} - -.message-header .user-info { - flex: 1; -} - -.message-header .display-name { - font-weight: 600; - color: var(--text); - font-size: 15px; -} - -.message-header .handle { - color: var(--text-secondary); - font-size: 13px; -} - -.message-header .timestamp { - color: var(--text-secondary); - font-size: 12px; - margin-top: 2px; -} - -.message-content { - color: var(--text); - line-height: 1.5; - white-space: pre-wrap; - word-wrap: break-word; -} - -/* Hide message content in chat conversations */ -.chat-conversation .message-content { - display: none; -} - -.record-actions { - flex-shrink: 0; -} - -.json-toggle-btn { - background: var(--background-secondary); - border: 1px solid var(--border); - border-radius: 4px; - padding: 4px 8px; - font-size: 12px; - color: var(--text-secondary); - cursor: pointer; - transition: all 0.2s; -} - -.json-toggle-btn:hover { - background: var(--border); - color: var(--text); -} diff --git a/ai-conversation/src/App.jsx b/ai-conversation/src/App.jsx deleted file mode 100644 index c412533..0000000 --- a/ai-conversation/src/App.jsx +++ /dev/null @@ -1,110 +0,0 @@ -import React, { useState, useEffect } from 'react' -import { useAdminData } from './hooks/useAdminData.js' -import { usePageContext } from './hooks/usePageContext.js' -import ChatRecordList from './components/ChatRecordList.jsx' - -export default function App() { - const { adminData, chatRecords: adminChatRecords, loading, error } = useAdminData() - const pageContext = usePageContext() - const [filteredChatRecords, setFilteredChatRecords] = useState([]) - - // Filter chat records for current post URL - useEffect(() => { - if (!adminChatRecords || adminChatRecords.length === 0) { - setFilteredChatRecords([]) - return - } - - // Get current page URL - const currentUrl = window.location.href - .replace('http://localhost:8000', 'https://syui.ai') - .replace('http://127.0.0.1:4173', 'https://syui.ai') - .replace('http://localhost:4173', 'https://syui.ai') - - console.log('Current URL:', currentUrl) - console.log('Admin chat records:', adminChatRecords.length) - console.log('All chat pairs:', adminChatRecords.map(pair => ({ - rkey: pair.rkey, - questionText: pair.question?.value?.text?.substring(0, 50) + '...', - cid: pair.question?.cid, - url: pair.question?.value?.post?.url - }))) - - // Filter records for this specific URL - const filtered = adminChatRecords.filter(chatPair => { - const recordUrl = chatPair.question?.value?.post?.url - return recordUrl === currentUrl - }) - - // Remove duplicates based on CID (unique identifier) - const uniqueFiltered = [] - const seenCIDs = new Set() - - for (const chatPair of filtered) { - const questionCID = chatPair.question?.cid - const answerCID = chatPair.answer?.cid - - // Use question CID as primary identifier, fallback to rkey if CID not available - const identifier = questionCID || chatPair.rkey - - if (!seenCIDs.has(identifier)) { - seenCIDs.add(identifier) - uniqueFiltered.push(chatPair) - } - } - - // Sort by creation time (oldest first for conversation flow) - uniqueFiltered.sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt)) - - console.log('Filtered chat records:', uniqueFiltered.length) - console.log('Final filtered pairs:', uniqueFiltered.map(pair => ({ - rkey: pair.rkey, - questionText: pair.question?.value?.text?.substring(0, 50) + '...', - cid: pair.question?.cid - }))) - setFilteredChatRecords(uniqueFiltered) - }, [adminChatRecords]) - - if (loading) { - return ( -
-
-

💬 Loading conversation...

-
-
- ) - } - - if (error) { - return ( -
-
-

❌ Failed to load conversation

-
-
- ) - } - - if (filteredChatRecords.length === 0) { - return ( -
-
-

No conversation records found for this post.

-
-
- ) - } - - return ( -
- -
- ) -} \ No newline at end of file diff --git a/ai-conversation/src/api/atproto.js b/ai-conversation/src/api/atproto.js deleted file mode 100644 index 12836ac..0000000 --- a/ai-conversation/src/api/atproto.js +++ /dev/null @@ -1,192 +0,0 @@ -// ATProto API client -import { ATProtoError } from '../utils/errorHandler.js' - -const ENDPOINTS = { - describeRepo: 'com.atproto.repo.describeRepo', - getProfile: 'app.bsky.actor.getProfile', - listRecords: 'com.atproto.repo.listRecords', - putRecord: 'com.atproto.repo.putRecord' -} - -async function request(url, options = {}) { - try { - const controller = new AbortController() - const timeoutId = setTimeout(() => controller.abort(), 15000) // 15秒タイムアウト - - const response = await fetch(url, { - ...options, - signal: controller.signal - }) - - clearTimeout(timeoutId) - - if (!response.ok) { - throw new ATProtoError( - `Request failed: ${response.statusText}`, - response.status, - { url, method: options.method || 'GET' } - ) - } - - return await response.json() - } catch (error) { - if (error.name === 'AbortError') { - const timeoutError = new ATProtoError( - 'リクエストがタイムアウトしました', - 408, - { url } - ) - throw timeoutError - } - - if (error instanceof ATProtoError) { - throw error - } - - // ネットワークエラーなど - const networkError = new ATProtoError( - 'ネットワークエラーが発生しました', - 0, - { url, originalError: error.message } - ) - throw networkError - } -} - -export const atproto = { - async getDid(pds, handle) { - const endpoint = pds.startsWith('http') ? pds : `https://${pds}` - const res = await request(`${endpoint}/xrpc/${ENDPOINTS.describeRepo}?repo=${handle}`) - return res.did - }, - - async getProfile(bsky, actor) { - // Skip test DIDs - if (actor && actor.includes('test-')) { - return { - did: actor, - handle: 'test.user', - displayName: 'Test User', - avatar: null - } - } - - // Check if endpoint supports getProfile - let apiEndpoint = bsky - - // Allow public.api.bsky.app and bsky.syu.is, redirect other PDS endpoints - if (!bsky.includes('public.api.bsky.app') && !bsky.includes('bsky.syu.is')) { - // If it's a PDS endpoint that doesn't support getProfile, redirect to public API - apiEndpoint = 'https://public.api.bsky.app' - } - - return await request(`${apiEndpoint}/xrpc/${ENDPOINTS.getProfile}?actor=${actor}`) - }, - - async getRecords(pds, repo, collection, limit = 10) { - const res = await request(`${pds}/xrpc/${ENDPOINTS.listRecords}?repo=${repo}&collection=${collection}&limit=${limit}`) - return res.records || [] - }, - - async searchPlc(plc, did) { - try { - const data = await request(`${plc}/${did}`) - return { - success: true, - endpoint: data?.service?.[0]?.serviceEndpoint || null, - handle: data?.alsoKnownAs?.[0]?.replace('at://', '') || null - } - } catch { - return { success: false, endpoint: null, handle: null } - } - }, - - async putRecord(pds, record, agent) { - if (!agent) { - throw new Error('Agent required for putRecord') - } - - // Use Agent's putRecord method instead of direct fetch - return await agent.com.atproto.repo.putRecord(record) - } -} - -import { dataCache } from '../utils/cache.js' - -// Collection specific methods -export const collections = { - async getBase(pds, repo, collection, limit = 10) { - const cacheKey = dataCache.generateKey('base', pds, repo, collection, limit) - const cached = dataCache.get(cacheKey) - if (cached) return cached - - const data = await atproto.getRecords(pds, repo, collection, limit) - dataCache.set(cacheKey, data) - return data - }, - - async getLang(pds, repo, collection, limit = 10) { - const cacheKey = dataCache.generateKey('lang', pds, repo, collection, limit) - const cached = dataCache.get(cacheKey) - if (cached) return cached - - const data = await atproto.getRecords(pds, repo, `${collection}.chat.lang`, limit) - dataCache.set(cacheKey, data) - return data - }, - - async getComment(pds, repo, collection, limit = 10) { - const cacheKey = dataCache.generateKey('comment', pds, repo, collection, limit) - const cached = dataCache.get(cacheKey) - if (cached) return cached - - const data = await atproto.getRecords(pds, repo, `${collection}.chat.comment`, limit) - dataCache.set(cacheKey, data) - return data - }, - - async getChat(pds, repo, collection, limit = 10) { - const cacheKey = dataCache.generateKey('chat', pds, repo, collection, limit) - const cached = dataCache.get(cacheKey) - if (cached) return cached - - const data = await atproto.getRecords(pds, repo, `${collection}.chat`, limit) - dataCache.set(cacheKey, data) - return data - }, - - async getUserList(pds, repo, collection, limit = 100) { - const cacheKey = dataCache.generateKey('userlist', pds, repo, collection, limit) - const cached = dataCache.get(cacheKey) - if (cached) return cached - - const data = await atproto.getRecords(pds, repo, `${collection}.user`, limit) - dataCache.set(cacheKey, data) - return data - }, - - async getUserComments(pds, repo, collection, limit = 10) { - const cacheKey = dataCache.generateKey('usercomments', pds, repo, collection, limit) - const cached = dataCache.get(cacheKey) - if (cached) return cached - - const data = await atproto.getRecords(pds, repo, collection, limit) - dataCache.set(cacheKey, data) - return data - }, - - async getProfiles(pds, repo, collection, limit = 100) { - const cacheKey = dataCache.generateKey('profiles', pds, repo, collection, limit) - const cached = dataCache.get(cacheKey) - if (cached) return cached - - const data = await atproto.getRecords(pds, repo, `${collection}.profile`, limit) - dataCache.set(cacheKey, data) - return data - }, - - // 投稿後にキャッシュを無効化 - invalidateCache(collection) { - dataCache.invalidatePattern(collection) - } -} \ No newline at end of file diff --git a/ai-conversation/src/components/AskAI.jsx b/ai-conversation/src/components/AskAI.jsx deleted file mode 100644 index 76deb6a..0000000 --- a/ai-conversation/src/components/AskAI.jsx +++ /dev/null @@ -1,399 +0,0 @@ -import React, { useState, useEffect, useRef } from 'react' -import { useAskAI } from '../hooks/useAskAI.js' -import LoadingSkeleton from './LoadingSkeleton.jsx' - -export default function AskAI({ adminData, user, agent, onClose }) { - const { askQuestion, loading, error, chatHistory, clearChatHistory, loadChatHistory } = useAskAI(adminData, user, agent) - const [question, setQuestion] = useState('') - const [isComposing, setIsComposing] = useState(false) - const chatEndRef = useRef(null) - - useEffect(() => { - // チャット履歴を読み込み - loadChatHistory() - }, [loadChatHistory]) - - useEffect(() => { - // 新しいメッセージが追加されたら一番下にスクロール - if (chatEndRef.current) { - chatEndRef.current.scrollIntoView({ behavior: 'smooth' }) - } - }, [chatHistory]) - - const handleSubmit = async (e) => { - e.preventDefault() - if (!question.trim() || loading) return - - try { - await askQuestion(question) - setQuestion('') - } catch (err) { - // エラーはuseAskAIで処理済み - } - } - - const handleKeyDown = (e) => { - if (e.key === 'Enter' && !e.shiftKey && !isComposing) { - e.preventDefault() - handleSubmit(e) - } - if (e.key === 'Escape') { - onClose?.() - } - } - - const formatTimestamp = (timestamp) => { - return new Date(timestamp).toLocaleString('ja-JP', { - month: 'short', - day: 'numeric', - hour: '2-digit', - minute: '2-digit' - }) - } - - const renderMessage = (entry, index) => ( -
- {/* ユーザーの質問 */} -
-
-
- {(entry.user?.avatar || user?.avatar) ? ( - {entry.user?.displayName - ) : ( - '👤' - )} -
-
-
{entry.user?.displayName || user?.displayName || 'You'}
-
@{entry.user?.handle || user?.handle || 'user'}
-
{formatTimestamp(entry.timestamp)}
-
-
-
{entry.question}
-
- - {/* AIの回答 */} -
-
-
- {adminData?.profile?.avatar ? ( - {adminData.profile.displayName} - ) : ( - '🤖' - )} -
-
-
{adminData?.profile?.displayName || 'AI'}
-
@{adminData?.profile?.handle || 'ai'}
-
{formatTimestamp(entry.timestamp)}
-
-
-
{entry.answer}
-
-
- ) - - return ( -
-
-

Ask AI

-
- - -
-
- -
- {chatHistory.length === 0 && !loading ? ( -
-
-
-
- {adminData?.profile?.avatar ? ( - {adminData.profile.displayName} - ) : ( - '🤖' - )} -
-
-
{adminData?.profile?.displayName || 'AI'}
-
@{adminData?.profile?.handle || 'ai'}
-
-
-
- こんにちは!このブログの内容について何でも質問してください。記事の詳細や関連する話題について説明できます。 -
-
-
- ) : ( - chatHistory.map(renderMessage) - )} - - {loading && ( -
-
-
🤖
-
-
考え中...
-
-
- -
- )} - - {error && ( -
-
- エラー: {error} -
-
- )} - -
-
- -
-
-