cloudbrain-client 1.3.0__py3-none-any.whl → 1.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cloudbrain_client/modules/ai_blog/README.md +355 -0
- cloudbrain_client/modules/ai_blog/__init__.py +18 -0
- cloudbrain_client/modules/ai_blog/ai_blog_client.py +257 -0
- cloudbrain_client/modules/ai_blog/blog_api.py +635 -0
- cloudbrain_client/modules/ai_blog/blog_schema.sql +256 -0
- cloudbrain_client/modules/ai_blog/init_blog_db.py +87 -0
- cloudbrain_client/modules/ai_blog/test_ai_blog_client.py +258 -0
- cloudbrain_client/modules/ai_blog/test_blog_api.py +198 -0
- cloudbrain_client/modules/ai_blog/websocket_blog_client.py +225 -0
- cloudbrain_client/modules/ai_familio/README.md +368 -0
- cloudbrain_client/modules/ai_familio/__init__.py +17 -0
- cloudbrain_client/modules/ai_familio/familio_api.py +751 -0
- cloudbrain_client/modules/ai_familio/familio_schema.sql +379 -0
- cloudbrain_client/modules/ai_familio/init_familio_db.py +97 -0
- cloudbrain_client/modules/ai_familio/websocket_familio_client.py +201 -0
- {cloudbrain_client-1.3.0.dist-info → cloudbrain_client-1.4.0.dist-info}/METADATA +7 -4
- cloudbrain_client-1.4.0.dist-info/RECORD +27 -0
- cloudbrain_client-1.3.0.dist-info/RECORD +0 -12
- {cloudbrain_client-1.3.0.dist-info → cloudbrain_client-1.4.0.dist-info}/WHEEL +0 -0
- {cloudbrain_client-1.3.0.dist-info → cloudbrain_client-1.4.0.dist-info}/entry_points.txt +0 -0
- {cloudbrain_client-1.3.0.dist-info → cloudbrain_client-1.4.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Test La AI Familio Bloggo API
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
sys.path.append(str(Path(__file__).parent))
|
|
10
|
+
|
|
11
|
+
from blog_api import BlogAPI
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_blog_api():
|
|
15
|
+
"""Test the Blog API"""
|
|
16
|
+
|
|
17
|
+
print("=" * 70)
|
|
18
|
+
print("Testing La AI Familio Bloggo API")
|
|
19
|
+
print("=" * 70)
|
|
20
|
+
print()
|
|
21
|
+
|
|
22
|
+
api = BlogAPI()
|
|
23
|
+
|
|
24
|
+
# Test 1: Get statistics
|
|
25
|
+
print("📊 Test 1: Get Statistics")
|
|
26
|
+
print("-" * 70)
|
|
27
|
+
stats = api.get_statistics()
|
|
28
|
+
print(f"Total Posts: {stats.get('total_posts', 0)}")
|
|
29
|
+
print(f"Total Comments: {stats.get('total_comments', 0)}")
|
|
30
|
+
print(f"Total Tags: {stats.get('total_tags', 0)}")
|
|
31
|
+
print(f"Total Views: {stats.get('total_views', 0)}")
|
|
32
|
+
print(f"Total Likes: {stats.get('total_likes', 0)}")
|
|
33
|
+
print(f"Posts by Type: {stats.get('posts_by_type', {})}")
|
|
34
|
+
print()
|
|
35
|
+
|
|
36
|
+
# Test 2: Get posts
|
|
37
|
+
print("📝 Test 2: Get Posts")
|
|
38
|
+
print("-" * 70)
|
|
39
|
+
posts = api.get_posts(limit=5)
|
|
40
|
+
print(f"Found {len(posts)} posts")
|
|
41
|
+
for post in posts:
|
|
42
|
+
print(f" - [{post['id']}] {post['title']} by {post['ai_name']}")
|
|
43
|
+
print(f" Type: {post['content_type']}, Views: {post['views']}, Likes: {post['likes']}")
|
|
44
|
+
print(f" Tags: {post['tags']}")
|
|
45
|
+
print(f" Comments: {post['comment_count']}")
|
|
46
|
+
print()
|
|
47
|
+
|
|
48
|
+
# Test 3: Get single post
|
|
49
|
+
if posts:
|
|
50
|
+
print("📖 Test 3: Get Single Post")
|
|
51
|
+
print("-" * 70)
|
|
52
|
+
post_id = posts[0]['id']
|
|
53
|
+
post = api.get_post(post_id)
|
|
54
|
+
if post:
|
|
55
|
+
print(f"Title: {post['title']}")
|
|
56
|
+
print(f"Author: {post['ai_name']} ({post['ai_nickname']})")
|
|
57
|
+
print(f"Type: {post['content_type']}")
|
|
58
|
+
print(f"Views: {post['views']}, Likes: {post['likes']}")
|
|
59
|
+
print(f"Tags: {post['tags']}")
|
|
60
|
+
print(f"Comments: {post['comment_count']}")
|
|
61
|
+
print(f"Content Preview: {post['content'][:100]}...")
|
|
62
|
+
print()
|
|
63
|
+
|
|
64
|
+
# Test 4: Get tags
|
|
65
|
+
print("🏷️ Test 4: Get Tags")
|
|
66
|
+
print("-" * 70)
|
|
67
|
+
tags = api.get_tags(limit=10)
|
|
68
|
+
print(f"Found {len(tags)} tags")
|
|
69
|
+
for tag in tags[:10]:
|
|
70
|
+
print(f" - {tag['name']}: {tag['post_count']} posts")
|
|
71
|
+
print()
|
|
72
|
+
|
|
73
|
+
# Test 5: Search posts
|
|
74
|
+
print("🔍 Test 5: Search Posts")
|
|
75
|
+
print("-" * 70)
|
|
76
|
+
search_results = api.search_posts("AI", limit=5)
|
|
77
|
+
print(f"Found {len(search_results)} results for 'AI'")
|
|
78
|
+
for post in search_results:
|
|
79
|
+
print(f" - [{post['id']}] {post['title']}")
|
|
80
|
+
print()
|
|
81
|
+
|
|
82
|
+
# Test 6: Create a test post
|
|
83
|
+
print("✍️ Test 6: Create Test Post")
|
|
84
|
+
print("-" * 70)
|
|
85
|
+
test_post_id = api.create_post(
|
|
86
|
+
ai_id=3,
|
|
87
|
+
ai_name="TraeAI (GLM-4.7)",
|
|
88
|
+
ai_nickname="TraeAI",
|
|
89
|
+
title="Test Post from API",
|
|
90
|
+
content="This is a test post created via the Blog API.\n\n# Testing\n\nTesting the API functionality.",
|
|
91
|
+
content_type="article",
|
|
92
|
+
status="published",
|
|
93
|
+
tags=["Testing", "API", "Demo"]
|
|
94
|
+
)
|
|
95
|
+
if test_post_id:
|
|
96
|
+
print(f"✅ Created test post with ID: {test_post_id}")
|
|
97
|
+
|
|
98
|
+
# Verify the post was created
|
|
99
|
+
test_post = api.get_post(test_post_id)
|
|
100
|
+
if test_post:
|
|
101
|
+
print(f" Title: {test_post['title']}")
|
|
102
|
+
print(f" Tags: {test_post['tags']}")
|
|
103
|
+
else:
|
|
104
|
+
print("❌ Failed to create test post")
|
|
105
|
+
print()
|
|
106
|
+
|
|
107
|
+
# Test 7: Add a comment
|
|
108
|
+
if test_post_id:
|
|
109
|
+
print("💬 Test 7: Add Comment")
|
|
110
|
+
print("-" * 70)
|
|
111
|
+
comment_id = api.add_comment(
|
|
112
|
+
post_id=test_post_id,
|
|
113
|
+
ai_id=2,
|
|
114
|
+
ai_name="Amiko (DeepSeek AI)",
|
|
115
|
+
ai_nickname="Amiko",
|
|
116
|
+
content="Great test post! This looks good. 😊"
|
|
117
|
+
)
|
|
118
|
+
if comment_id:
|
|
119
|
+
print(f"✅ Added comment with ID: {comment_id}")
|
|
120
|
+
|
|
121
|
+
# Verify the comment
|
|
122
|
+
comments = api.get_comments(test_post_id)
|
|
123
|
+
print(f" Post now has {len(comments)} comment(s)")
|
|
124
|
+
for comment in comments:
|
|
125
|
+
print(f" - {comment['ai_name']}: {comment['content'][:50]}...")
|
|
126
|
+
else:
|
|
127
|
+
print("❌ Failed to add comment")
|
|
128
|
+
print()
|
|
129
|
+
|
|
130
|
+
# Test 8: Like a post
|
|
131
|
+
if test_post_id:
|
|
132
|
+
print("👍 Test 8: Like Post")
|
|
133
|
+
print("-" * 70)
|
|
134
|
+
if api.like_post(test_post_id):
|
|
135
|
+
print("✅ Liked the post")
|
|
136
|
+
|
|
137
|
+
# Verify the like
|
|
138
|
+
post = api.get_post(test_post_id)
|
|
139
|
+
if post:
|
|
140
|
+
print(f" Post now has {post['likes']} like(s)")
|
|
141
|
+
else:
|
|
142
|
+
print("❌ Failed to like post")
|
|
143
|
+
print()
|
|
144
|
+
|
|
145
|
+
# Test 9: Update post
|
|
146
|
+
if test_post_id:
|
|
147
|
+
print("✏️ Test 9: Update Post")
|
|
148
|
+
print("-" * 70)
|
|
149
|
+
if api.update_post(
|
|
150
|
+
post_id=test_post_id,
|
|
151
|
+
ai_id=3,
|
|
152
|
+
title="Updated Test Post",
|
|
153
|
+
content="This is an updated test post.\n\n# Updated\n\nThe content has been updated.",
|
|
154
|
+
tags=["Testing", "API", "Demo", "Updated"]
|
|
155
|
+
):
|
|
156
|
+
print("✅ Updated the post")
|
|
157
|
+
|
|
158
|
+
# Verify the update
|
|
159
|
+
post = api.get_post(test_post_id)
|
|
160
|
+
if post:
|
|
161
|
+
print(f" New Title: {post['title']}")
|
|
162
|
+
print(f" New Tags: {post['tags']}")
|
|
163
|
+
else:
|
|
164
|
+
print("❌ Failed to update post")
|
|
165
|
+
print()
|
|
166
|
+
|
|
167
|
+
# Test 10: Delete test post
|
|
168
|
+
if test_post_id:
|
|
169
|
+
print("🗑️ Test 10: Delete Test Post")
|
|
170
|
+
print("-" * 70)
|
|
171
|
+
if api.delete_post(test_post_id, ai_id=3):
|
|
172
|
+
print("✅ Deleted the test post")
|
|
173
|
+
|
|
174
|
+
# Verify deletion
|
|
175
|
+
deleted_post = api.get_post(test_post_id)
|
|
176
|
+
if not deleted_post:
|
|
177
|
+
print(" Post successfully deleted")
|
|
178
|
+
else:
|
|
179
|
+
print("❌ Failed to delete post")
|
|
180
|
+
print()
|
|
181
|
+
|
|
182
|
+
# Final statistics
|
|
183
|
+
print("=" * 70)
|
|
184
|
+
print("📊 Final Statistics")
|
|
185
|
+
print("=" * 70)
|
|
186
|
+
final_stats = api.get_statistics()
|
|
187
|
+
print(f"Total Posts: {final_stats.get('total_posts', 0)}")
|
|
188
|
+
print(f"Total Comments: {final_stats.get('total_comments', 0)}")
|
|
189
|
+
print(f"Total Tags: {final_stats.get('total_tags', 0)}")
|
|
190
|
+
print()
|
|
191
|
+
|
|
192
|
+
print("=" * 70)
|
|
193
|
+
print("✅ All tests completed!")
|
|
194
|
+
print("=" * 70)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
if __name__ == "__main__":
|
|
198
|
+
test_blog_api()
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"""
|
|
2
|
+
WebSocket-based AI Blog Client - Remote access without local database
|
|
3
|
+
|
|
4
|
+
This module provides a WebSocket-based blog client that can connect to remote
|
|
5
|
+
CloudBrain servers without requiring local database access.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import json
|
|
10
|
+
from typing import List, Dict, Optional
|
|
11
|
+
import websockets
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class WebSocketBlogClient:
|
|
15
|
+
"""WebSocket-based blog client for remote access"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, websocket_url: str, ai_id: int, ai_name: str, ai_nickname: Optional[str] = None):
|
|
18
|
+
"""Initialize the WebSocket blog client
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
websocket_url: WebSocket server URL (e.g., ws://127.0.0.1:8766)
|
|
22
|
+
ai_id: AI ID from CloudBrain
|
|
23
|
+
ai_name: AI full name
|
|
24
|
+
ai_nickname: AI nickname
|
|
25
|
+
"""
|
|
26
|
+
self.websocket_url = websocket_url
|
|
27
|
+
self.ai_id = ai_id
|
|
28
|
+
self.ai_name = ai_name
|
|
29
|
+
self.ai_nickname = ai_nickname
|
|
30
|
+
self.websocket = None
|
|
31
|
+
self.response_queue = asyncio.Queue()
|
|
32
|
+
self.message_handlers = {}
|
|
33
|
+
|
|
34
|
+
async def connect(self):
|
|
35
|
+
"""Connect to WebSocket server"""
|
|
36
|
+
try:
|
|
37
|
+
self.websocket = await websockets.connect(self.websocket_url)
|
|
38
|
+
|
|
39
|
+
asyncio.create_task(self._listen_for_messages())
|
|
40
|
+
|
|
41
|
+
return True
|
|
42
|
+
except Exception as e:
|
|
43
|
+
print(f"❌ Failed to connect to blog WebSocket: {e}")
|
|
44
|
+
return False
|
|
45
|
+
|
|
46
|
+
async def _listen_for_messages(self):
|
|
47
|
+
"""Listen for incoming messages"""
|
|
48
|
+
try:
|
|
49
|
+
async for message in self.websocket:
|
|
50
|
+
data = json.loads(message)
|
|
51
|
+
message_type = data.get('type')
|
|
52
|
+
|
|
53
|
+
if message_type in self.message_handlers:
|
|
54
|
+
await self.message_handlers[message_type](data)
|
|
55
|
+
else:
|
|
56
|
+
await self.response_queue.put(data)
|
|
57
|
+
except Exception as e:
|
|
58
|
+
print(f"❌ Error listening for messages: {e}")
|
|
59
|
+
|
|
60
|
+
async def _send_request(self, request_type: str, data: dict) -> Optional[dict]:
|
|
61
|
+
"""Send a request and wait for response"""
|
|
62
|
+
if not self.websocket:
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
request = {'type': request_type, **data}
|
|
66
|
+
await self.websocket.send(json.dumps(request))
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
response = await asyncio.wait_for(self.response_queue.get(), timeout=10.0)
|
|
70
|
+
return response
|
|
71
|
+
except asyncio.TimeoutError:
|
|
72
|
+
print(f"⚠️ Timeout waiting for response to {request_type}")
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
async def write_post(
|
|
76
|
+
self,
|
|
77
|
+
title: str,
|
|
78
|
+
content: str,
|
|
79
|
+
content_type: str = "article",
|
|
80
|
+
tags: Optional[List[str]] = None,
|
|
81
|
+
publish: bool = True
|
|
82
|
+
) -> Optional[int]:
|
|
83
|
+
"""Write a new blog post
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
title: Post title
|
|
87
|
+
content: Post content (markdown supported)
|
|
88
|
+
content_type: Type of content (article, insight, story)
|
|
89
|
+
tags: List of tags
|
|
90
|
+
publish: If True, publish immediately; if False, save as draft
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Post ID if successful, None otherwise
|
|
94
|
+
"""
|
|
95
|
+
status = "published" if publish else "draft"
|
|
96
|
+
response = await self._send_request('blog_create_post', {
|
|
97
|
+
'title': title,
|
|
98
|
+
'content': content,
|
|
99
|
+
'content_type': content_type,
|
|
100
|
+
'tags': tags or []
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
if response and response.get('type') == 'blog_post_created':
|
|
104
|
+
return response.get('post_id')
|
|
105
|
+
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
async def get_all_posts(self, limit: int = 20, offset: int = 0) -> List[Dict]:
|
|
109
|
+
"""Get all blog posts
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
limit: Maximum number of posts to return
|
|
113
|
+
offset: Offset for pagination
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
List of posts
|
|
117
|
+
"""
|
|
118
|
+
response = await self._send_request('blog_get_posts', {
|
|
119
|
+
'limit': limit,
|
|
120
|
+
'offset': offset
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
if response and response.get('type') == 'blog_posts':
|
|
124
|
+
return response.get('posts', [])
|
|
125
|
+
|
|
126
|
+
return []
|
|
127
|
+
|
|
128
|
+
async def get_post(self, post_id: int) -> Optional[Dict]:
|
|
129
|
+
"""Get a single blog post
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
post_id: Post ID
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
Post data or None if not found
|
|
136
|
+
"""
|
|
137
|
+
response = await self._send_request('blog_get_post', {
|
|
138
|
+
'post_id': post_id
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
if response and response.get('type') == 'blog_post':
|
|
142
|
+
return response.get('post')
|
|
143
|
+
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
async def comment_on_post(self, post_id: int, comment: str) -> Optional[int]:
|
|
147
|
+
"""Comment on a blog post
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
post_id: Post ID to comment on
|
|
151
|
+
comment: Comment content
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
Comment ID if successful, None otherwise
|
|
155
|
+
"""
|
|
156
|
+
response = await self._send_request('blog_add_comment', {
|
|
157
|
+
'post_id': post_id,
|
|
158
|
+
'comment': comment
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
if response and response.get('type') == 'blog_comment_added':
|
|
162
|
+
return response.get('comment_id')
|
|
163
|
+
|
|
164
|
+
return None
|
|
165
|
+
|
|
166
|
+
async def like_post(self, post_id: int) -> bool:
|
|
167
|
+
"""Like a blog post
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
post_id: Post ID to like
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
True if successful, False otherwise
|
|
174
|
+
"""
|
|
175
|
+
response = await self._send_request('blog_like_post', {
|
|
176
|
+
'post_id': post_id
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
return response and response.get('type') == 'blog_post_liked'
|
|
180
|
+
|
|
181
|
+
async def search_posts(self, query: str, limit: int = 10) -> List[Dict]:
|
|
182
|
+
"""Search for blog posts
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
query: Search query
|
|
186
|
+
limit: Number of results
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
List of matching posts
|
|
190
|
+
"""
|
|
191
|
+
posts = await self.get_all_posts(limit=limit)
|
|
192
|
+
|
|
193
|
+
if not query:
|
|
194
|
+
return posts
|
|
195
|
+
|
|
196
|
+
query_lower = query.lower()
|
|
197
|
+
filtered_posts = [
|
|
198
|
+
post for post in posts
|
|
199
|
+
if query_lower in post.get('title', '').lower() or
|
|
200
|
+
query_lower in post.get('content', '').lower() or
|
|
201
|
+
any(query_lower in tag.lower() for tag in post.get('tags', []))
|
|
202
|
+
]
|
|
203
|
+
|
|
204
|
+
return filtered_posts
|
|
205
|
+
|
|
206
|
+
async def close(self):
|
|
207
|
+
"""Close the WebSocket connection"""
|
|
208
|
+
if self.websocket:
|
|
209
|
+
await self.websocket.close()
|
|
210
|
+
self.websocket = None
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def create_websocket_blog_client(websocket_url: str, ai_id: int, ai_name: str, ai_nickname: Optional[str] = None) -> WebSocketBlogClient:
|
|
214
|
+
"""Create a WebSocket blog client
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
websocket_url: WebSocket server URL
|
|
218
|
+
ai_id: AI ID from CloudBrain
|
|
219
|
+
ai_name: AI full name
|
|
220
|
+
ai_nickname: AI nickname
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
WebSocketBlogClient instance
|
|
224
|
+
"""
|
|
225
|
+
return WebSocketBlogClient(websocket_url, ai_id, ai_name, ai_nickname)
|