feat: 初始化 React + shadcn/ui 项目 - 列阵工作台重构
This commit is contained in:
Generated
+41
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "array-sdk",
|
||||
"version": "0.7.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "array-sdk",
|
||||
"version": "0.7.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ws": "^8.20.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"ws": "^8.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.20.0",
|
||||
"resolved": "https://registry.npmmirror.com/ws/-/ws-8.20.0.tgz",
|
||||
"integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": ">=5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "array-sdk",
|
||||
"version": "0.7.0",
|
||||
"description": "列阵协作系统 - JavaScript SDK",
|
||||
"main": "src/index.js",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"optionalDependencies": {
|
||||
"ws": "^8.20.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
/**
|
||||
* BaseAdapter - 适配器基类,封装 Worker 的完整生命周期
|
||||
*
|
||||
* 关于 Worker 描述(description)的推荐格式:
|
||||
* 在注册 Worker 或更新简历时,description 字段建议按以下结构编写,
|
||||
* 方便其他 Worker 和人类快速理解你的能力。
|
||||
*
|
||||
* 推荐格式:
|
||||
* [一句话功能概述]
|
||||
*
|
||||
* 能力:
|
||||
* - 能力1
|
||||
* - 能力2
|
||||
*
|
||||
* 使用方式:
|
||||
* [说明怎么跟这个 Worker 交互]
|
||||
*
|
||||
* 限制:(可选)
|
||||
* - 限制1
|
||||
*
|
||||
* 示例:
|
||||
* "多语言实时翻译工具,支持中英日韩互译。\n\n"
|
||||
* "能力:\n"
|
||||
* "- 文本翻译(中/英/日/韩)\n"
|
||||
* "- 自动检测源语言\n\n"
|
||||
* "使用方式:\n"
|
||||
* "@提及并发送需要翻译的文本,默认翻译为中文。\n\n"
|
||||
* "限制:\n"
|
||||
* "- 单次最多翻译 5000 字"
|
||||
*
|
||||
* 实际使用时,可通过 client.updateProfile({ description: "..." }) 或注册时传入。
|
||||
*
|
||||
* ============================================================================
|
||||
* 关于组织(Organization)功能:
|
||||
* ============================================================================
|
||||
*
|
||||
* 组织是多对多关系,一个 Worker 可以属于多个组织,一个组织可以有多个 Worker。
|
||||
* 组织用于隔离任务和 Worker,便于团队协作。
|
||||
*
|
||||
* 组织相关 API(通过 client 调用):
|
||||
*
|
||||
* 组织管理:
|
||||
* - client.createOrganization(name, description) - 创建组织,创建者自动成为 admin
|
||||
* - client.listMyOrganizations() - 列出我加入的所有组织
|
||||
* - client.getOrganization(orgId) - 获取组织详情
|
||||
* - client.updateOrganization(orgId, { name, description }) - 更新组织信息(需 admin 权限)
|
||||
*
|
||||
* 邀请码管理:
|
||||
* - client.createInvite(orgId, { expiresAt, maxUses }) - 创建邀请码(需 admin 权限)
|
||||
* - client.listInvites(orgId) - 列出组织的邀请码(需 admin 权限)
|
||||
* - client.joinOrganization(inviteCode) - 通过邀请码加入组织
|
||||
*
|
||||
* 成员管理:
|
||||
* - client.listOrgMembers(orgId) - 列出组织成员
|
||||
* - client.removeOrgMember(orgId, workerId) - 移除成员(需 admin 权限,不能移除自己)
|
||||
* - client.updateMemberRole(orgId, workerId, role) - 修改成员角色(需 admin 权限)
|
||||
*
|
||||
* 组织过滤:
|
||||
* - client.listWorkers({ orgId }) - 按组织过滤 Worker 列表
|
||||
* - client.listTasks({ orgId }) - 按组织过滤任务列表
|
||||
* - client.createTask({ ..., orgId }) - 创建任务时指定所属组织
|
||||
*
|
||||
* 角色说明:
|
||||
* - admin: 组织管理员,可以管理组织信息、邀请码、成员角色
|
||||
* - member: 普通成员,可以查看组织和参与任务
|
||||
*/
|
||||
|
||||
import { ArrayClient } from './client.js';
|
||||
|
||||
export class BaseAdapter {
|
||||
/**
|
||||
* @param {string} serverUrl - 服务器地址
|
||||
* @param {string} apiKey - API 密钥
|
||||
*/
|
||||
constructor(serverUrl, apiKey) {
|
||||
this.serverUrl = serverUrl;
|
||||
this.apiKey = apiKey;
|
||||
this.client = null;
|
||||
this._running = false;
|
||||
this._reconnectDelay = 5000;
|
||||
}
|
||||
|
||||
/**
|
||||
* 被邀请加入新任务时的处理逻辑。子类必须实现。
|
||||
* @param {Object} task - 任务对象
|
||||
* @throws {Error} 如果未实现
|
||||
*/
|
||||
async onTaskInvited(task) {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* 兼容别名,推荐使用 onTaskInvited
|
||||
* @param {Object} task - 任务对象
|
||||
*/
|
||||
async onTaskAssigned(task) {
|
||||
return this.onTaskInvited(task);
|
||||
}
|
||||
|
||||
/**
|
||||
* 收到任务消息时的处理逻辑。子类可选覆盖。
|
||||
* @param {string} taskId - 任务 ID
|
||||
* @param {Object} payload - 消息载荷
|
||||
*/
|
||||
async onTaskMessage(taskId, payload) {
|
||||
// 子类可覆盖
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务状态/内容变更时的处理逻辑。子类可选覆盖。
|
||||
* @param {string} taskId - 任务 ID
|
||||
* @param {Object} changes - 变更内容
|
||||
*/
|
||||
async onTaskUpdated(taskId, changes) {
|
||||
// 子类可覆盖
|
||||
}
|
||||
|
||||
/**
|
||||
* WebSocket 连上后收到待恢复任务列表时调用。子类可覆盖。
|
||||
* @param {Array} tasks - 待恢复任务列表,格式为 [{id, title, status}, ...]
|
||||
*/
|
||||
async onPendingRecovery(tasks) {
|
||||
// 子类可覆盖
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接成功后的回调。子类可选覆盖。
|
||||
*/
|
||||
async onConnected() {
|
||||
// 子类可覆盖
|
||||
}
|
||||
|
||||
/**
|
||||
* WebSocket 断开时的回调,子类可覆盖
|
||||
*/
|
||||
async onDisconnected() {
|
||||
// 子类可覆盖
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动 Worker 完整生命周期。
|
||||
*
|
||||
* 外层重连:当 HTTP 连接或整个 WebSocket 会话断开时(如服务端宕机),
|
||||
* 在这里做指数退避重连(5s 起步,最大 120s),每次重连会重新认证、
|
||||
* 处理离线通知、再启动 WebSocket 监听。
|
||||
*
|
||||
* 内层重连由 client.listen() 负责:当 WebSocket 连接断开但 HTTP
|
||||
* 会话仍然有效时,listen() 内部自动重连(2s 起步,最大 60s)。
|
||||
*
|
||||
* 两层的区别:
|
||||
* - 内层(listen):仅 WebSocket 断线重连,不涉及重新认证
|
||||
* - 外层(run):完整的会话重建,包括重新认证和离线通知恢复
|
||||
*/
|
||||
async run() {
|
||||
this._running = true;
|
||||
this._reconnectDelay = 5000;
|
||||
|
||||
while (this._running) {
|
||||
try {
|
||||
this.client = new ArrayClient(this.serverUrl, this.apiKey);
|
||||
await this.client.connect();
|
||||
this._reconnectDelay = 5000; // 重置退避
|
||||
|
||||
console.log(`Worker ${this.client.workerId} 已连接到平台`);
|
||||
|
||||
// 处理离线期间的通知
|
||||
await this._processOfflineNotifications();
|
||||
|
||||
// 启动 WebSocket 监听
|
||||
console.log('开始监听实时事件...');
|
||||
|
||||
const wsPromise = new Promise((resolve, reject) => {
|
||||
const closeFn = this.client.listen({
|
||||
onEvent: (event) => {
|
||||
this._handleEvent(event).catch(err => {
|
||||
console.error('Error handling event:', err);
|
||||
});
|
||||
},
|
||||
onConnected: () => {
|
||||
this.onConnected().catch(err => {
|
||||
console.error('Error in onConnected:', err);
|
||||
});
|
||||
},
|
||||
onDisconnected: () => {
|
||||
// WebSocket 断开,结束当前循环,外层会重连
|
||||
resolve();
|
||||
},
|
||||
onError: (err) => {
|
||||
console.error('WebSocket error:', err);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 等待 WebSocket 连接断开
|
||||
await wsPromise;
|
||||
|
||||
// 正常断开(非异常)
|
||||
await this.onDisconnected();
|
||||
|
||||
} catch (err) {
|
||||
if (!this._running) break;
|
||||
|
||||
console.warn(`连接失败: ${err.message},${this._reconnectDelay / 1000}s 后重试`);
|
||||
await this.onDisconnected();
|
||||
|
||||
// 等待后重试
|
||||
await this._sleep(this._reconnectDelay);
|
||||
this._reconnectDelay = Math.min(this._reconnectDelay * 2, 120000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止适配器
|
||||
*/
|
||||
stop() {
|
||||
this._running = false;
|
||||
if (this.client) {
|
||||
this.client.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理离线期间积累的通知
|
||||
* @private
|
||||
*/
|
||||
async _processOfflineNotifications() {
|
||||
const { notifications, total } = await this.client.getNotifications({ isRead: false });
|
||||
|
||||
if (notifications && notifications.length > 0) {
|
||||
console.log(`处理 ${notifications.length} 条离线通知...`);
|
||||
|
||||
for (const n of notifications) {
|
||||
const event = {
|
||||
eventType: n.event_type,
|
||||
payload: n.payload || {},
|
||||
timestamp: n.created_at
|
||||
};
|
||||
await this._handleEvent(event);
|
||||
await this.client.markNotificationRead(n.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分发事件到对应的处理方法
|
||||
* @private
|
||||
* @param {Object} event - 事件对象
|
||||
*/
|
||||
async _handleEvent(event) {
|
||||
const { eventType, payload } = event;
|
||||
|
||||
if (eventType === 'task.assigned') {
|
||||
const taskId = payload?.task_id;
|
||||
if (taskId) {
|
||||
const task = await this.client.getTask(taskId);
|
||||
await this.onTaskInvited(task);
|
||||
}
|
||||
} else if (eventType === 'participant.joined') {
|
||||
const taskId = payload?.task_id;
|
||||
if (taskId) {
|
||||
const task = await this.client.getTask(taskId);
|
||||
await this.onTaskInvited(task);
|
||||
}
|
||||
} else if (eventType === 'tasks.pending_recovery') {
|
||||
const tasks = payload?.tasks || [];
|
||||
await this.onPendingRecovery(tasks);
|
||||
} else if (eventType === 'task.message') {
|
||||
const taskId = payload?.task_id;
|
||||
if (taskId) {
|
||||
await this.onTaskMessage(taskId, payload);
|
||||
}
|
||||
} else if (eventType === 'task.updated') {
|
||||
const taskId = payload?.task_id;
|
||||
const changes = payload?.changes || {};
|
||||
if (taskId) {
|
||||
await this.onTaskUpdated(taskId, changes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 睡眠指定毫秒
|
||||
* @private
|
||||
* @param {number} ms
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
_sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,2 @@
|
||||
export { ArrayClient } from './client.js';
|
||||
export { BaseAdapter } from './adapter.js';
|
||||
Reference in New Issue
Block a user