Compare commits
2 Commits
62280265b4
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| e297ec7245 | |||
| 1310d1770a |
@@ -1,44 +0,0 @@
|
|||||||
# 🤝 Contributing to Plexus
|
|
||||||
|
|
||||||
We maintain high standards for code quality and documentation. Please follow this workflow for all contributions.
|
|
||||||
|
|
||||||
## 🔄 Development Workflow
|
|
||||||
|
|
||||||
### 1. Pick a Task
|
|
||||||
Before coding, ensure you are working on an assigned task.
|
|
||||||
- List tasks: `make task-list`
|
|
||||||
- Add a task: `make task-add title="Your Task"`
|
|
||||||
- Mark as in-progress: (Update manually in `tasks/tasks.duckdb` or via CLI if implemented).
|
|
||||||
|
|
||||||
### 2. Implementation
|
|
||||||
- Write clean, modular code.
|
|
||||||
- Follow the existing design patterns (Vue 3 Composition API, Pinia, Socket.io).
|
|
||||||
- Ensure UI changes are mobile-friendly and follow the Discord-inspired aesthetic.
|
|
||||||
|
|
||||||
### 3. Verification
|
|
||||||
Before committing, you **must** verify your changes:
|
|
||||||
- Run linting: `make lint`
|
|
||||||
- Run tests: `make test`
|
|
||||||
- Manual check: Verify the feature in the browser.
|
|
||||||
|
|
||||||
### 4. Commit
|
|
||||||
We use **Husky** and **lint-staged** to enforce quality.
|
|
||||||
- Your commit will fail if linting or tests do not pass.
|
|
||||||
- Use descriptive commit messages (e.g., `feat: add user profiles`, `fix: message alignment`).
|
|
||||||
|
|
||||||
### 5. Documentation
|
|
||||||
If your change affects the architecture, data model, or API:
|
|
||||||
- Update the relevant file in `docs/`.
|
|
||||||
- Ensure the root `README.md` is still accurate.
|
|
||||||
|
|
||||||
### 6. Finalize Task
|
|
||||||
Mark the task as done:
|
|
||||||
- `make task-done id=X`
|
|
||||||
|
|
||||||
## 🛠 Tooling
|
|
||||||
- **Linting**: ESLint for JS/Vue, Ruff for Python.
|
|
||||||
- **Testing**: Mocha for backend integration tests.
|
|
||||||
- **Automation**: Use the `Makefile` for all common operations.
|
|
||||||
|
|
||||||
---
|
|
||||||
Thank you for helping make Plexus better!
|
|
||||||
22
Makefile
22
Makefile
@@ -21,25 +21,3 @@ test:
|
|||||||
# Docker Shell
|
# Docker Shell
|
||||||
shell:
|
shell:
|
||||||
docker compose run --rm dev-shell
|
docker compose run --rm dev-shell
|
||||||
|
|
||||||
# Task Tracker Integration
|
|
||||||
PYTHON=python3
|
|
||||||
TASK_CLI=tasks/cli.py
|
|
||||||
|
|
||||||
task-list:
|
|
||||||
$(PYTHON) $(TASK_CLI) list
|
|
||||||
|
|
||||||
task-add:
|
|
||||||
$(PYTHON) $(TASK_CLI) add "$(title)"
|
|
||||||
|
|
||||||
task-done:
|
|
||||||
$(PYTHON) $(TASK_CLI) done $(id)
|
|
||||||
|
|
||||||
task-update:
|
|
||||||
$(PYTHON) $(TASK_CLI) update $(id) $(status)
|
|
||||||
|
|
||||||
task-delete:
|
|
||||||
$(PYTHON) $(TASK_CLI) delete $(id)
|
|
||||||
|
|
||||||
task-filter:
|
|
||||||
$(PYTHON) $(TASK_CLI) list --status $(status)
|
|
||||||
|
|||||||
17
README.md
17
README.md
@@ -48,21 +48,4 @@ make install
|
|||||||
```bash
|
```bash
|
||||||
# Start the dev environment (Docker)
|
# Start the dev environment (Docker)
|
||||||
make dev
|
make dev
|
||||||
|
|
||||||
# Or run locally
|
|
||||||
cd server && npm run dev
|
|
||||||
cd client && npm run dev
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🤝 Contributing
|
|
||||||
|
|
||||||
We follow a strict development workflow. Please read [CONTRIBUTING.md](./CONTRIBUTING.md) before starting.
|
|
||||||
|
|
||||||
1. **Pick a Task**: Use `make task-list` to find something to work on.
|
|
||||||
2. **Code**: Implement your changes.
|
|
||||||
3. **Verify**: Run `make lint test` to ensure quality.
|
|
||||||
4. **Commit**: Pre-commit hooks will automatically run linting and tests.
|
|
||||||
5. **Document**: Update relevant docs if you add new features.
|
|
||||||
|
|
||||||
---
|
|
||||||
Built with ❤️ by the Plexus Team.
|
|
||||||
|
|||||||
@@ -98,39 +98,37 @@ const emit = defineEmits(['view-profile']);
|
|||||||
msg.status === 'failed' ? 'bg-red-500/5 border border-red-500/20' : 'hover:bg-white/[0.02]'
|
msg.status === 'failed' ? 'bg-red-500/5 border border-red-500/20' : 'hover:bg-white/[0.02]'
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<!-- Avatar -->
|
<!-- Avatar - always show for every message -->
|
||||||
<div class="w-10 flex-shrink-0">
|
<div class="w-10 flex-shrink-0">
|
||||||
<div
|
<div
|
||||||
v-if="index === 0 || currentMessages[index-1].walletAddress !== msg.walletAddress"
|
|
||||||
class="w-10 h-10 rounded-full flex items-center justify-center text-white font-bold text-sm shadow-lg border border-white/10 mt-1 cursor-pointer hover:opacity-80 transition-opacity"
|
class="w-10 h-10 rounded-full flex items-center justify-center text-white font-bold text-sm shadow-lg border border-white/10 mt-1 cursor-pointer hover:opacity-80 transition-opacity"
|
||||||
:class="msg.walletAddress === walletAddress ? 'bg-gradient-to-br from-violet-500 to-fuchsia-600' : 'bg-discord-sidebar'"
|
:class="msg.walletAddress === walletAddress ? 'bg-gradient-to-br from-violet-500 to-fuchsia-600' : 'bg-discord-sidebar'"
|
||||||
@click="emit('view-profile', msg.walletAddress)"
|
@click="emit('view-profile', msg.walletAddress)"
|
||||||
>
|
>
|
||||||
{{ msg.username?.substring(0, 2).toUpperCase() }}
|
{{ msg.username?.substring(0, 2).toUpperCase() || '??' }}
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-else
|
|
||||||
class="w-10 text-[10px] text-crypto-muted opacity-0 group-hover:opacity-100 text-right pr-2 pt-1.5 transition-opacity"
|
|
||||||
>
|
|
||||||
{{ formatTime(msg.timestamp) }}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Content -->
|
<!-- Content -->
|
||||||
<div class="flex-1 min-w-0">
|
<div class="flex-1 min-w-0">
|
||||||
<div
|
<div class="flex items-center gap-2 mb-1 flex-wrap">
|
||||||
v-if="index === 0 || currentMessages[index-1].walletAddress !== msg.walletAddress"
|
|
||||||
class="flex items-center gap-2 mb-1 flex-wrap"
|
|
||||||
>
|
|
||||||
<span
|
<span
|
||||||
:class="['text-sm font-bold hover:underline cursor-pointer', msg.walletAddress === walletAddress ? 'text-violet-400' : 'text-white']"
|
:class="['text-sm font-bold hover:underline cursor-pointer', msg.walletAddress === walletAddress ? 'text-violet-400' : 'text-white']"
|
||||||
@click="emit('view-profile', msg.walletAddress)"
|
@click="emit('view-profile', msg.walletAddress)"
|
||||||
>
|
>
|
||||||
{{ msg.username }}
|
{{ msg.username || 'Anonymous' }}
|
||||||
</span>
|
</span>
|
||||||
|
<!-- Status LED next to username -->
|
||||||
|
<div
|
||||||
|
v-if="msg.status"
|
||||||
|
class="led"
|
||||||
|
:class="{
|
||||||
|
'led-orange animate-pulse': msg.status === 'pending',
|
||||||
|
'led-green': msg.status === 'validated',
|
||||||
|
'led-red': msg.status === 'failed'
|
||||||
|
}"
|
||||||
|
/>
|
||||||
<span class="text-[10px] text-crypto-muted">{{ formatTime(msg.timestamp) }}</span>
|
<span class="text-[10px] text-crypto-muted">{{ formatTime(msg.timestamp) }}</span>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Message Content & Status -->
|
<!-- Message Content & Status -->
|
||||||
@@ -139,7 +137,7 @@ const emit = defineEmits(['view-profile']);
|
|||||||
{{ msg.content }}
|
{{ msg.content }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Transaction ID & Status Pill for all messages -->
|
<!-- Transaction ID (show on hover) -->
|
||||||
<div class="flex items-center gap-2 flex-shrink-0 mt-1">
|
<div class="flex items-center gap-2 flex-shrink-0 mt-1">
|
||||||
<span
|
<span
|
||||||
v-if="msg.txId && msg.status !== 'failed'"
|
v-if="msg.txId && msg.status !== 'failed'"
|
||||||
@@ -155,16 +153,6 @@ const emit = defineEmits(['view-profile']);
|
|||||||
<Copy v-else size="10" />
|
<Copy v-else size="10" />
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="msg.status"
|
|
||||||
class="led"
|
|
||||||
:class="{
|
|
||||||
'led-orange animate-pulse': msg.status === 'pending',
|
|
||||||
'led-green': msg.status === 'validated',
|
|
||||||
'led-red': msg.status === 'failed'
|
|
||||||
}"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -313,11 +313,17 @@ export const useChatStore = defineStore('chat', () => {
|
|||||||
const [addr1, addr2] = [walletAddress.value, targetWallet].sort();
|
const [addr1, addr2] = [walletAddress.value, targetWallet].sort();
|
||||||
const dmChannelId = `dm:${addr1}:${addr2}`;
|
const dmChannelId = `dm:${addr1}:${addr2}`;
|
||||||
|
|
||||||
|
// Find the other user's name
|
||||||
|
const otherUser = users.value.find(u => u.wallet_address === targetWallet);
|
||||||
|
const otherName = otherUser?.username || targetWallet.slice(0, 6) + '...';
|
||||||
|
|
||||||
// Add to channels list if not exists
|
// Add to channels list if not exists
|
||||||
if (!channels.value.find(c => c.id === dmChannelId)) {
|
if (!channels.value.find(c => c.id === dmChannelId)) {
|
||||||
channels.value.push({
|
channels.value.push({
|
||||||
id: dmChannelId,
|
id: dmChannelId,
|
||||||
name: `DM: ${targetWallet.slice(0, 4)}...`
|
name: otherName,
|
||||||
|
isDM: true,
|
||||||
|
targetWallet
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -351,7 +357,9 @@ export const useChatStore = defineStore('chat', () => {
|
|||||||
// Map snake_case to camelCase and set status
|
// Map snake_case to camelCase and set status
|
||||||
messages.value[channelId] = data.map(m => ({
|
messages.value[channelId] = data.map(m => ({
|
||||||
...m,
|
...m,
|
||||||
|
walletAddress: m.wallet_address,
|
||||||
txId: m.tx_id,
|
txId: m.tx_id,
|
||||||
|
channelId: m.channel_id,
|
||||||
status: 'validated' // Messages from DB are confirmed
|
status: 'validated' // Messages from DB are confirmed
|
||||||
}));
|
}));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -629,7 +629,7 @@ app.post('/api/summary', async (req, res) => {
|
|||||||
"X-Title": "Plexus Social"
|
"X-Title": "Plexus Social"
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
"model": "google/learnlm-1.5-pro-experimental:free",
|
"model": "xiaomi/mimo-v2-flash:free",
|
||||||
"messages": [
|
"messages": [
|
||||||
{
|
{
|
||||||
"role": "system",
|
"role": "system",
|
||||||
|
|||||||
62
tasks/cli.py
62
tasks/cli.py
@@ -1,62 +0,0 @@
|
|||||||
import argparse
|
|
||||||
|
|
||||||
from db import add_task, delete_task, init_db, list_tasks, update_task
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
init_db()
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description="Plexus Task Tracker")
|
|
||||||
subparsers = parser.add_subparsers(dest="command")
|
|
||||||
|
|
||||||
# Add task
|
|
||||||
add_parser = subparsers.add_parser("add", help="Add a new task")
|
|
||||||
add_parser.add_argument("title", help="Task title")
|
|
||||||
|
|
||||||
# List tasks
|
|
||||||
list_parser = subparsers.add_parser("list", help="List tasks")
|
|
||||||
list_parser.add_argument("--status", help="Filter by status")
|
|
||||||
|
|
||||||
# Update task
|
|
||||||
update_parser = subparsers.add_parser("update", help="Update task status")
|
|
||||||
update_parser.add_argument("id", type=int, help="Task ID")
|
|
||||||
update_parser.add_argument("status", help="New status")
|
|
||||||
|
|
||||||
# Done task
|
|
||||||
done_parser = subparsers.add_parser("done", help="Mark task as done")
|
|
||||||
done_parser.add_argument("id", type=int, help="Task ID")
|
|
||||||
|
|
||||||
# Delete task
|
|
||||||
delete_parser = subparsers.add_parser("delete", help="Delete a task")
|
|
||||||
delete_parser.add_argument("id", type=int, help="Task ID")
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
if args.command == "add":
|
|
||||||
add_task(args.title)
|
|
||||||
print(f"Task added: {args.title}")
|
|
||||||
|
|
||||||
elif args.command == "list":
|
|
||||||
tasks = list_tasks(args.status)
|
|
||||||
print(f"{'ID':<5} {'Status':<15} {'Title'}")
|
|
||||||
print("-" * 40)
|
|
||||||
for t in tasks:
|
|
||||||
print(f"{t[0]:<5} {t[2]:<15} {t[1]}")
|
|
||||||
|
|
||||||
elif args.command == "update":
|
|
||||||
update_task(args.id, args.status)
|
|
||||||
print(f"Task {args.id} updated to {args.status}")
|
|
||||||
|
|
||||||
elif args.command == "done":
|
|
||||||
update_task(args.id, "done")
|
|
||||||
print(f"Task {args.id} marked as done")
|
|
||||||
|
|
||||||
elif args.command == "delete":
|
|
||||||
delete_task(args.id)
|
|
||||||
print(f"Task {args.id} deleted")
|
|
||||||
|
|
||||||
else:
|
|
||||||
parser.print_help()
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
46
tasks/db.py
46
tasks/db.py
@@ -1,46 +0,0 @@
|
|||||||
|
|
||||||
import duckdb
|
|
||||||
|
|
||||||
DB_PATH = "tasks/tasks.duckdb"
|
|
||||||
|
|
||||||
def init_db():
|
|
||||||
|
|
||||||
con = duckdb.connect(DB_PATH)
|
|
||||||
con.execute("""
|
|
||||||
CREATE TABLE IF NOT EXISTS tasks (
|
|
||||||
id INTEGER PRIMARY KEY,
|
|
||||||
title VARCHAR,
|
|
||||||
status VARCHAR DEFAULT 'todo',
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
)
|
|
||||||
""")
|
|
||||||
con.execute("CREATE SEQUENCE IF NOT EXISTS seq_task_id START 1")
|
|
||||||
con.close()
|
|
||||||
|
|
||||||
def get_connection():
|
|
||||||
return duckdb.connect(DB_PATH)
|
|
||||||
|
|
||||||
def add_task(title):
|
|
||||||
con = get_connection()
|
|
||||||
con.execute("INSERT INTO tasks (id, title) VALUES (nextval('seq_task_id'), ?)", [title])
|
|
||||||
con.close()
|
|
||||||
|
|
||||||
def list_tasks(status=None):
|
|
||||||
con = get_connection()
|
|
||||||
if status:
|
|
||||||
res = con.execute("SELECT * FROM tasks WHERE status = ? ORDER BY id", [status]).fetchall()
|
|
||||||
else:
|
|
||||||
res = con.execute("SELECT * FROM tasks ORDER BY id").fetchall()
|
|
||||||
con.close()
|
|
||||||
return res
|
|
||||||
|
|
||||||
def update_task(task_id, status):
|
|
||||||
con = get_connection()
|
|
||||||
con.execute("UPDATE tasks SET status = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?", [status, task_id])
|
|
||||||
con.close()
|
|
||||||
|
|
||||||
def delete_task(task_id):
|
|
||||||
con = get_connection()
|
|
||||||
con.execute("DELETE FROM tasks WHERE id = ?", [task_id])
|
|
||||||
con.close()
|
|
||||||
Binary file not shown.
Reference in New Issue
Block a user