feat: 初始化 React + shadcn/ui 项目 - 列阵工作台重构

This commit is contained in:
admin
2026-06-02 00:46:16 +08:00
commit d962256165
68 changed files with 17092 additions and 0 deletions
+41
View File
@@ -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
}
}
}
}
}
+11
View File
@@ -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"
}
}
+291
View File
@@ -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));
}
}
+1155
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -0,0 +1,2 @@
export { ArrayClient } from './client.js';
export { BaseAdapter } from './adapter.js';