From b587576e8560323a5b5be7aff434f3f1dc16a486 Mon Sep 17 00:00:00 2001 From: tanglong <842690096@qq.com> Date: Thu, 18 Jun 2026 13:50:03 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8A=96=E9=9F=B3=E6=95=B0=E6=8D=AE=E8=8E=B7?= =?UTF-8?q?=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + CLIENT_README.txt | 19 + dist/agent-foundation-client-yuedong.zip | Bin 0 -> 8994 bytes .../CLIENT_README.txt | 19 + .../package-lock.json | 57 ++ .../package.json | 15 + .../scripts/local_douyin_worker.js | 507 ++++++++++++++++++ .../scripts/start_local_client.ps1 | 139 +++++ .../start_yuedong_client.bat | 5 + package-lock.json | 57 ++ package.json | 15 + scripts/local_douyin_worker.js | 507 ++++++++++++++++++ scripts/local_media_worker.ps1 | 71 +++ scripts/start_local_client.ps1 | 139 +++++ .../AbstractPlaceholderCapabilityHandler.java | 8 +- .../capability/AgentCapabilityHandler.java | 6 +- .../agent/capability/CapabilityRegistry.java | 6 +- .../ContentGenerationCapabilityHandler.java | 6 +- ...stomerDashboardDailyCapabilityHandler.java | 14 +- ...cCallComplianceDailyCapabilityHandler.java | 6 +- .../DataSyncStatusCapabilityHandler.java | 6 +- .../PeriodSummaryReportCapabilityHandler.java | 6 +- ...udentAttendanceDailyCapabilityHandler.java | 6 +- .../WecomOperationDailyCapabilityHandler.java | 6 +- .../OperationReportController.java | 5 +- .../dto}/AgentCapabilityResponse.java | 4 +- .../agent/{ => report/model}/AgentIntent.java | 2 +- .../model}/AgentRequestContext.java | 2 +- .../service}/OperationReportService.java | 6 +- .../controller}/AgentRuntimeController.java | 6 +- .../dto}/AgentMessageRequest.java | 2 +- .../dto}/AgentMessageResponse.java | 2 +- .../service}/AgentOrchestratorService.java | 17 +- .../service}/IntentRecognitionService.java | 8 +- .../agentfoundation/api/DeepseekClient.java | 3 + .../api/{ => dto}/DeepseekChatRequest.java | 2 +- .../api/{ => dto}/DeepseekChatResponse.java | 2 +- .../controller/ChatController.java | 4 +- .../CrmDatabaseController.java | 4 +- .../crm/{ => service}/CrmDatabaseService.java | 2 +- .../{ => dto}/EmployeeAccountMapping.java | 2 +- .../{ => dto}/EmployeeDataPermission.java | 2 +- .../{ => dto}/EmployeeDirectoryResponse.java | 2 +- .../EmployeeDirectoryService.java | 6 +- .../{domain => dto/chat}/ChatRequest.java | 2 +- .../{domain => dto/chat}/ChatResponse.java | 2 +- .../wecom/{ => client}/WecomClient.java | 11 +- .../WecomCallbackController.java | 9 +- .../wecom/{ => dto}/WecomApiResponse.java | 2 +- .../WecomExternalContactBatchRequest.java | 2 +- .../WecomExternalContactBatchResponse.java | 2 +- .../WecomFollowUserListResponse.java | 2 +- .../{ => dto}/WecomSendMessageRequest.java | 2 +- .../{ => dto}/WecomStudentContactStats.java | 2 +- .../wecom/{ => dto}/WecomTokenResponse.java | 2 +- .../WecomUserBehaviorDataRequest.java | 2 +- .../WecomUserBehaviorDataResponse.java | 2 +- .../{ => model}/WecomEncryptedEnvelope.java | 2 +- .../{ => model}/WecomIncomingMessage.java | 2 +- .../{ => service}/WecomCryptoService.java | 2 +- .../WecomStudentContactStatsService.java | 7 +- .../media/controller/MediaTaskController.java | 71 +++ .../controller/MediaWorkerController.java | 53 ++ .../media/dto/CreateMediaTaskRequest.java | 15 + .../media/dto/MediaTaskListResponse.java | 12 + .../media/dto/MediaTaskResponse.java | 38 ++ .../media/dto/WorkerPollRequest.java | 10 + .../media/dto/WorkerTaskResultRequest.java | 12 + .../media/model/MediaTask.java | 162 ++++++ .../media/model/MediaTaskStatus.java | 8 + .../media/model/MediaTaskType.java | 8 + .../media/service/MediaTaskService.java | 118 ++++ .../controller/JumpRopeAiController.java | 10 +- .../{ => dto}/JumpRopeAiResponse.java | 4 +- .../{ => dto}/JumpRopeAnalyzeRequest.java | 2 +- .../{ => dto}/JumpRopeChatRequest.java | 2 +- .../{ => model}/JumpRopeExerciseData.java | 2 +- .../jumprope/{ => model}/JumpRopeMessage.java | 2 +- .../jumprope/{ => model}/JumpRopeSession.java | 2 +- .../JumpRopeVideoRecommendation.java | 2 +- .../jumprope/{ => model}/MiniProgramUser.java | 2 +- .../JumpRopeAiRepository.java | 8 +- .../{ => service}/JumpRopeAiService.java | 16 +- .../agentfoundation/service/ChatService.java | 8 +- start_local_client.bat | 5 + start_yuedong_client.bat | 5 + 86 files changed, 2243 insertions(+), 94 deletions(-) create mode 100644 CLIENT_README.txt create mode 100644 dist/agent-foundation-client-yuedong.zip create mode 100644 dist/agent-foundation-client-yuedong/CLIENT_README.txt create mode 100644 dist/agent-foundation-client-yuedong/package-lock.json create mode 100644 dist/agent-foundation-client-yuedong/package.json create mode 100644 dist/agent-foundation-client-yuedong/scripts/local_douyin_worker.js create mode 100644 dist/agent-foundation-client-yuedong/scripts/start_local_client.ps1 create mode 100644 dist/agent-foundation-client-yuedong/start_yuedong_client.bat create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 scripts/local_douyin_worker.js create mode 100644 scripts/local_media_worker.ps1 create mode 100644 scripts/start_local_client.ps1 rename src/main/java/com/company/agentfoundation/agent/capability/{ => content}/ContentGenerationCapabilityHandler.java (74%) rename src/main/java/com/company/agentfoundation/agent/capability/{ => customer}/CustomerDashboardDailyCapabilityHandler.java (85%) rename src/main/java/com/company/agentfoundation/agent/capability/{ => ec}/EcCallComplianceDailyCapabilityHandler.java (75%) rename src/main/java/com/company/agentfoundation/agent/capability/{ => integration}/DataSyncStatusCapabilityHandler.java (75%) rename src/main/java/com/company/agentfoundation/agent/capability/{ => management}/PeriodSummaryReportCapabilityHandler.java (75%) rename src/main/java/com/company/agentfoundation/agent/capability/{ => student}/StudentAttendanceDailyCapabilityHandler.java (73%) rename src/main/java/com/company/agentfoundation/agent/capability/{ => wecom}/WecomOperationDailyCapabilityHandler.java (73%) rename src/main/java/com/company/agentfoundation/agent/{ => report/controller}/OperationReportController.java (94%) rename src/main/java/com/company/agentfoundation/agent/{ => report/dto}/AgentCapabilityResponse.java (83%) rename src/main/java/com/company/agentfoundation/agent/{ => report/model}/AgentIntent.java (96%) rename src/main/java/com/company/agentfoundation/agent/{ => report/model}/AgentRequestContext.java (84%) rename src/main/java/com/company/agentfoundation/agent/{ => report/service}/OperationReportService.java (85%) rename src/main/java/com/company/agentfoundation/agent/{ => runtime/controller}/AgentRuntimeController.java (82%) rename src/main/java/com/company/agentfoundation/agent/{ => runtime/dto}/AgentMessageRequest.java (72%) rename src/main/java/com/company/agentfoundation/agent/{ => runtime/dto}/AgentMessageResponse.java (85%) rename src/main/java/com/company/agentfoundation/agent/{ => runtime/service}/AgentOrchestratorService.java (87%) rename src/main/java/com/company/agentfoundation/agent/{ => runtime/service}/IntentRecognitionService.java (94%) rename src/main/java/com/company/agentfoundation/api/{ => dto}/DeepseekChatRequest.java (80%) rename src/main/java/com/company/agentfoundation/api/{ => dto}/DeepseekChatResponse.java (86%) rename src/main/java/com/company/agentfoundation/crm/{ => controller}/CrmDatabaseController.java (95%) rename src/main/java/com/company/agentfoundation/crm/{ => service}/CrmDatabaseService.java (98%) rename src/main/java/com/company/agentfoundation/directory/{ => dto}/EmployeeAccountMapping.java (88%) rename src/main/java/com/company/agentfoundation/directory/{ => dto}/EmployeeDataPermission.java (73%) rename src/main/java/com/company/agentfoundation/directory/{ => dto}/EmployeeDirectoryResponse.java (74%) rename src/main/java/com/company/agentfoundation/directory/{ => service}/EmployeeDirectoryService.java (90%) rename src/main/java/com/company/agentfoundation/{domain => dto/chat}/ChatRequest.java (86%) rename src/main/java/com/company/agentfoundation/{domain => dto/chat}/ChatResponse.java (84%) rename src/main/java/com/company/agentfoundation/integration/wecom/{ => client}/WecomClient.java (91%) rename src/main/java/com/company/agentfoundation/integration/wecom/{ => controller}/WecomCallbackController.java (93%) rename src/main/java/com/company/agentfoundation/integration/wecom/{ => dto}/WecomApiResponse.java (71%) rename src/main/java/com/company/agentfoundation/integration/wecom/{ => dto}/WecomExternalContactBatchRequest.java (79%) rename src/main/java/com/company/agentfoundation/integration/wecom/{ => dto}/WecomExternalContactBatchResponse.java (92%) rename src/main/java/com/company/agentfoundation/integration/wecom/{ => dto}/WecomFollowUserListResponse.java (83%) rename src/main/java/com/company/agentfoundation/integration/wecom/{ => dto}/WecomSendMessageRequest.java (95%) rename src/main/java/com/company/agentfoundation/integration/wecom/{ => dto}/WecomStudentContactStats.java (83%) rename src/main/java/com/company/agentfoundation/integration/wecom/{ => dto}/WecomTokenResponse.java (84%) rename src/main/java/com/company/agentfoundation/integration/wecom/{ => dto}/WecomUserBehaviorDataRequest.java (82%) rename src/main/java/com/company/agentfoundation/integration/wecom/{ => dto}/WecomUserBehaviorDataResponse.java (93%) rename src/main/java/com/company/agentfoundation/integration/wecom/{ => model}/WecomEncryptedEnvelope.java (93%) rename src/main/java/com/company/agentfoundation/integration/wecom/{ => model}/WecomIncomingMessage.java (96%) rename src/main/java/com/company/agentfoundation/integration/wecom/{ => service}/WecomCryptoService.java (98%) rename src/main/java/com/company/agentfoundation/integration/wecom/{ => service}/WecomStudentContactStatsService.java (93%) create mode 100644 src/main/java/com/company/agentfoundation/media/controller/MediaTaskController.java create mode 100644 src/main/java/com/company/agentfoundation/media/controller/MediaWorkerController.java create mode 100644 src/main/java/com/company/agentfoundation/media/dto/CreateMediaTaskRequest.java create mode 100644 src/main/java/com/company/agentfoundation/media/dto/MediaTaskListResponse.java create mode 100644 src/main/java/com/company/agentfoundation/media/dto/MediaTaskResponse.java create mode 100644 src/main/java/com/company/agentfoundation/media/dto/WorkerPollRequest.java create mode 100644 src/main/java/com/company/agentfoundation/media/dto/WorkerTaskResultRequest.java create mode 100644 src/main/java/com/company/agentfoundation/media/model/MediaTask.java create mode 100644 src/main/java/com/company/agentfoundation/media/model/MediaTaskStatus.java create mode 100644 src/main/java/com/company/agentfoundation/media/model/MediaTaskType.java create mode 100644 src/main/java/com/company/agentfoundation/media/service/MediaTaskService.java rename src/main/java/com/company/agentfoundation/{ => miniprogram/jumprope}/controller/JumpRopeAiController.java (74%) rename src/main/java/com/company/agentfoundation/miniprogram/jumprope/{ => dto}/JumpRopeAiResponse.java (65%) rename src/main/java/com/company/agentfoundation/miniprogram/jumprope/{ => dto}/JumpRopeAnalyzeRequest.java (80%) rename src/main/java/com/company/agentfoundation/miniprogram/jumprope/{ => dto}/JumpRopeChatRequest.java (72%) rename src/main/java/com/company/agentfoundation/miniprogram/jumprope/{ => model}/JumpRopeExerciseData.java (87%) rename src/main/java/com/company/agentfoundation/miniprogram/jumprope/{ => model}/JumpRopeMessage.java (66%) rename src/main/java/com/company/agentfoundation/miniprogram/jumprope/{ => model}/JumpRopeSession.java (79%) rename src/main/java/com/company/agentfoundation/miniprogram/jumprope/{ => model}/JumpRopeVideoRecommendation.java (76%) rename src/main/java/com/company/agentfoundation/miniprogram/jumprope/{ => model}/MiniProgramUser.java (68%) rename src/main/java/com/company/agentfoundation/miniprogram/jumprope/{ => repository}/JumpRopeAiRepository.java (95%) rename src/main/java/com/company/agentfoundation/miniprogram/jumprope/{ => service}/JumpRopeAiService.java (93%) create mode 100644 start_local_client.bat create mode 100644 start_yuedong_client.bat diff --git a/.gitignore b/.gitignore index 16b61c5..a9a4ce5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ /target/ /.idea/ *.iml +/node_modules/ +/data/platform-profiles/ diff --git a/CLIENT_README.txt b/CLIENT_README.txt new file mode 100644 index 0000000..95842fb --- /dev/null +++ b/CLIENT_README.txt @@ -0,0 +1,19 @@ +Yuedong short-video local worker + +Before first use, install: +1. Node.js LTS +2. Microsoft Edge or Google Chrome + +How to start: +1. Unzip this package to a normal folder, for example Desktop\agent-foundation-client. +2. Double-click start_yuedong_client.bat. +3. If a Douyin login page opens, login in the browser, then press Enter in the terminal window. +4. Keep the worker terminal window open while using the system. + +Server API: +http://agent.yuedongsports.com + +Local login data is stored only on this computer under: +data\platform-profiles\ + +Do not copy data\platform-profiles\ to another person. diff --git a/dist/agent-foundation-client-yuedong.zip b/dist/agent-foundation-client-yuedong.zip new file mode 100644 index 0000000000000000000000000000000000000000..430b563d004d3b07ae6eb1ff8272776f479f6e2c GIT binary patch literal 8994 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 literal 0 HcmV?d00001 diff --git a/dist/agent-foundation-client-yuedong/CLIENT_README.txt b/dist/agent-foundation-client-yuedong/CLIENT_README.txt new file mode 100644 index 0000000..95842fb --- /dev/null +++ b/dist/agent-foundation-client-yuedong/CLIENT_README.txt @@ -0,0 +1,19 @@ +Yuedong short-video local worker + +Before first use, install: +1. Node.js LTS +2. Microsoft Edge or Google Chrome + +How to start: +1. Unzip this package to a normal folder, for example Desktop\agent-foundation-client. +2. Double-click start_yuedong_client.bat. +3. If a Douyin login page opens, login in the browser, then press Enter in the terminal window. +4. Keep the worker terminal window open while using the system. + +Server API: +http://agent.yuedongsports.com + +Local login data is stored only on this computer under: +data\platform-profiles\ + +Do not copy data\platform-profiles\ to another person. diff --git a/dist/agent-foundation-client-yuedong/package-lock.json b/dist/agent-foundation-client-yuedong/package-lock.json new file mode 100644 index 0000000..08e0733 --- /dev/null +++ b/dist/agent-foundation-client-yuedong/package-lock.json @@ -0,0 +1,57 @@ +{ + "name": "agent-foundation-local-worker", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "agent-foundation-local-worker", + "dependencies": { + "playwright": "^1.61.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright": { + "version": "1.61.0", + "resolved": "https://registry.npmmirror.com/playwright/-/playwright-1.61.0.tgz", + "integrity": "sha512-Z+7BeeqQPRRzklHsVFP4KTGIyMxKUmfeRA4WisM6G3/XW6nwGeX6fX9qYaDa+CiUqpOkb2f6X3nar05R3kSuJQ==", + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.61.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.61.0", + "resolved": "https://registry.npmmirror.com/playwright-core/-/playwright-core-1.61.0.tgz", + "integrity": "sha512-caX7TrY3Ml6egyDX0WUcTHDxodl/b51y5wJOdCEA36QviK/s2g081hvmGs8eaE3DWb6NYZQ6BjO/QkNRPenoPA==", + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + } + } +} diff --git a/dist/agent-foundation-client-yuedong/package.json b/dist/agent-foundation-client-yuedong/package.json new file mode 100644 index 0000000..97c513e --- /dev/null +++ b/dist/agent-foundation-client-yuedong/package.json @@ -0,0 +1,15 @@ +{ + "name": "agent-foundation-local-worker", + "private": true, + "type": "commonjs", + "scripts": { + "douyin:login": "node scripts/local_douyin_worker.js login", + "douyin:worker": "node scripts/local_douyin_worker.js worker", + "douyin:worker:loop": "node scripts/local_douyin_worker.js worker-loop", + "douyin:check": "node scripts/local_douyin_worker.js check-login", + "douyin:metrics": "node scripts/local_douyin_worker.js metrics" + }, + "dependencies": { + "playwright": "^1.61.0" + } +} diff --git a/dist/agent-foundation-client-yuedong/scripts/local_douyin_worker.js b/dist/agent-foundation-client-yuedong/scripts/local_douyin_worker.js new file mode 100644 index 0000000..9c8c3cf --- /dev/null +++ b/dist/agent-foundation-client-yuedong/scripts/local_douyin_worker.js @@ -0,0 +1,507 @@ +const fs = require('fs'); +const path = require('path'); +const readline = require('readline'); + +const DEFAULT_BASE_URL = process.env.MEDIA_BASE_URL || 'http://localhost:8880'; +const DEFAULT_WORKER_ID = process.env.MEDIA_WORKER_ID || 'local-douyin-worker'; +const DEFAULT_WORKER_TOKEN = process.env.MEDIA_WORKER_TOKEN || 'local-worker-dev-token'; +const DEFAULT_ACCOUNT_ID = process.env.DOUYIN_ACCOUNT_ID || 'douyin_company'; +const DEFAULT_CREATOR_URL = process.env.DOUYIN_CREATOR_URL || 'https://creator.douyin.com'; +const DEFAULT_CONTENT_URL = process.env.DOUYIN_CONTENT_URL || 'https://creator.douyin.com/creator-micro/content/manage'; +const DEFAULT_PROFILE_ROOT = process.env.DOUYIN_PROFILE_ROOT || path.resolve('data/platform-profiles'); + +const LOGIN_TEXT = new RegExp([ + '\\u626b\\u7801\\u767b\\u5f55', + '\\u9a8c\\u8bc1\\u7801', + '\\u624b\\u673a\\u53f7', + '\\u767b\\u5f55' +].join('|')); + +const CREATOR_TEXT = new RegExp([ + '\\u521b\\u4f5c\\u670d\\u52a1', + '\\u53d1\\u5e03\\u4f5c\\u54c1', + '\\u5185\\u5bb9\\u7ba1\\u7406', + '\\u6570\\u636e\\u4e2d\\u5fc3', + '\\u4f5c\\u54c1\\u7ba1\\u7406', + '\\u53d1\\u5e03\\u89c6\\u9891' +].join('|')); + +function requirePlaywright() { + try { + return require('playwright'); + } catch (error) { + throw new Error('Playwright is not installed. Run npm.cmd install in the project root first.'); + } +} + +function profileDir(accountId = DEFAULT_ACCOUNT_ID) { + return path.resolve(DEFAULT_PROFILE_ROOT, accountId); +} + +function ensureDir(dir) { + fs.mkdirSync(dir, { recursive: true }); +} + +function browserChannel() { + if (process.env.DOUYIN_BROWSER_CHANNEL) { + return process.env.DOUYIN_BROWSER_CHANNEL; + } + return process.platform === 'win32' ? 'msedge' : undefined; +} + +async function launchDouyinContext({ accountId = DEFAULT_ACCOUNT_ID, headless = false } = {}) { + const { chromium } = requirePlaywright(); + const dir = profileDir(accountId); + ensureDir(dir); + const channel = browserChannel(); + const options = { + headless, + viewport: { width: 1440, height: 1000 }, + acceptDownloads: true, + args: [ + '--disable-blink-features=AutomationControlled', + '--disable-dev-shm-usage' + ] + }; + if (channel) { + options.channel = channel; + } + return chromium.launchPersistentContext(dir, options); +} + +async function waitForEnter(message) { + const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); + return new Promise(resolve => { + rl.question(message, () => { + rl.close(); + resolve(); + }); + }); +} + +async function login() { + const accountId = process.argv[3] || DEFAULT_ACCOUNT_ID; + const context = await launchDouyinContext({ accountId, headless: false }); + const page = context.pages()[0] || await context.newPage(); + await page.goto(DEFAULT_CREATOR_URL, { waitUntil: 'domcontentloaded', timeout: 60000 }); + console.log(`Douyin login profile: ${profileDir(accountId)}`); + console.log('Please scan/login in the opened browser. Keep this window open until login succeeds.'); + await waitForEnter('Press Enter here after Douyin creator center is logged in...'); + const status = await detectLogin(page, accountId); + await context.close(); + console.log(JSON.stringify(status, null, 2)); +} + +async function checkLogin(accountId = DEFAULT_ACCOUNT_ID) { + const context = await launchDouyinContext({ accountId, headless: true }); + const page = context.pages()[0] || await context.newPage(); + try { + await page.goto(DEFAULT_CREATOR_URL, { waitUntil: 'domcontentloaded', timeout: 60000 }); + await page.waitForTimeout(2500); + return await detectLogin(page, accountId); + } finally { + await context.close(); + } +} + +async function detectLogin(page, accountId = DEFAULT_ACCOUNT_ID) { + const url = page.url(); + const title = await page.title().catch(() => ''); + const bodyText = await page.locator('body').innerText({ timeout: 5000 }).catch(() => ''); + const loginVisible = LOGIN_TEXT.test(bodyText); + const creatorVisible = CREATOR_TEXT.test(bodyText); + const loggedIn = creatorVisible || (!loginVisible && /creator\.douyin\.com/.test(url)); + return { + loggedIn, + status: loggedIn ? 'SUCCESS' : 'FAILED', + message: loggedIn ? 'Douyin creator profile is logged in.' : 'Douyin creator profile is not logged in.', + url, + title, + profileDir: profileDir(accountId) + }; +} + +async function collectMetrics(task) { + const payload = task.payload || {}; + const accountId = task.accountId || DEFAULT_ACCOUNT_ID; + const targetUrl = payload.metricsUrl || payload.contentUrl || DEFAULT_CONTENT_URL; + const headless = payload.headless === false ? false : true; + const context = await launchDouyinContext({ accountId, headless }); + const page = context.pages()[0] || await context.newPage(); + try { + await page.goto(targetUrl, { waitUntil: 'domcontentloaded', timeout: 60000 }); + await page.waitForTimeout(Number(payload.waitMs || 5000)); + const status = await detectLogin(page, accountId); + if (!status.loggedIn) { + return { + status: 'FAILED', + errorMessage: status.message, + result: { + action: 'sync_metrics', + loggedIn: false, + ...status + } + }; + } + + const text = await page.locator('body').innerText({ timeout: 15000 }).catch(() => ''); + const screenshot = await saveScreenshot(page, task.id); + const videos = await collectVideoMetrics(page, Number(payload.maxVideos || 20)); + const metrics = videos.length > 0 ? summarizeVideos(videos) : extractMetrics(text); + return { + status: 'SUCCESS', + errorMessage: null, + result: { + action: 'sync_metrics', + loggedIn: true, + url: page.url(), + title: await page.title().catch(() => ''), + metrics, + videos, + videoCount: videos.length, + extractionMode: videos.length > 0 ? 'video_list' : 'page_summary', + screenshot, + pageTextPreview: compactText(text).slice(0, 2000) + } + }; + } finally { + await context.close().catch(() => {}); + } +} + +async function saveScreenshot(page, taskId) { + const dir = path.resolve('target/media/douyin'); + ensureDir(dir); + const file = path.join(dir, `${taskId || Date.now()}-metrics.png`); + await page.screenshot({ path: file, fullPage: true }).catch(() => {}); + return file; +} + +function compactText(text) { + return (text || '').replace(/\s+/g, ' ').trim(); +} + +function extractMetrics(text) { + const compact = compactText(text); + return { + views: findMetric(compact, ['\\u64ad\\u653e', '\\u6d4f\\u89c8', '\\u89c2\\u770b', '\\u5c55\\u73b0']), + likes: findMetric(compact, ['\\u70b9\\u8d5e']), + comments: findMetric(compact, ['\\u8bc4\\u8bba']), + collects: findMetric(compact, ['\\u6536\\u85cf']), + shares: findMetric(compact, ['\\u5206\\u4eab', '\\u8f6c\\u53d1']) + }; +} + +async function collectVideoMetrics(page, limit = 20) { + await autoScroll(page); + const blocks = await page.evaluate(maxBlocks => { + const selectors = [ + 'tr', + 'article', + 'li', + '[class*="card"]', + '[class*="item"]', + '[class*="work"]', + '[class*="video"]', + '[class*="content"]' + ]; + const seen = new Set(); + const rows = []; + for (const selector of selectors) { + for (const element of document.querySelectorAll(selector)) { + if (rows.length >= maxBlocks) { + break; + } + const rect = element.getBoundingClientRect(); + if (rect.width < 160 || rect.height < 40) { + continue; + } + const text = (element.innerText || '').replace(/\s+/g, ' ').trim(); + if (text.length < 12 || text.length > 1200) { + continue; + } + const signature = text.slice(0, 120); + if (seen.has(signature)) { + continue; + } + seen.add(signature); + const link = element.querySelector('a[href]'); + rows.push({ + text, + href: link ? link.href : '', + rect: { + x: Math.round(rect.x), + y: Math.round(rect.y), + width: Math.round(rect.width), + height: Math.round(rect.height) + } + }); + } + } + return rows; + }, Math.max(limit * 8, 80)); + + const videos = []; + const seen = new Set(); + for (const block of blocks) { + const video = parseVideoBlock(block); + if (!video) { + continue; + } + const key = `${video.title}|${video.views}|${video.likes}|${video.comments}|${video.href}`; + if (seen.has(key)) { + continue; + } + seen.add(key); + videos.push(video); + if (videos.length >= limit) { + break; + } + } + return videos; +} + +async function autoScroll(page) { + await page.evaluate(async () => { + await new Promise(resolve => { + let count = 0; + const timer = setInterval(() => { + window.scrollBy(0, Math.floor(window.innerHeight * 0.8)); + count += 1; + if (count >= 3) { + clearInterval(timer); + resolve(); + } + }, 500); + }); + }).catch(() => {}); + await page.waitForTimeout(800).catch(() => {}); +} + +function parseVideoBlock(block) { + const text = compactText(block.text); + const metrics = extractMetrics(text); + const score = metrics.views + metrics.likes + metrics.comments + metrics.collects + metrics.shares; + if (score <= 0) { + return null; + } + const title = extractVideoTitle(text); + return { + title, + href: block.href || '', + views: metrics.views, + likes: metrics.likes, + comments: metrics.comments, + collects: metrics.collects, + shares: metrics.shares, + interactionCount: metrics.likes + metrics.comments + metrics.collects + metrics.shares, + interactionRate: metrics.views <= 0 + ? 0 + : (metrics.likes + metrics.comments + metrics.collects + metrics.shares) / metrics.views, + rawTextPreview: text.slice(0, 500) + }; +} + +function extractVideoTitle(text) { + const cleaned = text + .replace(/\d{4}[-/.]\d{1,2}[-/.]\d{1,2}.*/g, '') + .replace(/(\u64ad\u653e|\u6d4f\u89c8|\u89c2\u770b|\u5c55\u73b0|\u70b9\u8d5e|\u8bc4\u8bba|\u6536\u85cf|\u5206\u4eab|\u8f6c\u53d1).*/g, '') + .trim(); + if (cleaned.length >= 2) { + return cleaned.slice(0, 120); + } + return text.slice(0, 80); +} + +function summarizeVideos(videos) { + const summary = { + views: 0, + likes: 0, + comments: 0, + collects: 0, + shares: 0 + }; + for (const video of videos) { + summary.views += Number(video.views || 0); + summary.likes += Number(video.likes || 0); + summary.comments += Number(video.comments || 0); + summary.collects += Number(video.collects || 0); + summary.shares += Number(video.shares || 0); + } + return summary; +} + +function findMetric(text, labels) { + for (const label of labels) { + const labelPattern = new RegExp(label); + const decodedLabel = label.replace(/\\u([0-9a-fA-F]{4})/g, (_, code) => String.fromCharCode(parseInt(code, 16))); + const after = new RegExp(`${label}[^0-9\\d]{0,16}([0-9][0-9,.]*)(\\s*[\\u4e07\\u5343wWkK]?)`); + const before = new RegExp(`([0-9][0-9,.]*)(\\s*[\\u4e07\\u5343wWkK]?)[^0-9\\d]{0,16}${label}`); + if (labelPattern.test(text)) { + const afterMatch = text.match(after); + if (afterMatch) { + return parseMetricNumber(afterMatch[1], afterMatch[2]); + } + const beforeMatch = text.match(before); + if (beforeMatch) { + return parseMetricNumber(beforeMatch[1], beforeMatch[2]); + } + const looseIndex = text.indexOf(decodedLabel); + if (looseIndex >= 0) { + const around = text.slice(Math.max(0, looseIndex - 30), looseIndex + 60); + const number = around.match(/([0-9][0-9,.]*)(\s*[\u4e07\u5343wWkK]?)/); + if (number) { + return parseMetricNumber(number[1], number[2]); + } + } + } + } + return 0; +} + +function parseMetricNumber(raw, unit = '') { + const value = Number(String(raw).replace(/,/g, '')); + if (!Number.isFinite(value)) { + return 0; + } + const normalizedUnit = unit.trim().toLowerCase(); + if (normalizedUnit.includes('\u4e07') || normalizedUnit.includes('w')) { + return Math.round(value * 10000); + } + if (normalizedUnit.includes('\u5343') || normalizedUnit.includes('k')) { + return Math.round(value * 1000); + } + return Math.round(value); +} + +async function pollTasks() { + const response = await fetch(`${DEFAULT_BASE_URL}/v1/media/workers/poll`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Worker-Token': DEFAULT_WORKER_TOKEN + }, + body: JSON.stringify({ + workerId: DEFAULT_WORKER_ID, + platforms: ['douyin'], + maxTasks: 5 + }) + }); + if (!response.ok) { + throw new Error(`Poll failed: ${response.status} ${await response.text()}`); + } + return response.json(); +} + +async function submitResult(taskId, body) { + const response = await fetch(`${DEFAULT_BASE_URL}/v1/media/workers/tasks/${taskId}/result`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Worker-Token': DEFAULT_WORKER_TOKEN + }, + body: JSON.stringify(body) + }); + if (!response.ok) { + throw new Error(`Submit result failed: ${response.status} ${await response.text()}`); + } + return response.json(); +} + +async function handleTask(task) { + const payload = task.payload || {}; + const action = payload.action || (task.taskType === 'SYNC_METRICS' ? 'sync_metrics' : 'check_login'); + if (action === 'check_login') { + const status = await checkLogin(task.accountId || DEFAULT_ACCOUNT_ID); + return submitResult(task.id, { + status: status.loggedIn ? 'SUCCESS' : 'FAILED', + errorMessage: status.loggedIn ? null : status.message, + result: { + action, + loggedIn: status.loggedIn, + message: status.message, + url: status.url, + title: status.title, + profileDir: profileDir(task.accountId || DEFAULT_ACCOUNT_ID) + } + }); + } + + if (action === 'sync_metrics') { + const result = await collectMetrics(task); + return submitResult(task.id, result); + } + + return submitResult(task.id, { + status: 'FAILED', + errorMessage: `Unsupported local Douyin worker action: ${action}`, + result: { action } + }); +} + +async function workerOnce() { + const tasks = await pollTasks(); + if (!Array.isArray(tasks) || tasks.length === 0) { + console.log('No pending Douyin tasks.'); + return; + } + for (const task of tasks) { + const result = await handleTask(task); + console.log(`Completed ${task.id}: ${result.status}`); + } +} + +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +async function workerLoop() { + const intervalMs = Number(process.env.MEDIA_WORKER_INTERVAL_MS || 30000); + console.log(`Douyin worker loop started. baseUrl=${DEFAULT_BASE_URL}, intervalMs=${intervalMs}`); + while (true) { + try { + await workerOnce(); + } catch (error) { + console.error(error.stack || error.message); + } + await sleep(intervalMs); + } +} + +async function main() { + const mode = process.argv[2]; + if (mode === 'login') { + await login(); + return; + } + if (mode === 'check-login') { + const status = await checkLogin(process.argv[3] || DEFAULT_ACCOUNT_ID); + console.log(JSON.stringify(status, null, 2)); + process.exit(status.loggedIn ? 0 : 2); + } + if (mode === 'metrics') { + const task = { + id: `manual_${Date.now()}`, + taskType: 'SYNC_METRICS', + platform: 'douyin', + accountId: process.argv[3] || DEFAULT_ACCOUNT_ID, + payload: { action: 'sync_metrics', headless: false } + }; + console.log(JSON.stringify((await collectMetrics(task)).result, null, 2)); + return; + } + if (mode === 'worker') { + await workerOnce(); + return; + } + if (mode === 'worker-loop') { + await workerLoop(); + return; + } + throw new Error('Usage: node scripts/local_douyin_worker.js [accountId]'); +} + +main().catch(error => { + console.error(error.stack || error.message); + process.exit(1); +}); diff --git a/dist/agent-foundation-client-yuedong/scripts/start_local_client.ps1 b/dist/agent-foundation-client-yuedong/scripts/start_local_client.ps1 new file mode 100644 index 0000000..d20221f --- /dev/null +++ b/dist/agent-foundation-client-yuedong/scripts/start_local_client.ps1 @@ -0,0 +1,139 @@ +param( + [string]$BaseUrl = "http://localhost:8880", + [string]$AccountId = "douyin_company", + [int]$WorkerIntervalSeconds = 30, + [switch]$RemoteBackend +) + +$ErrorActionPreference = "Stop" +$Root = Resolve-Path (Join-Path $PSScriptRoot "..") +Set-Location $Root + +function Write-Step([string]$Message) { + Write-Host "" + Write-Host "==> $Message" -ForegroundColor Cyan +} + +function Require-Command([string]$Name, [string]$InstallHint) { + if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) { + throw "$Name is not installed or not in PATH. $InstallHint" + } +} + +function Test-BackendHealth([string]$Url) { + try { + $response = Invoke-WebRequest -UseBasicParsing -Uri "$Url/actuator/health" -TimeoutSec 3 + return $response.StatusCode -ge 200 -and $response.StatusCode -lt 300 + } catch { + return $false + } +} + +function Find-BackendJar { + $jars = Get-ChildItem -Path (Join-Path $Root "target") -Filter "agent-foundation-*.jar" -ErrorAction SilentlyContinue | + Where-Object { $_.Name -notlike "*.original" } | + Sort-Object LastWriteTime -Descending + if ($jars.Count -eq 0) { + return $null + } + return $jars[0].FullName +} + +function Test-BrowserInstalled { + $paths = @( + "$env:ProgramFiles\Microsoft\Edge\Application\msedge.exe", + "${env:ProgramFiles(x86)}\Microsoft\Edge\Application\msedge.exe", + "$env:ProgramFiles\Google\Chrome\Application\chrome.exe", + "${env:ProgramFiles(x86)}\Google\Chrome\Application\chrome.exe" + ) + foreach ($path in $paths) { + if ($path -and (Test-Path $path)) { + return $true + } + } + return $false +} + +function Test-LocalBaseUrl([string]$Url) { + return $Url -match "^https?://(localhost|127\.0\.0\.1)(:\d+)?/?$" +} + +Write-Step "Checking local runtime" +Require-Command "node" "Install Node.js LTS from https://nodejs.org/" +Require-Command "npm.cmd" "Install Node.js LTS from https://nodejs.org/" +if (-not $RemoteBackend) { + Require-Command "java" "Install JDK 17+ or JRE 17+." +} +if (-not (Test-BrowserInstalled)) { + throw "Microsoft Edge or Google Chrome is required." +} + +Write-Step "Installing Node dependencies if needed" +if (-not (Test-Path (Join-Path $Root "node_modules\playwright"))) { + & npm.cmd install + if ($LASTEXITCODE -ne 0) { + throw "npm install failed." + } +} else { + Write-Host "Node dependencies already installed." +} + +if ($RemoteBackend -or -not (Test-LocalBaseUrl $BaseUrl)) { + Write-Step "Checking remote backend" + if (Test-BackendHealth $BaseUrl) { + Write-Host "Remote backend is reachable: $BaseUrl" + } else { + Write-Host "Remote backend health check did not return OK. Continuing because /actuator/health may be disabled." + Write-Host "Worker will connect to: $BaseUrl" + } +} else { + Write-Step "Starting backend service if needed" + if (Test-BackendHealth $BaseUrl) { + Write-Host "Backend is already running: $BaseUrl" + } else { + $jar = Find-BackendJar + if (-not $jar) { + throw "Backend jar not found under target\. Build the Spring Boot jar first." + } + $backendCommand = "Set-Location '$Root'; java -jar '$jar'" + Start-Process powershell.exe -ArgumentList @("-NoExit", "-ExecutionPolicy", "Bypass", "-Command", $backendCommand) -WindowStyle Normal + Write-Host "Waiting for backend health..." + $ready = $false + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Seconds 2 + if (Test-BackendHealth $BaseUrl) { + $ready = $true + break + } + } + if (-not $ready) { + throw "Backend did not become healthy at $BaseUrl. Check the backend window." + } + } +} + +Write-Step "Checking Douyin login" +$env:MEDIA_BASE_URL = $BaseUrl +$env:DOUYIN_ACCOUNT_ID = $AccountId +$checkOutput = & npm.cmd run douyin:check -- $AccountId 2>&1 +if ($LASTEXITCODE -ne 0) { + Write-Host $checkOutput + Write-Host "" + Write-Host "Douyin login is required. A browser will open. Login, then press Enter in that window." + & npm.cmd run douyin:login -- $AccountId + if ($LASTEXITCODE -ne 0) { + throw "Douyin login failed." + } +} else { + Write-Host "Douyin login profile is ready." +} + +Write-Step "Starting Douyin worker loop" +$workerCommand = "Set-Location '$Root'; `$env:MEDIA_BASE_URL='$BaseUrl'; `$env:DOUYIN_ACCOUNT_ID='$AccountId'; `$env:MEDIA_WORKER_INTERVAL_MS='$($WorkerIntervalSeconds * 1000)'; npm.cmd run douyin:worker:loop" +Start-Process powershell.exe -ArgumentList @("-NoExit", "-ExecutionPolicy", "Bypass", "-Command", $workerCommand) -WindowStyle Normal + +Write-Host "" +Write-Host "Local client is ready." +Write-Host "Backend: $BaseUrl" +Write-Host "Account: $AccountId" +Write-Host "Worker polling interval: $WorkerIntervalSeconds seconds" diff --git a/dist/agent-foundation-client-yuedong/start_yuedong_client.bat b/dist/agent-foundation-client-yuedong/start_yuedong_client.bat new file mode 100644 index 0000000..df335ef --- /dev/null +++ b/dist/agent-foundation-client-yuedong/start_yuedong_client.bat @@ -0,0 +1,5 @@ +@echo off +setlocal +cd /d "%~dp0" +powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0scripts\start_local_client.ps1" -BaseUrl "http://agent.yuedongsports.com" -RemoteBackend +pause diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..08e0733 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,57 @@ +{ + "name": "agent-foundation-local-worker", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "agent-foundation-local-worker", + "dependencies": { + "playwright": "^1.61.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright": { + "version": "1.61.0", + "resolved": "https://registry.npmmirror.com/playwright/-/playwright-1.61.0.tgz", + "integrity": "sha512-Z+7BeeqQPRRzklHsVFP4KTGIyMxKUmfeRA4WisM6G3/XW6nwGeX6fX9qYaDa+CiUqpOkb2f6X3nar05R3kSuJQ==", + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.61.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.61.0", + "resolved": "https://registry.npmmirror.com/playwright-core/-/playwright-core-1.61.0.tgz", + "integrity": "sha512-caX7TrY3Ml6egyDX0WUcTHDxodl/b51y5wJOdCEA36QviK/s2g081hvmGs8eaE3DWb6NYZQ6BjO/QkNRPenoPA==", + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..97c513e --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "agent-foundation-local-worker", + "private": true, + "type": "commonjs", + "scripts": { + "douyin:login": "node scripts/local_douyin_worker.js login", + "douyin:worker": "node scripts/local_douyin_worker.js worker", + "douyin:worker:loop": "node scripts/local_douyin_worker.js worker-loop", + "douyin:check": "node scripts/local_douyin_worker.js check-login", + "douyin:metrics": "node scripts/local_douyin_worker.js metrics" + }, + "dependencies": { + "playwright": "^1.61.0" + } +} diff --git a/scripts/local_douyin_worker.js b/scripts/local_douyin_worker.js new file mode 100644 index 0000000..9c8c3cf --- /dev/null +++ b/scripts/local_douyin_worker.js @@ -0,0 +1,507 @@ +const fs = require('fs'); +const path = require('path'); +const readline = require('readline'); + +const DEFAULT_BASE_URL = process.env.MEDIA_BASE_URL || 'http://localhost:8880'; +const DEFAULT_WORKER_ID = process.env.MEDIA_WORKER_ID || 'local-douyin-worker'; +const DEFAULT_WORKER_TOKEN = process.env.MEDIA_WORKER_TOKEN || 'local-worker-dev-token'; +const DEFAULT_ACCOUNT_ID = process.env.DOUYIN_ACCOUNT_ID || 'douyin_company'; +const DEFAULT_CREATOR_URL = process.env.DOUYIN_CREATOR_URL || 'https://creator.douyin.com'; +const DEFAULT_CONTENT_URL = process.env.DOUYIN_CONTENT_URL || 'https://creator.douyin.com/creator-micro/content/manage'; +const DEFAULT_PROFILE_ROOT = process.env.DOUYIN_PROFILE_ROOT || path.resolve('data/platform-profiles'); + +const LOGIN_TEXT = new RegExp([ + '\\u626b\\u7801\\u767b\\u5f55', + '\\u9a8c\\u8bc1\\u7801', + '\\u624b\\u673a\\u53f7', + '\\u767b\\u5f55' +].join('|')); + +const CREATOR_TEXT = new RegExp([ + '\\u521b\\u4f5c\\u670d\\u52a1', + '\\u53d1\\u5e03\\u4f5c\\u54c1', + '\\u5185\\u5bb9\\u7ba1\\u7406', + '\\u6570\\u636e\\u4e2d\\u5fc3', + '\\u4f5c\\u54c1\\u7ba1\\u7406', + '\\u53d1\\u5e03\\u89c6\\u9891' +].join('|')); + +function requirePlaywright() { + try { + return require('playwright'); + } catch (error) { + throw new Error('Playwright is not installed. Run npm.cmd install in the project root first.'); + } +} + +function profileDir(accountId = DEFAULT_ACCOUNT_ID) { + return path.resolve(DEFAULT_PROFILE_ROOT, accountId); +} + +function ensureDir(dir) { + fs.mkdirSync(dir, { recursive: true }); +} + +function browserChannel() { + if (process.env.DOUYIN_BROWSER_CHANNEL) { + return process.env.DOUYIN_BROWSER_CHANNEL; + } + return process.platform === 'win32' ? 'msedge' : undefined; +} + +async function launchDouyinContext({ accountId = DEFAULT_ACCOUNT_ID, headless = false } = {}) { + const { chromium } = requirePlaywright(); + const dir = profileDir(accountId); + ensureDir(dir); + const channel = browserChannel(); + const options = { + headless, + viewport: { width: 1440, height: 1000 }, + acceptDownloads: true, + args: [ + '--disable-blink-features=AutomationControlled', + '--disable-dev-shm-usage' + ] + }; + if (channel) { + options.channel = channel; + } + return chromium.launchPersistentContext(dir, options); +} + +async function waitForEnter(message) { + const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); + return new Promise(resolve => { + rl.question(message, () => { + rl.close(); + resolve(); + }); + }); +} + +async function login() { + const accountId = process.argv[3] || DEFAULT_ACCOUNT_ID; + const context = await launchDouyinContext({ accountId, headless: false }); + const page = context.pages()[0] || await context.newPage(); + await page.goto(DEFAULT_CREATOR_URL, { waitUntil: 'domcontentloaded', timeout: 60000 }); + console.log(`Douyin login profile: ${profileDir(accountId)}`); + console.log('Please scan/login in the opened browser. Keep this window open until login succeeds.'); + await waitForEnter('Press Enter here after Douyin creator center is logged in...'); + const status = await detectLogin(page, accountId); + await context.close(); + console.log(JSON.stringify(status, null, 2)); +} + +async function checkLogin(accountId = DEFAULT_ACCOUNT_ID) { + const context = await launchDouyinContext({ accountId, headless: true }); + const page = context.pages()[0] || await context.newPage(); + try { + await page.goto(DEFAULT_CREATOR_URL, { waitUntil: 'domcontentloaded', timeout: 60000 }); + await page.waitForTimeout(2500); + return await detectLogin(page, accountId); + } finally { + await context.close(); + } +} + +async function detectLogin(page, accountId = DEFAULT_ACCOUNT_ID) { + const url = page.url(); + const title = await page.title().catch(() => ''); + const bodyText = await page.locator('body').innerText({ timeout: 5000 }).catch(() => ''); + const loginVisible = LOGIN_TEXT.test(bodyText); + const creatorVisible = CREATOR_TEXT.test(bodyText); + const loggedIn = creatorVisible || (!loginVisible && /creator\.douyin\.com/.test(url)); + return { + loggedIn, + status: loggedIn ? 'SUCCESS' : 'FAILED', + message: loggedIn ? 'Douyin creator profile is logged in.' : 'Douyin creator profile is not logged in.', + url, + title, + profileDir: profileDir(accountId) + }; +} + +async function collectMetrics(task) { + const payload = task.payload || {}; + const accountId = task.accountId || DEFAULT_ACCOUNT_ID; + const targetUrl = payload.metricsUrl || payload.contentUrl || DEFAULT_CONTENT_URL; + const headless = payload.headless === false ? false : true; + const context = await launchDouyinContext({ accountId, headless }); + const page = context.pages()[0] || await context.newPage(); + try { + await page.goto(targetUrl, { waitUntil: 'domcontentloaded', timeout: 60000 }); + await page.waitForTimeout(Number(payload.waitMs || 5000)); + const status = await detectLogin(page, accountId); + if (!status.loggedIn) { + return { + status: 'FAILED', + errorMessage: status.message, + result: { + action: 'sync_metrics', + loggedIn: false, + ...status + } + }; + } + + const text = await page.locator('body').innerText({ timeout: 15000 }).catch(() => ''); + const screenshot = await saveScreenshot(page, task.id); + const videos = await collectVideoMetrics(page, Number(payload.maxVideos || 20)); + const metrics = videos.length > 0 ? summarizeVideos(videos) : extractMetrics(text); + return { + status: 'SUCCESS', + errorMessage: null, + result: { + action: 'sync_metrics', + loggedIn: true, + url: page.url(), + title: await page.title().catch(() => ''), + metrics, + videos, + videoCount: videos.length, + extractionMode: videos.length > 0 ? 'video_list' : 'page_summary', + screenshot, + pageTextPreview: compactText(text).slice(0, 2000) + } + }; + } finally { + await context.close().catch(() => {}); + } +} + +async function saveScreenshot(page, taskId) { + const dir = path.resolve('target/media/douyin'); + ensureDir(dir); + const file = path.join(dir, `${taskId || Date.now()}-metrics.png`); + await page.screenshot({ path: file, fullPage: true }).catch(() => {}); + return file; +} + +function compactText(text) { + return (text || '').replace(/\s+/g, ' ').trim(); +} + +function extractMetrics(text) { + const compact = compactText(text); + return { + views: findMetric(compact, ['\\u64ad\\u653e', '\\u6d4f\\u89c8', '\\u89c2\\u770b', '\\u5c55\\u73b0']), + likes: findMetric(compact, ['\\u70b9\\u8d5e']), + comments: findMetric(compact, ['\\u8bc4\\u8bba']), + collects: findMetric(compact, ['\\u6536\\u85cf']), + shares: findMetric(compact, ['\\u5206\\u4eab', '\\u8f6c\\u53d1']) + }; +} + +async function collectVideoMetrics(page, limit = 20) { + await autoScroll(page); + const blocks = await page.evaluate(maxBlocks => { + const selectors = [ + 'tr', + 'article', + 'li', + '[class*="card"]', + '[class*="item"]', + '[class*="work"]', + '[class*="video"]', + '[class*="content"]' + ]; + const seen = new Set(); + const rows = []; + for (const selector of selectors) { + for (const element of document.querySelectorAll(selector)) { + if (rows.length >= maxBlocks) { + break; + } + const rect = element.getBoundingClientRect(); + if (rect.width < 160 || rect.height < 40) { + continue; + } + const text = (element.innerText || '').replace(/\s+/g, ' ').trim(); + if (text.length < 12 || text.length > 1200) { + continue; + } + const signature = text.slice(0, 120); + if (seen.has(signature)) { + continue; + } + seen.add(signature); + const link = element.querySelector('a[href]'); + rows.push({ + text, + href: link ? link.href : '', + rect: { + x: Math.round(rect.x), + y: Math.round(rect.y), + width: Math.round(rect.width), + height: Math.round(rect.height) + } + }); + } + } + return rows; + }, Math.max(limit * 8, 80)); + + const videos = []; + const seen = new Set(); + for (const block of blocks) { + const video = parseVideoBlock(block); + if (!video) { + continue; + } + const key = `${video.title}|${video.views}|${video.likes}|${video.comments}|${video.href}`; + if (seen.has(key)) { + continue; + } + seen.add(key); + videos.push(video); + if (videos.length >= limit) { + break; + } + } + return videos; +} + +async function autoScroll(page) { + await page.evaluate(async () => { + await new Promise(resolve => { + let count = 0; + const timer = setInterval(() => { + window.scrollBy(0, Math.floor(window.innerHeight * 0.8)); + count += 1; + if (count >= 3) { + clearInterval(timer); + resolve(); + } + }, 500); + }); + }).catch(() => {}); + await page.waitForTimeout(800).catch(() => {}); +} + +function parseVideoBlock(block) { + const text = compactText(block.text); + const metrics = extractMetrics(text); + const score = metrics.views + metrics.likes + metrics.comments + metrics.collects + metrics.shares; + if (score <= 0) { + return null; + } + const title = extractVideoTitle(text); + return { + title, + href: block.href || '', + views: metrics.views, + likes: metrics.likes, + comments: metrics.comments, + collects: metrics.collects, + shares: metrics.shares, + interactionCount: metrics.likes + metrics.comments + metrics.collects + metrics.shares, + interactionRate: metrics.views <= 0 + ? 0 + : (metrics.likes + metrics.comments + metrics.collects + metrics.shares) / metrics.views, + rawTextPreview: text.slice(0, 500) + }; +} + +function extractVideoTitle(text) { + const cleaned = text + .replace(/\d{4}[-/.]\d{1,2}[-/.]\d{1,2}.*/g, '') + .replace(/(\u64ad\u653e|\u6d4f\u89c8|\u89c2\u770b|\u5c55\u73b0|\u70b9\u8d5e|\u8bc4\u8bba|\u6536\u85cf|\u5206\u4eab|\u8f6c\u53d1).*/g, '') + .trim(); + if (cleaned.length >= 2) { + return cleaned.slice(0, 120); + } + return text.slice(0, 80); +} + +function summarizeVideos(videos) { + const summary = { + views: 0, + likes: 0, + comments: 0, + collects: 0, + shares: 0 + }; + for (const video of videos) { + summary.views += Number(video.views || 0); + summary.likes += Number(video.likes || 0); + summary.comments += Number(video.comments || 0); + summary.collects += Number(video.collects || 0); + summary.shares += Number(video.shares || 0); + } + return summary; +} + +function findMetric(text, labels) { + for (const label of labels) { + const labelPattern = new RegExp(label); + const decodedLabel = label.replace(/\\u([0-9a-fA-F]{4})/g, (_, code) => String.fromCharCode(parseInt(code, 16))); + const after = new RegExp(`${label}[^0-9\\d]{0,16}([0-9][0-9,.]*)(\\s*[\\u4e07\\u5343wWkK]?)`); + const before = new RegExp(`([0-9][0-9,.]*)(\\s*[\\u4e07\\u5343wWkK]?)[^0-9\\d]{0,16}${label}`); + if (labelPattern.test(text)) { + const afterMatch = text.match(after); + if (afterMatch) { + return parseMetricNumber(afterMatch[1], afterMatch[2]); + } + const beforeMatch = text.match(before); + if (beforeMatch) { + return parseMetricNumber(beforeMatch[1], beforeMatch[2]); + } + const looseIndex = text.indexOf(decodedLabel); + if (looseIndex >= 0) { + const around = text.slice(Math.max(0, looseIndex - 30), looseIndex + 60); + const number = around.match(/([0-9][0-9,.]*)(\s*[\u4e07\u5343wWkK]?)/); + if (number) { + return parseMetricNumber(number[1], number[2]); + } + } + } + } + return 0; +} + +function parseMetricNumber(raw, unit = '') { + const value = Number(String(raw).replace(/,/g, '')); + if (!Number.isFinite(value)) { + return 0; + } + const normalizedUnit = unit.trim().toLowerCase(); + if (normalizedUnit.includes('\u4e07') || normalizedUnit.includes('w')) { + return Math.round(value * 10000); + } + if (normalizedUnit.includes('\u5343') || normalizedUnit.includes('k')) { + return Math.round(value * 1000); + } + return Math.round(value); +} + +async function pollTasks() { + const response = await fetch(`${DEFAULT_BASE_URL}/v1/media/workers/poll`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Worker-Token': DEFAULT_WORKER_TOKEN + }, + body: JSON.stringify({ + workerId: DEFAULT_WORKER_ID, + platforms: ['douyin'], + maxTasks: 5 + }) + }); + if (!response.ok) { + throw new Error(`Poll failed: ${response.status} ${await response.text()}`); + } + return response.json(); +} + +async function submitResult(taskId, body) { + const response = await fetch(`${DEFAULT_BASE_URL}/v1/media/workers/tasks/${taskId}/result`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Worker-Token': DEFAULT_WORKER_TOKEN + }, + body: JSON.stringify(body) + }); + if (!response.ok) { + throw new Error(`Submit result failed: ${response.status} ${await response.text()}`); + } + return response.json(); +} + +async function handleTask(task) { + const payload = task.payload || {}; + const action = payload.action || (task.taskType === 'SYNC_METRICS' ? 'sync_metrics' : 'check_login'); + if (action === 'check_login') { + const status = await checkLogin(task.accountId || DEFAULT_ACCOUNT_ID); + return submitResult(task.id, { + status: status.loggedIn ? 'SUCCESS' : 'FAILED', + errorMessage: status.loggedIn ? null : status.message, + result: { + action, + loggedIn: status.loggedIn, + message: status.message, + url: status.url, + title: status.title, + profileDir: profileDir(task.accountId || DEFAULT_ACCOUNT_ID) + } + }); + } + + if (action === 'sync_metrics') { + const result = await collectMetrics(task); + return submitResult(task.id, result); + } + + return submitResult(task.id, { + status: 'FAILED', + errorMessage: `Unsupported local Douyin worker action: ${action}`, + result: { action } + }); +} + +async function workerOnce() { + const tasks = await pollTasks(); + if (!Array.isArray(tasks) || tasks.length === 0) { + console.log('No pending Douyin tasks.'); + return; + } + for (const task of tasks) { + const result = await handleTask(task); + console.log(`Completed ${task.id}: ${result.status}`); + } +} + +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +async function workerLoop() { + const intervalMs = Number(process.env.MEDIA_WORKER_INTERVAL_MS || 30000); + console.log(`Douyin worker loop started. baseUrl=${DEFAULT_BASE_URL}, intervalMs=${intervalMs}`); + while (true) { + try { + await workerOnce(); + } catch (error) { + console.error(error.stack || error.message); + } + await sleep(intervalMs); + } +} + +async function main() { + const mode = process.argv[2]; + if (mode === 'login') { + await login(); + return; + } + if (mode === 'check-login') { + const status = await checkLogin(process.argv[3] || DEFAULT_ACCOUNT_ID); + console.log(JSON.stringify(status, null, 2)); + process.exit(status.loggedIn ? 0 : 2); + } + if (mode === 'metrics') { + const task = { + id: `manual_${Date.now()}`, + taskType: 'SYNC_METRICS', + platform: 'douyin', + accountId: process.argv[3] || DEFAULT_ACCOUNT_ID, + payload: { action: 'sync_metrics', headless: false } + }; + console.log(JSON.stringify((await collectMetrics(task)).result, null, 2)); + return; + } + if (mode === 'worker') { + await workerOnce(); + return; + } + if (mode === 'worker-loop') { + await workerLoop(); + return; + } + throw new Error('Usage: node scripts/local_douyin_worker.js [accountId]'); +} + +main().catch(error => { + console.error(error.stack || error.message); + process.exit(1); +}); diff --git a/scripts/local_media_worker.ps1 b/scripts/local_media_worker.ps1 new file mode 100644 index 0000000..35df048 --- /dev/null +++ b/scripts/local_media_worker.ps1 @@ -0,0 +1,71 @@ +param( + [string]$BaseUrl = "http://localhost:8880", + [string]$WorkerId = "local-worker-01", + [string]$WorkerToken = "local-worker-dev-token" +) + +$headers = @{ + "X-Worker-Token" = $WorkerToken +} + +$pollBody = @{ + workerId = $WorkerId + platforms = @("douyin", "xhs", "wxvideo", "kuaishou", "bilibili", "weibo") + maxTasks = 5 +} | ConvertTo-Json + +$tasks = Invoke-RestMethod ` + -Uri "$BaseUrl/v1/media/workers/poll" ` + -Method Post ` + -Headers $headers ` + -ContentType "application/json" ` + -Body $pollBody + +foreach ($task in $tasks) { + if ($task.taskType -eq "SYNC_COMMENTS") { + $result = @{ + status = "SUCCESS" + result = @{ + comments = @( + @{ + platform = $task.platform + accountId = $task.accountId + userName = "测试用户" + content = "这个设备多少钱?" + likedCount = 12 + intent = "sales_lead" + } + ) + } + } + } elseif ($task.taskType -eq "SYNC_METRICS") { + $result = @{ + status = "SUCCESS" + result = @{ + metrics = @{ + views = 12000 + likes = 860 + comments = 56 + collects = 128 + shares = 43 + } + } + } + } else { + $result = @{ + status = "SUCCESS" + result = @{ + message = "local worker mock completed" + } + } + } + + Invoke-RestMethod ` + -Uri "$BaseUrl/v1/media/workers/tasks/$($task.id)/result" ` + -Method Post ` + -Headers $headers ` + -ContentType "application/json" ` + -Body ($result | ConvertTo-Json -Depth 10) | Out-Null + + Write-Host "completed $($task.id) $($task.taskType) $($task.platform)" +} diff --git a/scripts/start_local_client.ps1 b/scripts/start_local_client.ps1 new file mode 100644 index 0000000..d20221f --- /dev/null +++ b/scripts/start_local_client.ps1 @@ -0,0 +1,139 @@ +param( + [string]$BaseUrl = "http://localhost:8880", + [string]$AccountId = "douyin_company", + [int]$WorkerIntervalSeconds = 30, + [switch]$RemoteBackend +) + +$ErrorActionPreference = "Stop" +$Root = Resolve-Path (Join-Path $PSScriptRoot "..") +Set-Location $Root + +function Write-Step([string]$Message) { + Write-Host "" + Write-Host "==> $Message" -ForegroundColor Cyan +} + +function Require-Command([string]$Name, [string]$InstallHint) { + if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) { + throw "$Name is not installed or not in PATH. $InstallHint" + } +} + +function Test-BackendHealth([string]$Url) { + try { + $response = Invoke-WebRequest -UseBasicParsing -Uri "$Url/actuator/health" -TimeoutSec 3 + return $response.StatusCode -ge 200 -and $response.StatusCode -lt 300 + } catch { + return $false + } +} + +function Find-BackendJar { + $jars = Get-ChildItem -Path (Join-Path $Root "target") -Filter "agent-foundation-*.jar" -ErrorAction SilentlyContinue | + Where-Object { $_.Name -notlike "*.original" } | + Sort-Object LastWriteTime -Descending + if ($jars.Count -eq 0) { + return $null + } + return $jars[0].FullName +} + +function Test-BrowserInstalled { + $paths = @( + "$env:ProgramFiles\Microsoft\Edge\Application\msedge.exe", + "${env:ProgramFiles(x86)}\Microsoft\Edge\Application\msedge.exe", + "$env:ProgramFiles\Google\Chrome\Application\chrome.exe", + "${env:ProgramFiles(x86)}\Google\Chrome\Application\chrome.exe" + ) + foreach ($path in $paths) { + if ($path -and (Test-Path $path)) { + return $true + } + } + return $false +} + +function Test-LocalBaseUrl([string]$Url) { + return $Url -match "^https?://(localhost|127\.0\.0\.1)(:\d+)?/?$" +} + +Write-Step "Checking local runtime" +Require-Command "node" "Install Node.js LTS from https://nodejs.org/" +Require-Command "npm.cmd" "Install Node.js LTS from https://nodejs.org/" +if (-not $RemoteBackend) { + Require-Command "java" "Install JDK 17+ or JRE 17+." +} +if (-not (Test-BrowserInstalled)) { + throw "Microsoft Edge or Google Chrome is required." +} + +Write-Step "Installing Node dependencies if needed" +if (-not (Test-Path (Join-Path $Root "node_modules\playwright"))) { + & npm.cmd install + if ($LASTEXITCODE -ne 0) { + throw "npm install failed." + } +} else { + Write-Host "Node dependencies already installed." +} + +if ($RemoteBackend -or -not (Test-LocalBaseUrl $BaseUrl)) { + Write-Step "Checking remote backend" + if (Test-BackendHealth $BaseUrl) { + Write-Host "Remote backend is reachable: $BaseUrl" + } else { + Write-Host "Remote backend health check did not return OK. Continuing because /actuator/health may be disabled." + Write-Host "Worker will connect to: $BaseUrl" + } +} else { + Write-Step "Starting backend service if needed" + if (Test-BackendHealth $BaseUrl) { + Write-Host "Backend is already running: $BaseUrl" + } else { + $jar = Find-BackendJar + if (-not $jar) { + throw "Backend jar not found under target\. Build the Spring Boot jar first." + } + $backendCommand = "Set-Location '$Root'; java -jar '$jar'" + Start-Process powershell.exe -ArgumentList @("-NoExit", "-ExecutionPolicy", "Bypass", "-Command", $backendCommand) -WindowStyle Normal + Write-Host "Waiting for backend health..." + $ready = $false + for ($i = 0; $i -lt 40; $i++) { + Start-Sleep -Seconds 2 + if (Test-BackendHealth $BaseUrl) { + $ready = $true + break + } + } + if (-not $ready) { + throw "Backend did not become healthy at $BaseUrl. Check the backend window." + } + } +} + +Write-Step "Checking Douyin login" +$env:MEDIA_BASE_URL = $BaseUrl +$env:DOUYIN_ACCOUNT_ID = $AccountId +$checkOutput = & npm.cmd run douyin:check -- $AccountId 2>&1 +if ($LASTEXITCODE -ne 0) { + Write-Host $checkOutput + Write-Host "" + Write-Host "Douyin login is required. A browser will open. Login, then press Enter in that window." + & npm.cmd run douyin:login -- $AccountId + if ($LASTEXITCODE -ne 0) { + throw "Douyin login failed." + } +} else { + Write-Host "Douyin login profile is ready." +} + +Write-Step "Starting Douyin worker loop" +$workerCommand = "Set-Location '$Root'; `$env:MEDIA_BASE_URL='$BaseUrl'; `$env:DOUYIN_ACCOUNT_ID='$AccountId'; `$env:MEDIA_WORKER_INTERVAL_MS='$($WorkerIntervalSeconds * 1000)'; npm.cmd run douyin:worker:loop" +Start-Process powershell.exe -ArgumentList @("-NoExit", "-ExecutionPolicy", "Bypass", "-Command", $workerCommand) -WindowStyle Normal + +Write-Host "" +Write-Host "Local client is ready." +Write-Host "Backend: $BaseUrl" +Write-Host "Account: $AccountId" +Write-Host "Worker polling interval: $WorkerIntervalSeconds seconds" diff --git a/src/main/java/com/company/agentfoundation/agent/capability/AbstractPlaceholderCapabilityHandler.java b/src/main/java/com/company/agentfoundation/agent/capability/AbstractPlaceholderCapabilityHandler.java index a94685f..ce97444 100644 --- a/src/main/java/com/company/agentfoundation/agent/capability/AbstractPlaceholderCapabilityHandler.java +++ b/src/main/java/com/company/agentfoundation/agent/capability/AbstractPlaceholderCapabilityHandler.java @@ -1,12 +1,12 @@ package com.company.agentfoundation.agent.capability; -import com.company.agentfoundation.agent.AgentCapabilityResponse; -import com.company.agentfoundation.agent.AgentIntent; -import com.company.agentfoundation.agent.AgentRequestContext; +import com.company.agentfoundation.agent.report.dto.AgentCapabilityResponse; +import com.company.agentfoundation.agent.report.model.AgentIntent; +import com.company.agentfoundation.agent.report.model.AgentRequestContext; import java.util.List; -abstract class AbstractPlaceholderCapabilityHandler implements AgentCapabilityHandler { +public abstract class AbstractPlaceholderCapabilityHandler implements AgentCapabilityHandler { private final AgentIntent intent; private final String message; private final List dataSources; diff --git a/src/main/java/com/company/agentfoundation/agent/capability/AgentCapabilityHandler.java b/src/main/java/com/company/agentfoundation/agent/capability/AgentCapabilityHandler.java index 609c0bc..9a39b71 100644 --- a/src/main/java/com/company/agentfoundation/agent/capability/AgentCapabilityHandler.java +++ b/src/main/java/com/company/agentfoundation/agent/capability/AgentCapabilityHandler.java @@ -1,8 +1,8 @@ package com.company.agentfoundation.agent.capability; -import com.company.agentfoundation.agent.AgentCapabilityResponse; -import com.company.agentfoundation.agent.AgentIntent; -import com.company.agentfoundation.agent.AgentRequestContext; +import com.company.agentfoundation.agent.report.dto.AgentCapabilityResponse; +import com.company.agentfoundation.agent.report.model.AgentIntent; +import com.company.agentfoundation.agent.report.model.AgentRequestContext; public interface AgentCapabilityHandler { AgentIntent intent(); diff --git a/src/main/java/com/company/agentfoundation/agent/capability/CapabilityRegistry.java b/src/main/java/com/company/agentfoundation/agent/capability/CapabilityRegistry.java index 11e8f34..d902ce9 100644 --- a/src/main/java/com/company/agentfoundation/agent/capability/CapabilityRegistry.java +++ b/src/main/java/com/company/agentfoundation/agent/capability/CapabilityRegistry.java @@ -1,8 +1,8 @@ package com.company.agentfoundation.agent.capability; -import com.company.agentfoundation.agent.AgentCapabilityResponse; -import com.company.agentfoundation.agent.AgentIntent; -import com.company.agentfoundation.agent.AgentRequestContext; +import com.company.agentfoundation.agent.report.dto.AgentCapabilityResponse; +import com.company.agentfoundation.agent.report.model.AgentIntent; +import com.company.agentfoundation.agent.report.model.AgentRequestContext; import org.springframework.stereotype.Service; import java.util.EnumMap; diff --git a/src/main/java/com/company/agentfoundation/agent/capability/ContentGenerationCapabilityHandler.java b/src/main/java/com/company/agentfoundation/agent/capability/content/ContentGenerationCapabilityHandler.java similarity index 74% rename from src/main/java/com/company/agentfoundation/agent/capability/ContentGenerationCapabilityHandler.java rename to src/main/java/com/company/agentfoundation/agent/capability/content/ContentGenerationCapabilityHandler.java index 9c2f72f..9e61827 100644 --- a/src/main/java/com/company/agentfoundation/agent/capability/ContentGenerationCapabilityHandler.java +++ b/src/main/java/com/company/agentfoundation/agent/capability/content/ContentGenerationCapabilityHandler.java @@ -1,6 +1,8 @@ -package com.company.agentfoundation.agent.capability; +package com.company.agentfoundation.agent.capability.content; -import com.company.agentfoundation.agent.AgentIntent; +import com.company.agentfoundation.agent.capability.AbstractPlaceholderCapabilityHandler; + +import com.company.agentfoundation.agent.report.model.AgentIntent; import org.springframework.stereotype.Component; import java.util.List; diff --git a/src/main/java/com/company/agentfoundation/agent/capability/CustomerDashboardDailyCapabilityHandler.java b/src/main/java/com/company/agentfoundation/agent/capability/customer/CustomerDashboardDailyCapabilityHandler.java similarity index 85% rename from src/main/java/com/company/agentfoundation/agent/capability/CustomerDashboardDailyCapabilityHandler.java rename to src/main/java/com/company/agentfoundation/agent/capability/customer/CustomerDashboardDailyCapabilityHandler.java index 38c23b3..db3c2a7 100644 --- a/src/main/java/com/company/agentfoundation/agent/capability/CustomerDashboardDailyCapabilityHandler.java +++ b/src/main/java/com/company/agentfoundation/agent/capability/customer/CustomerDashboardDailyCapabilityHandler.java @@ -1,10 +1,12 @@ -package com.company.agentfoundation.agent.capability; +package com.company.agentfoundation.agent.capability.customer; -import com.company.agentfoundation.agent.AgentCapabilityResponse; -import com.company.agentfoundation.agent.AgentIntent; -import com.company.agentfoundation.agent.AgentRequestContext; -import com.company.agentfoundation.integration.wecom.WecomStudentContactStats; -import com.company.agentfoundation.integration.wecom.WecomStudentContactStatsService; +import com.company.agentfoundation.agent.capability.AgentCapabilityHandler; + +import com.company.agentfoundation.agent.report.dto.AgentCapabilityResponse; +import com.company.agentfoundation.agent.report.model.AgentIntent; +import com.company.agentfoundation.agent.report.model.AgentRequestContext; +import com.company.agentfoundation.integration.wecom.dto.WecomStudentContactStats; +import com.company.agentfoundation.integration.wecom.service.WecomStudentContactStatsService; import org.springframework.stereotype.Component; import java.time.Instant; diff --git a/src/main/java/com/company/agentfoundation/agent/capability/EcCallComplianceDailyCapabilityHandler.java b/src/main/java/com/company/agentfoundation/agent/capability/ec/EcCallComplianceDailyCapabilityHandler.java similarity index 75% rename from src/main/java/com/company/agentfoundation/agent/capability/EcCallComplianceDailyCapabilityHandler.java rename to src/main/java/com/company/agentfoundation/agent/capability/ec/EcCallComplianceDailyCapabilityHandler.java index d4a554a..eae68e3 100644 --- a/src/main/java/com/company/agentfoundation/agent/capability/EcCallComplianceDailyCapabilityHandler.java +++ b/src/main/java/com/company/agentfoundation/agent/capability/ec/EcCallComplianceDailyCapabilityHandler.java @@ -1,6 +1,8 @@ -package com.company.agentfoundation.agent.capability; +package com.company.agentfoundation.agent.capability.ec; -import com.company.agentfoundation.agent.AgentIntent; +import com.company.agentfoundation.agent.capability.AbstractPlaceholderCapabilityHandler; + +import com.company.agentfoundation.agent.report.model.AgentIntent; import org.springframework.stereotype.Component; import java.util.List; diff --git a/src/main/java/com/company/agentfoundation/agent/capability/DataSyncStatusCapabilityHandler.java b/src/main/java/com/company/agentfoundation/agent/capability/integration/DataSyncStatusCapabilityHandler.java similarity index 75% rename from src/main/java/com/company/agentfoundation/agent/capability/DataSyncStatusCapabilityHandler.java rename to src/main/java/com/company/agentfoundation/agent/capability/integration/DataSyncStatusCapabilityHandler.java index e53c906..35fbc6d 100644 --- a/src/main/java/com/company/agentfoundation/agent/capability/DataSyncStatusCapabilityHandler.java +++ b/src/main/java/com/company/agentfoundation/agent/capability/integration/DataSyncStatusCapabilityHandler.java @@ -1,6 +1,8 @@ -package com.company.agentfoundation.agent.capability; +package com.company.agentfoundation.agent.capability.integration; -import com.company.agentfoundation.agent.AgentIntent; +import com.company.agentfoundation.agent.capability.AbstractPlaceholderCapabilityHandler; + +import com.company.agentfoundation.agent.report.model.AgentIntent; import org.springframework.stereotype.Component; import java.util.List; diff --git a/src/main/java/com/company/agentfoundation/agent/capability/PeriodSummaryReportCapabilityHandler.java b/src/main/java/com/company/agentfoundation/agent/capability/management/PeriodSummaryReportCapabilityHandler.java similarity index 75% rename from src/main/java/com/company/agentfoundation/agent/capability/PeriodSummaryReportCapabilityHandler.java rename to src/main/java/com/company/agentfoundation/agent/capability/management/PeriodSummaryReportCapabilityHandler.java index c2d4052..1a3dac9 100644 --- a/src/main/java/com/company/agentfoundation/agent/capability/PeriodSummaryReportCapabilityHandler.java +++ b/src/main/java/com/company/agentfoundation/agent/capability/management/PeriodSummaryReportCapabilityHandler.java @@ -1,6 +1,8 @@ -package com.company.agentfoundation.agent.capability; +package com.company.agentfoundation.agent.capability.management; -import com.company.agentfoundation.agent.AgentIntent; +import com.company.agentfoundation.agent.capability.AbstractPlaceholderCapabilityHandler; + +import com.company.agentfoundation.agent.report.model.AgentIntent; import org.springframework.stereotype.Component; import java.util.List; diff --git a/src/main/java/com/company/agentfoundation/agent/capability/StudentAttendanceDailyCapabilityHandler.java b/src/main/java/com/company/agentfoundation/agent/capability/student/StudentAttendanceDailyCapabilityHandler.java similarity index 73% rename from src/main/java/com/company/agentfoundation/agent/capability/StudentAttendanceDailyCapabilityHandler.java rename to src/main/java/com/company/agentfoundation/agent/capability/student/StudentAttendanceDailyCapabilityHandler.java index f105206..cf8f3c6 100644 --- a/src/main/java/com/company/agentfoundation/agent/capability/StudentAttendanceDailyCapabilityHandler.java +++ b/src/main/java/com/company/agentfoundation/agent/capability/student/StudentAttendanceDailyCapabilityHandler.java @@ -1,6 +1,8 @@ -package com.company.agentfoundation.agent.capability; +package com.company.agentfoundation.agent.capability.student; -import com.company.agentfoundation.agent.AgentIntent; +import com.company.agentfoundation.agent.capability.AbstractPlaceholderCapabilityHandler; + +import com.company.agentfoundation.agent.report.model.AgentIntent; import org.springframework.stereotype.Component; import java.util.List; diff --git a/src/main/java/com/company/agentfoundation/agent/capability/WecomOperationDailyCapabilityHandler.java b/src/main/java/com/company/agentfoundation/agent/capability/wecom/WecomOperationDailyCapabilityHandler.java similarity index 73% rename from src/main/java/com/company/agentfoundation/agent/capability/WecomOperationDailyCapabilityHandler.java rename to src/main/java/com/company/agentfoundation/agent/capability/wecom/WecomOperationDailyCapabilityHandler.java index 8e365f2..26b707f 100644 --- a/src/main/java/com/company/agentfoundation/agent/capability/WecomOperationDailyCapabilityHandler.java +++ b/src/main/java/com/company/agentfoundation/agent/capability/wecom/WecomOperationDailyCapabilityHandler.java @@ -1,6 +1,8 @@ -package com.company.agentfoundation.agent.capability; +package com.company.agentfoundation.agent.capability.wecom; -import com.company.agentfoundation.agent.AgentIntent; +import com.company.agentfoundation.agent.capability.AbstractPlaceholderCapabilityHandler; + +import com.company.agentfoundation.agent.report.model.AgentIntent; import org.springframework.stereotype.Component; import java.util.List; diff --git a/src/main/java/com/company/agentfoundation/agent/OperationReportController.java b/src/main/java/com/company/agentfoundation/agent/report/controller/OperationReportController.java similarity index 94% rename from src/main/java/com/company/agentfoundation/agent/OperationReportController.java rename to src/main/java/com/company/agentfoundation/agent/report/controller/OperationReportController.java index 7645560..b889d4b 100644 --- a/src/main/java/com/company/agentfoundation/agent/OperationReportController.java +++ b/src/main/java/com/company/agentfoundation/agent/report/controller/OperationReportController.java @@ -1,4 +1,7 @@ -package com.company.agentfoundation.agent; +package com.company.agentfoundation.agent.report.controller; + +import com.company.agentfoundation.agent.report.dto.AgentCapabilityResponse; +import com.company.agentfoundation.agent.report.service.OperationReportService; import com.company.agentfoundation.agent.capability.AgentCapabilityDescriptor; import com.company.agentfoundation.agent.capability.CapabilityRegistry; diff --git a/src/main/java/com/company/agentfoundation/agent/AgentCapabilityResponse.java b/src/main/java/com/company/agentfoundation/agent/report/dto/AgentCapabilityResponse.java similarity index 83% rename from src/main/java/com/company/agentfoundation/agent/AgentCapabilityResponse.java rename to src/main/java/com/company/agentfoundation/agent/report/dto/AgentCapabilityResponse.java index 1d10beb..f82bf18 100644 --- a/src/main/java/com/company/agentfoundation/agent/AgentCapabilityResponse.java +++ b/src/main/java/com/company/agentfoundation/agent/report/dto/AgentCapabilityResponse.java @@ -1,4 +1,6 @@ -package com.company.agentfoundation.agent; +package com.company.agentfoundation.agent.report.dto; + +import com.company.agentfoundation.agent.report.model.AgentIntent; import java.time.Instant; import java.util.List; diff --git a/src/main/java/com/company/agentfoundation/agent/AgentIntent.java b/src/main/java/com/company/agentfoundation/agent/report/model/AgentIntent.java similarity index 96% rename from src/main/java/com/company/agentfoundation/agent/AgentIntent.java rename to src/main/java/com/company/agentfoundation/agent/report/model/AgentIntent.java index c0ccf12..3a855ec 100644 --- a/src/main/java/com/company/agentfoundation/agent/AgentIntent.java +++ b/src/main/java/com/company/agentfoundation/agent/report/model/AgentIntent.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.agent; +package com.company.agentfoundation.agent.report.model; public enum AgentIntent { DATA_SYNC_STATUS("data_sync_status", "integration", "\u4e09\u7cfb\u7edf\u6570\u636e\u540c\u6b65\u72b6\u6001"), diff --git a/src/main/java/com/company/agentfoundation/agent/AgentRequestContext.java b/src/main/java/com/company/agentfoundation/agent/report/model/AgentRequestContext.java similarity index 84% rename from src/main/java/com/company/agentfoundation/agent/AgentRequestContext.java rename to src/main/java/com/company/agentfoundation/agent/report/model/AgentRequestContext.java index e2ca35f..54bb5ed 100644 --- a/src/main/java/com/company/agentfoundation/agent/AgentRequestContext.java +++ b/src/main/java/com/company/agentfoundation/agent/report/model/AgentRequestContext.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.agent; +package com.company.agentfoundation.agent.report.model; public record AgentRequestContext( String agentId, diff --git a/src/main/java/com/company/agentfoundation/agent/OperationReportService.java b/src/main/java/com/company/agentfoundation/agent/report/service/OperationReportService.java similarity index 85% rename from src/main/java/com/company/agentfoundation/agent/OperationReportService.java rename to src/main/java/com/company/agentfoundation/agent/report/service/OperationReportService.java index 8d2e4b8..f096b8c 100644 --- a/src/main/java/com/company/agentfoundation/agent/OperationReportService.java +++ b/src/main/java/com/company/agentfoundation/agent/report/service/OperationReportService.java @@ -1,4 +1,8 @@ -package com.company.agentfoundation.agent; +package com.company.agentfoundation.agent.report.service; + +import com.company.agentfoundation.agent.report.dto.AgentCapabilityResponse; +import com.company.agentfoundation.agent.report.model.AgentIntent; +import com.company.agentfoundation.agent.report.model.AgentRequestContext; import com.company.agentfoundation.agent.capability.CapabilityRegistry; import org.springframework.stereotype.Service; diff --git a/src/main/java/com/company/agentfoundation/agent/AgentRuntimeController.java b/src/main/java/com/company/agentfoundation/agent/runtime/controller/AgentRuntimeController.java similarity index 82% rename from src/main/java/com/company/agentfoundation/agent/AgentRuntimeController.java rename to src/main/java/com/company/agentfoundation/agent/runtime/controller/AgentRuntimeController.java index 90dc13f..c60a411 100644 --- a/src/main/java/com/company/agentfoundation/agent/AgentRuntimeController.java +++ b/src/main/java/com/company/agentfoundation/agent/runtime/controller/AgentRuntimeController.java @@ -1,4 +1,8 @@ -package com.company.agentfoundation.agent; +package com.company.agentfoundation.agent.runtime.controller; + +import com.company.agentfoundation.agent.runtime.dto.AgentMessageRequest; +import com.company.agentfoundation.agent.runtime.dto.AgentMessageResponse; +import com.company.agentfoundation.agent.runtime.service.AgentOrchestratorService; import com.company.agentfoundation.security.AgentAuthService; import jakarta.validation.Valid; diff --git a/src/main/java/com/company/agentfoundation/agent/AgentMessageRequest.java b/src/main/java/com/company/agentfoundation/agent/runtime/dto/AgentMessageRequest.java similarity index 72% rename from src/main/java/com/company/agentfoundation/agent/AgentMessageRequest.java rename to src/main/java/com/company/agentfoundation/agent/runtime/dto/AgentMessageRequest.java index b2cca04..63343a8 100644 --- a/src/main/java/com/company/agentfoundation/agent/AgentMessageRequest.java +++ b/src/main/java/com/company/agentfoundation/agent/runtime/dto/AgentMessageRequest.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.agent; +package com.company.agentfoundation.agent.runtime.dto; import jakarta.validation.constraints.NotBlank; diff --git a/src/main/java/com/company/agentfoundation/agent/AgentMessageResponse.java b/src/main/java/com/company/agentfoundation/agent/runtime/dto/AgentMessageResponse.java similarity index 85% rename from src/main/java/com/company/agentfoundation/agent/AgentMessageResponse.java rename to src/main/java/com/company/agentfoundation/agent/runtime/dto/AgentMessageResponse.java index d4328c5..761b4b3 100644 --- a/src/main/java/com/company/agentfoundation/agent/AgentMessageResponse.java +++ b/src/main/java/com/company/agentfoundation/agent/runtime/dto/AgentMessageResponse.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.agent; +package com.company.agentfoundation.agent.runtime.dto; import java.time.Instant; diff --git a/src/main/java/com/company/agentfoundation/agent/AgentOrchestratorService.java b/src/main/java/com/company/agentfoundation/agent/runtime/service/AgentOrchestratorService.java similarity index 87% rename from src/main/java/com/company/agentfoundation/agent/AgentOrchestratorService.java rename to src/main/java/com/company/agentfoundation/agent/runtime/service/AgentOrchestratorService.java index 13f39f5..3d9805a 100644 --- a/src/main/java/com/company/agentfoundation/agent/AgentOrchestratorService.java +++ b/src/main/java/com/company/agentfoundation/agent/runtime/service/AgentOrchestratorService.java @@ -1,10 +1,15 @@ -package com.company.agentfoundation.agent; +package com.company.agentfoundation.agent.runtime.service; -import com.company.agentfoundation.domain.ChatRequest; -import com.company.agentfoundation.domain.ChatResponse; -import com.company.agentfoundation.directory.EmployeeAccountMapping; -import com.company.agentfoundation.directory.EmployeeDirectoryResponse; -import com.company.agentfoundation.directory.EmployeeDirectoryService; +import com.company.agentfoundation.agent.report.dto.AgentCapabilityResponse; +import com.company.agentfoundation.agent.report.model.AgentIntent; +import com.company.agentfoundation.agent.report.model.AgentRequestContext; +import com.company.agentfoundation.agent.report.service.OperationReportService; + +import com.company.agentfoundation.dto.chat.ChatRequest; +import com.company.agentfoundation.dto.chat.ChatResponse; +import com.company.agentfoundation.directory.dto.EmployeeAccountMapping; +import com.company.agentfoundation.directory.dto.EmployeeDirectoryResponse; +import com.company.agentfoundation.directory.service.EmployeeDirectoryService; import com.company.agentfoundation.security.AgentContext; import com.company.agentfoundation.service.ChatService; import org.slf4j.Logger; diff --git a/src/main/java/com/company/agentfoundation/agent/IntentRecognitionService.java b/src/main/java/com/company/agentfoundation/agent/runtime/service/IntentRecognitionService.java similarity index 94% rename from src/main/java/com/company/agentfoundation/agent/IntentRecognitionService.java rename to src/main/java/com/company/agentfoundation/agent/runtime/service/IntentRecognitionService.java index ec64dfb..7a1ace7 100644 --- a/src/main/java/com/company/agentfoundation/agent/IntentRecognitionService.java +++ b/src/main/java/com/company/agentfoundation/agent/runtime/service/IntentRecognitionService.java @@ -1,7 +1,9 @@ -package com.company.agentfoundation.agent; +package com.company.agentfoundation.agent.runtime.service; -import com.company.agentfoundation.domain.ChatRequest; -import com.company.agentfoundation.domain.ChatResponse; +import com.company.agentfoundation.agent.report.model.AgentIntent; + +import com.company.agentfoundation.dto.chat.ChatRequest; +import com.company.agentfoundation.dto.chat.ChatResponse; import com.company.agentfoundation.security.AgentContext; import com.company.agentfoundation.service.ChatService; import org.slf4j.Logger; diff --git a/src/main/java/com/company/agentfoundation/api/DeepseekClient.java b/src/main/java/com/company/agentfoundation/api/DeepseekClient.java index 88a8079..8848559 100644 --- a/src/main/java/com/company/agentfoundation/api/DeepseekClient.java +++ b/src/main/java/com/company/agentfoundation/api/DeepseekClient.java @@ -1,4 +1,7 @@ package com.company.agentfoundation.api; + +import com.company.agentfoundation.api.dto.DeepseekChatRequest; +import com.company.agentfoundation.api.dto.DeepseekChatResponse; import com.company.agentfoundation.config.FoundationProperties; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; diff --git a/src/main/java/com/company/agentfoundation/api/DeepseekChatRequest.java b/src/main/java/com/company/agentfoundation/api/dto/DeepseekChatRequest.java similarity index 80% rename from src/main/java/com/company/agentfoundation/api/DeepseekChatRequest.java rename to src/main/java/com/company/agentfoundation/api/dto/DeepseekChatRequest.java index 5743784..0eb3cf7 100644 --- a/src/main/java/com/company/agentfoundation/api/DeepseekChatRequest.java +++ b/src/main/java/com/company/agentfoundation/api/dto/DeepseekChatRequest.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.api; +package com.company.agentfoundation.api.dto; import java.util.List; public record DeepseekChatRequest(String model, List messages, Double temperature, Boolean stream) { public record Message(String role, String content) {} diff --git a/src/main/java/com/company/agentfoundation/api/DeepseekChatResponse.java b/src/main/java/com/company/agentfoundation/api/dto/DeepseekChatResponse.java similarity index 86% rename from src/main/java/com/company/agentfoundation/api/DeepseekChatResponse.java rename to src/main/java/com/company/agentfoundation/api/dto/DeepseekChatResponse.java index 6d1f7d6..b405c15 100644 --- a/src/main/java/com/company/agentfoundation/api/DeepseekChatResponse.java +++ b/src/main/java/com/company/agentfoundation/api/dto/DeepseekChatResponse.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.api; +package com.company.agentfoundation.api.dto; import java.util.List; public record DeepseekChatResponse(List choices, Usage usage) { public record Choice(Message message) {} diff --git a/src/main/java/com/company/agentfoundation/controller/ChatController.java b/src/main/java/com/company/agentfoundation/controller/ChatController.java index d6d44f1..dba8669 100644 --- a/src/main/java/com/company/agentfoundation/controller/ChatController.java +++ b/src/main/java/com/company/agentfoundation/controller/ChatController.java @@ -1,6 +1,6 @@ package com.company.agentfoundation.controller; -import com.company.agentfoundation.domain.ChatRequest; -import com.company.agentfoundation.domain.ChatResponse; +import com.company.agentfoundation.dto.chat.ChatRequest; +import com.company.agentfoundation.dto.chat.ChatResponse; import com.company.agentfoundation.security.AgentAuthService; import com.company.agentfoundation.security.AgentContext; import com.company.agentfoundation.service.ChatService; diff --git a/src/main/java/com/company/agentfoundation/crm/CrmDatabaseController.java b/src/main/java/com/company/agentfoundation/crm/controller/CrmDatabaseController.java similarity index 95% rename from src/main/java/com/company/agentfoundation/crm/CrmDatabaseController.java rename to src/main/java/com/company/agentfoundation/crm/controller/CrmDatabaseController.java index 97826bb..2bd6ab4 100644 --- a/src/main/java/com/company/agentfoundation/crm/CrmDatabaseController.java +++ b/src/main/java/com/company/agentfoundation/crm/controller/CrmDatabaseController.java @@ -1,4 +1,6 @@ -package com.company.agentfoundation.crm; +package com.company.agentfoundation.crm.controller; + +import com.company.agentfoundation.crm.service.CrmDatabaseService; import com.company.agentfoundation.security.AgentAuthService; import org.springframework.http.ResponseEntity; diff --git a/src/main/java/com/company/agentfoundation/crm/CrmDatabaseService.java b/src/main/java/com/company/agentfoundation/crm/service/CrmDatabaseService.java similarity index 98% rename from src/main/java/com/company/agentfoundation/crm/CrmDatabaseService.java rename to src/main/java/com/company/agentfoundation/crm/service/CrmDatabaseService.java index 9056dbd..d4d18ba 100644 --- a/src/main/java/com/company/agentfoundation/crm/CrmDatabaseService.java +++ b/src/main/java/com/company/agentfoundation/crm/service/CrmDatabaseService.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.crm; +package com.company.agentfoundation.crm.service; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/com/company/agentfoundation/directory/EmployeeAccountMapping.java b/src/main/java/com/company/agentfoundation/directory/dto/EmployeeAccountMapping.java similarity index 88% rename from src/main/java/com/company/agentfoundation/directory/EmployeeAccountMapping.java rename to src/main/java/com/company/agentfoundation/directory/dto/EmployeeAccountMapping.java index 9e02246..27f55a0 100644 --- a/src/main/java/com/company/agentfoundation/directory/EmployeeAccountMapping.java +++ b/src/main/java/com/company/agentfoundation/directory/dto/EmployeeAccountMapping.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.directory; +package com.company.agentfoundation.directory.dto; public record EmployeeAccountMapping( Long id, diff --git a/src/main/java/com/company/agentfoundation/directory/EmployeeDataPermission.java b/src/main/java/com/company/agentfoundation/directory/dto/EmployeeDataPermission.java similarity index 73% rename from src/main/java/com/company/agentfoundation/directory/EmployeeDataPermission.java rename to src/main/java/com/company/agentfoundation/directory/dto/EmployeeDataPermission.java index 17bc08e..dcfa1af 100644 --- a/src/main/java/com/company/agentfoundation/directory/EmployeeDataPermission.java +++ b/src/main/java/com/company/agentfoundation/directory/dto/EmployeeDataPermission.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.directory; +package com.company.agentfoundation.directory.dto; public record EmployeeDataPermission( String sourceSystem, diff --git a/src/main/java/com/company/agentfoundation/directory/EmployeeDirectoryResponse.java b/src/main/java/com/company/agentfoundation/directory/dto/EmployeeDirectoryResponse.java similarity index 74% rename from src/main/java/com/company/agentfoundation/directory/EmployeeDirectoryResponse.java rename to src/main/java/com/company/agentfoundation/directory/dto/EmployeeDirectoryResponse.java index 581409c..a0ea89e 100644 --- a/src/main/java/com/company/agentfoundation/directory/EmployeeDirectoryResponse.java +++ b/src/main/java/com/company/agentfoundation/directory/dto/EmployeeDirectoryResponse.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.directory; +package com.company.agentfoundation.directory.dto; import java.util.List; diff --git a/src/main/java/com/company/agentfoundation/directory/EmployeeDirectoryService.java b/src/main/java/com/company/agentfoundation/directory/service/EmployeeDirectoryService.java similarity index 90% rename from src/main/java/com/company/agentfoundation/directory/EmployeeDirectoryService.java rename to src/main/java/com/company/agentfoundation/directory/service/EmployeeDirectoryService.java index dfca71d..d9ce543 100644 --- a/src/main/java/com/company/agentfoundation/directory/EmployeeDirectoryService.java +++ b/src/main/java/com/company/agentfoundation/directory/service/EmployeeDirectoryService.java @@ -1,4 +1,8 @@ -package com.company.agentfoundation.directory; +package com.company.agentfoundation.directory.service; + +import com.company.agentfoundation.directory.dto.EmployeeAccountMapping; +import com.company.agentfoundation.directory.dto.EmployeeDataPermission; +import com.company.agentfoundation.directory.dto.EmployeeDirectoryResponse; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; diff --git a/src/main/java/com/company/agentfoundation/domain/ChatRequest.java b/src/main/java/com/company/agentfoundation/dto/chat/ChatRequest.java similarity index 86% rename from src/main/java/com/company/agentfoundation/domain/ChatRequest.java rename to src/main/java/com/company/agentfoundation/dto/chat/ChatRequest.java index a1be53f..68eb33d 100644 --- a/src/main/java/com/company/agentfoundation/domain/ChatRequest.java +++ b/src/main/java/com/company/agentfoundation/dto/chat/ChatRequest.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.domain; +package com.company.agentfoundation.dto.chat; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotEmpty; import java.util.List; diff --git a/src/main/java/com/company/agentfoundation/domain/ChatResponse.java b/src/main/java/com/company/agentfoundation/dto/chat/ChatResponse.java similarity index 84% rename from src/main/java/com/company/agentfoundation/domain/ChatResponse.java rename to src/main/java/com/company/agentfoundation/dto/chat/ChatResponse.java index 3b7db18..aabaf50 100644 --- a/src/main/java/com/company/agentfoundation/domain/ChatResponse.java +++ b/src/main/java/com/company/agentfoundation/dto/chat/ChatResponse.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.domain; +package com.company.agentfoundation.dto.chat; import java.time.Instant; public record ChatResponse(String traceId, String agentId, String model, String content, Usage usage, Instant timestamp) { public record Usage(Integer promptTokens, Integer completionTokens, Integer totalTokens) {} diff --git a/src/main/java/com/company/agentfoundation/integration/wecom/WecomClient.java b/src/main/java/com/company/agentfoundation/integration/wecom/client/WecomClient.java similarity index 91% rename from src/main/java/com/company/agentfoundation/integration/wecom/WecomClient.java rename to src/main/java/com/company/agentfoundation/integration/wecom/client/WecomClient.java index 6a0ac7f..045f7ab 100644 --- a/src/main/java/com/company/agentfoundation/integration/wecom/WecomClient.java +++ b/src/main/java/com/company/agentfoundation/integration/wecom/client/WecomClient.java @@ -1,4 +1,13 @@ -package com.company.agentfoundation.integration.wecom; +package com.company.agentfoundation.integration.wecom.client; + +import com.company.agentfoundation.integration.wecom.dto.WecomApiResponse; +import com.company.agentfoundation.integration.wecom.dto.WecomExternalContactBatchRequest; +import com.company.agentfoundation.integration.wecom.dto.WecomExternalContactBatchResponse; +import com.company.agentfoundation.integration.wecom.dto.WecomFollowUserListResponse; +import com.company.agentfoundation.integration.wecom.dto.WecomSendMessageRequest; +import com.company.agentfoundation.integration.wecom.dto.WecomTokenResponse; +import com.company.agentfoundation.integration.wecom.dto.WecomUserBehaviorDataRequest; +import com.company.agentfoundation.integration.wecom.dto.WecomUserBehaviorDataResponse; import com.company.agentfoundation.config.FoundationProperties; import org.slf4j.Logger; diff --git a/src/main/java/com/company/agentfoundation/integration/wecom/WecomCallbackController.java b/src/main/java/com/company/agentfoundation/integration/wecom/controller/WecomCallbackController.java similarity index 93% rename from src/main/java/com/company/agentfoundation/integration/wecom/WecomCallbackController.java rename to src/main/java/com/company/agentfoundation/integration/wecom/controller/WecomCallbackController.java index 1d16403..584f91e 100644 --- a/src/main/java/com/company/agentfoundation/integration/wecom/WecomCallbackController.java +++ b/src/main/java/com/company/agentfoundation/integration/wecom/controller/WecomCallbackController.java @@ -1,6 +1,11 @@ -package com.company.agentfoundation.integration.wecom; +package com.company.agentfoundation.integration.wecom.controller; -import com.company.agentfoundation.agent.AgentOrchestratorService; +import com.company.agentfoundation.integration.wecom.client.WecomClient; +import com.company.agentfoundation.integration.wecom.model.WecomEncryptedEnvelope; +import com.company.agentfoundation.integration.wecom.model.WecomIncomingMessage; +import com.company.agentfoundation.integration.wecom.service.WecomCryptoService; + +import com.company.agentfoundation.agent.runtime.service.AgentOrchestratorService; import com.company.agentfoundation.config.FoundationProperties; import com.fasterxml.jackson.dataformat.xml.XmlMapper; import org.slf4j.Logger; diff --git a/src/main/java/com/company/agentfoundation/integration/wecom/WecomApiResponse.java b/src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomApiResponse.java similarity index 71% rename from src/main/java/com/company/agentfoundation/integration/wecom/WecomApiResponse.java rename to src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomApiResponse.java index 90b89ee..21abb38 100644 --- a/src/main/java/com/company/agentfoundation/integration/wecom/WecomApiResponse.java +++ b/src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomApiResponse.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.integration.wecom; +package com.company.agentfoundation.integration.wecom.dto; public record WecomApiResponse(Integer errcode, String errmsg) { public boolean success() { diff --git a/src/main/java/com/company/agentfoundation/integration/wecom/WecomExternalContactBatchRequest.java b/src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomExternalContactBatchRequest.java similarity index 79% rename from src/main/java/com/company/agentfoundation/integration/wecom/WecomExternalContactBatchRequest.java rename to src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomExternalContactBatchRequest.java index f45a77a..4065949 100644 --- a/src/main/java/com/company/agentfoundation/integration/wecom/WecomExternalContactBatchRequest.java +++ b/src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomExternalContactBatchRequest.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.integration.wecom; +package com.company.agentfoundation.integration.wecom.dto; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/src/main/java/com/company/agentfoundation/integration/wecom/WecomExternalContactBatchResponse.java b/src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomExternalContactBatchResponse.java similarity index 92% rename from src/main/java/com/company/agentfoundation/integration/wecom/WecomExternalContactBatchResponse.java rename to src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomExternalContactBatchResponse.java index c40514a..d110731 100644 --- a/src/main/java/com/company/agentfoundation/integration/wecom/WecomExternalContactBatchResponse.java +++ b/src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomExternalContactBatchResponse.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.integration.wecom; +package com.company.agentfoundation.integration.wecom.dto; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/src/main/java/com/company/agentfoundation/integration/wecom/WecomFollowUserListResponse.java b/src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomFollowUserListResponse.java similarity index 83% rename from src/main/java/com/company/agentfoundation/integration/wecom/WecomFollowUserListResponse.java rename to src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomFollowUserListResponse.java index eecf028..875eae8 100644 --- a/src/main/java/com/company/agentfoundation/integration/wecom/WecomFollowUserListResponse.java +++ b/src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomFollowUserListResponse.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.integration.wecom; +package com.company.agentfoundation.integration.wecom.dto; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/src/main/java/com/company/agentfoundation/integration/wecom/WecomSendMessageRequest.java b/src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomSendMessageRequest.java similarity index 95% rename from src/main/java/com/company/agentfoundation/integration/wecom/WecomSendMessageRequest.java rename to src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomSendMessageRequest.java index c07ba80..a1e1bdb 100644 --- a/src/main/java/com/company/agentfoundation/integration/wecom/WecomSendMessageRequest.java +++ b/src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomSendMessageRequest.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.integration.wecom; +package com.company.agentfoundation.integration.wecom.dto; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/src/main/java/com/company/agentfoundation/integration/wecom/WecomStudentContactStats.java b/src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomStudentContactStats.java similarity index 83% rename from src/main/java/com/company/agentfoundation/integration/wecom/WecomStudentContactStats.java rename to src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomStudentContactStats.java index b265dcc..648fc74 100644 --- a/src/main/java/com/company/agentfoundation/integration/wecom/WecomStudentContactStats.java +++ b/src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomStudentContactStats.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.integration.wecom; +package com.company.agentfoundation.integration.wecom.dto; public record WecomStudentContactStats( int totalStudents, diff --git a/src/main/java/com/company/agentfoundation/integration/wecom/WecomTokenResponse.java b/src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomTokenResponse.java similarity index 84% rename from src/main/java/com/company/agentfoundation/integration/wecom/WecomTokenResponse.java rename to src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomTokenResponse.java index d8d6299..69d9775 100644 --- a/src/main/java/com/company/agentfoundation/integration/wecom/WecomTokenResponse.java +++ b/src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomTokenResponse.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.integration.wecom; +package com.company.agentfoundation.integration.wecom.dto; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/src/main/java/com/company/agentfoundation/integration/wecom/WecomUserBehaviorDataRequest.java b/src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomUserBehaviorDataRequest.java similarity index 82% rename from src/main/java/com/company/agentfoundation/integration/wecom/WecomUserBehaviorDataRequest.java rename to src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomUserBehaviorDataRequest.java index cc64748..ab355a5 100644 --- a/src/main/java/com/company/agentfoundation/integration/wecom/WecomUserBehaviorDataRequest.java +++ b/src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomUserBehaviorDataRequest.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.integration.wecom; +package com.company.agentfoundation.integration.wecom.dto; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/src/main/java/com/company/agentfoundation/integration/wecom/WecomUserBehaviorDataResponse.java b/src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomUserBehaviorDataResponse.java similarity index 93% rename from src/main/java/com/company/agentfoundation/integration/wecom/WecomUserBehaviorDataResponse.java rename to src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomUserBehaviorDataResponse.java index d771db5..8c1219c 100644 --- a/src/main/java/com/company/agentfoundation/integration/wecom/WecomUserBehaviorDataResponse.java +++ b/src/main/java/com/company/agentfoundation/integration/wecom/dto/WecomUserBehaviorDataResponse.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.integration.wecom; +package com.company.agentfoundation.integration.wecom.dto; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/src/main/java/com/company/agentfoundation/integration/wecom/WecomEncryptedEnvelope.java b/src/main/java/com/company/agentfoundation/integration/wecom/model/WecomEncryptedEnvelope.java similarity index 93% rename from src/main/java/com/company/agentfoundation/integration/wecom/WecomEncryptedEnvelope.java rename to src/main/java/com/company/agentfoundation/integration/wecom/model/WecomEncryptedEnvelope.java index e65d816..d2b7117 100644 --- a/src/main/java/com/company/agentfoundation/integration/wecom/WecomEncryptedEnvelope.java +++ b/src/main/java/com/company/agentfoundation/integration/wecom/model/WecomEncryptedEnvelope.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.integration.wecom; +package com.company.agentfoundation.integration.wecom.model; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; diff --git a/src/main/java/com/company/agentfoundation/integration/wecom/WecomIncomingMessage.java b/src/main/java/com/company/agentfoundation/integration/wecom/model/WecomIncomingMessage.java similarity index 96% rename from src/main/java/com/company/agentfoundation/integration/wecom/WecomIncomingMessage.java rename to src/main/java/com/company/agentfoundation/integration/wecom/model/WecomIncomingMessage.java index ae8d665..1914916 100644 --- a/src/main/java/com/company/agentfoundation/integration/wecom/WecomIncomingMessage.java +++ b/src/main/java/com/company/agentfoundation/integration/wecom/model/WecomIncomingMessage.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.integration.wecom; +package com.company.agentfoundation.integration.wecom.model; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; diff --git a/src/main/java/com/company/agentfoundation/integration/wecom/WecomCryptoService.java b/src/main/java/com/company/agentfoundation/integration/wecom/service/WecomCryptoService.java similarity index 98% rename from src/main/java/com/company/agentfoundation/integration/wecom/WecomCryptoService.java rename to src/main/java/com/company/agentfoundation/integration/wecom/service/WecomCryptoService.java index f1d8e14..8e6768f 100644 --- a/src/main/java/com/company/agentfoundation/integration/wecom/WecomCryptoService.java +++ b/src/main/java/com/company/agentfoundation/integration/wecom/service/WecomCryptoService.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.integration.wecom; +package com.company.agentfoundation.integration.wecom.service; import com.company.agentfoundation.config.FoundationProperties; import org.springframework.stereotype.Service; diff --git a/src/main/java/com/company/agentfoundation/integration/wecom/WecomStudentContactStatsService.java b/src/main/java/com/company/agentfoundation/integration/wecom/service/WecomStudentContactStatsService.java similarity index 93% rename from src/main/java/com/company/agentfoundation/integration/wecom/WecomStudentContactStatsService.java rename to src/main/java/com/company/agentfoundation/integration/wecom/service/WecomStudentContactStatsService.java index 9327a99..fadf0d0 100644 --- a/src/main/java/com/company/agentfoundation/integration/wecom/WecomStudentContactStatsService.java +++ b/src/main/java/com/company/agentfoundation/integration/wecom/service/WecomStudentContactStatsService.java @@ -1,4 +1,9 @@ -package com.company.agentfoundation.integration.wecom; +package com.company.agentfoundation.integration.wecom.service; + +import com.company.agentfoundation.integration.wecom.client.WecomClient; +import com.company.agentfoundation.integration.wecom.dto.WecomExternalContactBatchResponse; +import com.company.agentfoundation.integration.wecom.dto.WecomStudentContactStats; +import com.company.agentfoundation.integration.wecom.dto.WecomUserBehaviorDataResponse; import org.springframework.stereotype.Service; diff --git a/src/main/java/com/company/agentfoundation/media/controller/MediaTaskController.java b/src/main/java/com/company/agentfoundation/media/controller/MediaTaskController.java new file mode 100644 index 0000000..5f1d10a --- /dev/null +++ b/src/main/java/com/company/agentfoundation/media/controller/MediaTaskController.java @@ -0,0 +1,71 @@ +package com.company.agentfoundation.media.controller; + +import com.company.agentfoundation.media.dto.CreateMediaTaskRequest; +import com.company.agentfoundation.media.dto.MediaTaskListResponse; +import com.company.agentfoundation.media.dto.MediaTaskResponse; +import com.company.agentfoundation.media.service.MediaTaskService; +import com.company.agentfoundation.security.AgentAuthService; +import jakarta.validation.Valid; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +@RestController +@RequestMapping("/v1/media") +public class MediaTaskController { + private final AgentAuthService authService; + private final MediaTaskService mediaTaskService; + + public MediaTaskController(AgentAuthService authService, MediaTaskService mediaTaskService) { + this.authService = authService; + this.mediaTaskService = mediaTaskService; + } + + @PostMapping("/tasks") + public ResponseEntity createTask( + @RequestHeader("X-Agent-Id") String agentId, + @RequestHeader("X-Internal-Key") String internalKey, + @Valid @RequestBody CreateMediaTaskRequest request + ) { + authService.authenticate(agentId, internalKey); + return ResponseEntity.ok(mediaTaskService.create(request)); + } + + @GetMapping("/tasks") + public ResponseEntity listTasks( + @RequestHeader("X-Agent-Id") String agentId, + @RequestHeader("X-Internal-Key") String internalKey, + @RequestParam(value = "status", required = false) String status, + @RequestParam(value = "platform", required = false) String platform + ) { + authService.authenticate(agentId, internalKey); + return ResponseEntity.ok(MediaTaskListResponse.from(mediaTaskService.list(status, platform))); + } + + @GetMapping("/tasks/{taskId}") + public ResponseEntity getTask( + @RequestHeader("X-Agent-Id") String agentId, + @RequestHeader("X-Internal-Key") String internalKey, + @PathVariable String taskId + ) { + authService.authenticate(agentId, internalKey); + return ResponseEntity.ok(mediaTaskService.get(taskId)); + } + + @GetMapping("/dashboard/summary") + public ResponseEntity> dashboardSummary( + @RequestHeader("X-Agent-Id") String agentId, + @RequestHeader("X-Internal-Key") String internalKey + ) { + authService.authenticate(agentId, internalKey); + return ResponseEntity.ok(mediaTaskService.dashboardSummary()); + } +} diff --git a/src/main/java/com/company/agentfoundation/media/controller/MediaWorkerController.java b/src/main/java/com/company/agentfoundation/media/controller/MediaWorkerController.java new file mode 100644 index 0000000..ef628b3 --- /dev/null +++ b/src/main/java/com/company/agentfoundation/media/controller/MediaWorkerController.java @@ -0,0 +1,53 @@ +package com.company.agentfoundation.media.controller; + +import com.company.agentfoundation.media.dto.MediaTaskResponse; +import com.company.agentfoundation.media.dto.WorkerPollRequest; +import com.company.agentfoundation.media.dto.WorkerTaskResultRequest; +import com.company.agentfoundation.media.service.MediaTaskService; +import jakarta.validation.Valid; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequestMapping("/v1/media/workers") +public class MediaWorkerController { + private static final String LOCAL_WORKER_TOKEN = "local-worker-dev-token"; + + private final MediaTaskService mediaTaskService; + + public MediaWorkerController(MediaTaskService mediaTaskService) { + this.mediaTaskService = mediaTaskService; + } + + @PostMapping("/poll") + public ResponseEntity> poll( + @RequestHeader("X-Worker-Token") String workerToken, + @Valid @RequestBody WorkerPollRequest request + ) { + authenticateWorker(workerToken); + return ResponseEntity.ok(mediaTaskService.poll(request)); + } + + @PostMapping("/tasks/{taskId}/result") + public ResponseEntity result( + @RequestHeader("X-Worker-Token") String workerToken, + @PathVariable String taskId, + @Valid @RequestBody WorkerTaskResultRequest request + ) { + authenticateWorker(workerToken); + return ResponseEntity.ok(mediaTaskService.complete(taskId, request)); + } + + private void authenticateWorker(String workerToken) { + if (!LOCAL_WORKER_TOKEN.equals(workerToken)) { + throw new IllegalArgumentException("Invalid local worker token"); + } + } +} diff --git a/src/main/java/com/company/agentfoundation/media/dto/CreateMediaTaskRequest.java b/src/main/java/com/company/agentfoundation/media/dto/CreateMediaTaskRequest.java new file mode 100644 index 0000000..de90ead --- /dev/null +++ b/src/main/java/com/company/agentfoundation/media/dto/CreateMediaTaskRequest.java @@ -0,0 +1,15 @@ +package com.company.agentfoundation.media.dto; + +import com.company.agentfoundation.media.model.MediaTaskType; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +import java.util.Map; + +public record CreateMediaTaskRequest( + @NotNull MediaTaskType taskType, + @NotBlank String platform, + @NotBlank String accountId, + Map payload +) { +} diff --git a/src/main/java/com/company/agentfoundation/media/dto/MediaTaskListResponse.java b/src/main/java/com/company/agentfoundation/media/dto/MediaTaskListResponse.java new file mode 100644 index 0000000..3b5c4ba --- /dev/null +++ b/src/main/java/com/company/agentfoundation/media/dto/MediaTaskListResponse.java @@ -0,0 +1,12 @@ +package com.company.agentfoundation.media.dto; + +import java.util.List; + +public record MediaTaskListResponse( + int total, + List items +) { + public static MediaTaskListResponse from(List items) { + return new MediaTaskListResponse(items.size(), items); + } +} diff --git a/src/main/java/com/company/agentfoundation/media/dto/MediaTaskResponse.java b/src/main/java/com/company/agentfoundation/media/dto/MediaTaskResponse.java new file mode 100644 index 0000000..59372ae --- /dev/null +++ b/src/main/java/com/company/agentfoundation/media/dto/MediaTaskResponse.java @@ -0,0 +1,38 @@ +package com.company.agentfoundation.media.dto; + +import com.company.agentfoundation.media.model.MediaTask; +import com.company.agentfoundation.media.model.MediaTaskStatus; +import com.company.agentfoundation.media.model.MediaTaskType; + +import java.time.Instant; +import java.util.Map; + +public record MediaTaskResponse( + String id, + MediaTaskType taskType, + String platform, + String accountId, + MediaTaskStatus status, + String assignedWorkerId, + Map payload, + Map result, + String errorMessage, + Instant createdAt, + Instant updatedAt +) { + public static MediaTaskResponse from(MediaTask task) { + return new MediaTaskResponse( + task.getId(), + task.getTaskType(), + task.getPlatform(), + task.getAccountId(), + task.getStatus(), + task.getAssignedWorkerId(), + task.getPayload(), + task.getResult(), + task.getErrorMessage(), + task.getCreatedAt(), + task.getUpdatedAt() + ); + } +} diff --git a/src/main/java/com/company/agentfoundation/media/dto/WorkerPollRequest.java b/src/main/java/com/company/agentfoundation/media/dto/WorkerPollRequest.java new file mode 100644 index 0000000..f45e223 --- /dev/null +++ b/src/main/java/com/company/agentfoundation/media/dto/WorkerPollRequest.java @@ -0,0 +1,10 @@ +package com.company.agentfoundation.media.dto; + +import java.util.List; + +public record WorkerPollRequest( + String workerId, + List platforms, + int maxTasks +) { +} diff --git a/src/main/java/com/company/agentfoundation/media/dto/WorkerTaskResultRequest.java b/src/main/java/com/company/agentfoundation/media/dto/WorkerTaskResultRequest.java new file mode 100644 index 0000000..730109b --- /dev/null +++ b/src/main/java/com/company/agentfoundation/media/dto/WorkerTaskResultRequest.java @@ -0,0 +1,12 @@ +package com.company.agentfoundation.media.dto; + +import com.company.agentfoundation.media.model.MediaTaskStatus; + +import java.util.Map; + +public record WorkerTaskResultRequest( + MediaTaskStatus status, + Map result, + String errorMessage +) { +} diff --git a/src/main/java/com/company/agentfoundation/media/model/MediaTask.java b/src/main/java/com/company/agentfoundation/media/model/MediaTask.java new file mode 100644 index 0000000..5c7c25b --- /dev/null +++ b/src/main/java/com/company/agentfoundation/media/model/MediaTask.java @@ -0,0 +1,162 @@ +package com.company.agentfoundation.media.model; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class MediaTask { + private final String id; + private final MediaTaskType taskType; + private final String platform; + private final String accountId; + private final Map payload; + private final Instant createdAt; + private MediaTaskStatus status; + private String assignedWorkerId; + private Map result; + private String errorMessage; + private Instant updatedAt; + + public MediaTask(String id, MediaTaskType taskType, String platform, String accountId, Map payload) { + this.id = id; + this.taskType = taskType; + this.platform = platform; + this.accountId = accountId; + this.payload = payload == null ? new LinkedHashMap<>() : new LinkedHashMap<>(payload); + this.createdAt = Instant.now(); + this.updatedAt = createdAt; + this.status = MediaTaskStatus.PENDING; + this.result = new LinkedHashMap<>(); + } + + public String getId() { return id; } + public MediaTaskType getTaskType() { return taskType; } + public String getPlatform() { return platform; } + public String getAccountId() { return accountId; } + public Map getPayload() { return payload; } + public Instant getCreatedAt() { return createdAt; } + public MediaTaskStatus getStatus() { return status; } + public String getAssignedWorkerId() { return assignedWorkerId; } + public Map getResult() { return result; } + public String getErrorMessage() { return errorMessage; } + public Instant getUpdatedAt() { return updatedAt; } + + public void assignTo(String workerId) { + this.assignedWorkerId = workerId; + this.status = MediaTaskStatus.RUNNING; + this.updatedAt = Instant.now(); + } + + public void complete(MediaTaskStatus status, Map result, String errorMessage) { + if (status != MediaTaskStatus.SUCCESS && status != MediaTaskStatus.FAILED) { + throw new IllegalArgumentException("Task result status must be SUCCESS or FAILED"); + } + this.status = status; + this.result = result == null ? new LinkedHashMap<>() : new LinkedHashMap<>(result); + this.errorMessage = errorMessage; + this.updatedAt = Instant.now(); + } + + public static Map aggregateMetrics(List tasks) { + long views = 0; + long likes = 0; + long comments = 0; + long collects = 0; + long shares = 0; + List> rows = new ArrayList<>(); + List> videoRows = new ArrayList<>(); + for (MediaTask task : tasks) { + if (task.status != MediaTaskStatus.SUCCESS || task.result == null) { + continue; + } + Map metrics = mapValue(task.result.get("metrics")); + if (metrics.isEmpty()) { + continue; + } + long taskViews = longValue(metrics.get("views")); + long taskLikes = longValue(metrics.get("likes")); + long taskComments = longValue(metrics.get("comments")); + long taskCollects = longValue(metrics.get("collects")); + long taskShares = longValue(metrics.get("shares")); + views += taskViews; + likes += taskLikes; + comments += taskComments; + collects += taskCollects; + shares += taskShares; + rows.add(Map.of( + "taskId", task.id, + "platform", task.platform, + "accountId", task.accountId, + "views", taskViews, + "likes", taskLikes, + "comments", taskComments, + "collects", taskCollects, + "shares", taskShares + )); + for (Map video : listOfMaps(task.result.get("videos"))) { + videoRows.add(Map.of( + "taskId", task.id, + "platform", task.platform, + "accountId", task.accountId, + "title", stringValue(video.get("title")), + "href", stringValue(video.get("href")), + "views", longValue(video.get("views")), + "likes", longValue(video.get("likes")), + "comments", longValue(video.get("comments")), + "collects", longValue(video.get("collects")), + "shares", longValue(video.get("shares")) + )); + } + } + Map summary = new LinkedHashMap<>(); + summary.put("views", views); + summary.put("likes", likes); + summary.put("comments", comments); + summary.put("collects", collects); + summary.put("shares", shares); + summary.put("interactionCount", likes + comments + collects + shares); + summary.put("interactionRate", views <= 0 ? 0.0 : (likes + comments + collects + shares) * 1.0 / views); + summary.put("rows", rows); + summary.put("videoCount", videoRows.size()); + summary.put("videoRows", videoRows); + return summary; + } + + @SuppressWarnings("unchecked") + private static Map mapValue(Object value) { + if (value instanceof Map map) { + return (Map) map; + } + return Map.of(); + } + + private static long longValue(Object value) { + if (value instanceof Number number) { + return number.longValue(); + } + if (value instanceof String text && !text.isBlank()) { + return Long.parseLong(text); + } + return 0; + } + + @SuppressWarnings("unchecked") + private static List> listOfMaps(Object value) { + if (!(value instanceof List list)) { + return List.of(); + } + List> maps = new ArrayList<>(); + for (Object item : list) { + if (item instanceof Map map) { + maps.add((Map) map); + } + } + return maps; + } + + private static String stringValue(Object value) { + return value == null ? "" : String.valueOf(value); + } +} diff --git a/src/main/java/com/company/agentfoundation/media/model/MediaTaskStatus.java b/src/main/java/com/company/agentfoundation/media/model/MediaTaskStatus.java new file mode 100644 index 0000000..c91b8b9 --- /dev/null +++ b/src/main/java/com/company/agentfoundation/media/model/MediaTaskStatus.java @@ -0,0 +1,8 @@ +package com.company.agentfoundation.media.model; + +public enum MediaTaskStatus { + PENDING, + RUNNING, + SUCCESS, + FAILED +} diff --git a/src/main/java/com/company/agentfoundation/media/model/MediaTaskType.java b/src/main/java/com/company/agentfoundation/media/model/MediaTaskType.java new file mode 100644 index 0000000..f078433 --- /dev/null +++ b/src/main/java/com/company/agentfoundation/media/model/MediaTaskType.java @@ -0,0 +1,8 @@ +package com.company.agentfoundation.media.model; + +public enum MediaTaskType { + SYNC_COMMENTS, + SYNC_METRICS, + REPLY_COMMENT, + PUBLISH_CONTENT +} diff --git a/src/main/java/com/company/agentfoundation/media/service/MediaTaskService.java b/src/main/java/com/company/agentfoundation/media/service/MediaTaskService.java new file mode 100644 index 0000000..1d4219d --- /dev/null +++ b/src/main/java/com/company/agentfoundation/media/service/MediaTaskService.java @@ -0,0 +1,118 @@ +package com.company.agentfoundation.media.service; + +import com.company.agentfoundation.media.dto.CreateMediaTaskRequest; +import com.company.agentfoundation.media.dto.MediaTaskResponse; +import com.company.agentfoundation.media.dto.WorkerPollRequest; +import com.company.agentfoundation.media.dto.WorkerTaskResultRequest; +import com.company.agentfoundation.media.model.MediaTask; +import com.company.agentfoundation.media.model.MediaTaskStatus; +import org.springframework.stereotype.Service; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +@Service +public class MediaTaskService { + private final Map tasks = new ConcurrentHashMap<>(); + private final Map workerHeartbeats = new ConcurrentHashMap<>(); + + public MediaTaskResponse create(CreateMediaTaskRequest request) { + MediaTask task = new MediaTask( + "media_" + UUID.randomUUID(), + request.taskType(), + normalize(request.platform()), + request.accountId(), + request.payload() + ); + tasks.put(task.getId(), task); + return MediaTaskResponse.from(task); + } + + public MediaTaskResponse get(String taskId) { + return MediaTaskResponse.from(required(taskId)); + } + + public List list(String status, String platform) { + return tasks.values().stream() + .filter(task -> status == null || status.isBlank() || task.getStatus().name().equalsIgnoreCase(status)) + .filter(task -> platform == null || platform.isBlank() || task.getPlatform().equalsIgnoreCase(platform)) + .sorted(Comparator.comparing(MediaTask::getCreatedAt).reversed()) + .map(MediaTaskResponse::from) + .toList(); + } + + public List poll(WorkerPollRequest request) { + String workerId = request.workerId() == null || request.workerId().isBlank() + ? "local-worker" + : request.workerId(); + int limit = request.maxTasks() <= 0 ? 1 : Math.min(request.maxTasks(), 10); + List platforms = request.platforms() == null + ? List.of() + : request.platforms().stream().map(this::normalize).toList(); + workerHeartbeats.put(workerId, Instant.now()); + + List assigned = new ArrayList<>(); + for (MediaTask task : tasks.values().stream() + .filter(task -> task.getStatus() == MediaTaskStatus.PENDING) + .filter(task -> platforms.isEmpty() || platforms.contains(task.getPlatform())) + .sorted(Comparator.comparing(MediaTask::getCreatedAt)) + .toList()) { + synchronized (task) { + if (task.getStatus() != MediaTaskStatus.PENDING) { + continue; + } + task.assignTo(workerId); + assigned.add(MediaTaskResponse.from(task)); + } + if (assigned.size() >= limit) { + break; + } + } + return assigned; + } + + public MediaTaskResponse complete(String taskId, WorkerTaskResultRequest request) { + MediaTask task = required(taskId); + MediaTaskStatus status = request.status() == null ? MediaTaskStatus.SUCCESS : request.status(); + synchronized (task) { + task.complete(status, request.result(), request.errorMessage()); + } + return MediaTaskResponse.from(task); + } + + public Map dashboardSummary() { + List allTasks = new ArrayList<>(tasks.values()); + Map summary = new LinkedHashMap<>(); + summary.put("taskCount", allTasks.size()); + summary.put("pendingCount", countByStatus(allTasks, MediaTaskStatus.PENDING)); + summary.put("runningCount", countByStatus(allTasks, MediaTaskStatus.RUNNING)); + summary.put("successCount", countByStatus(allTasks, MediaTaskStatus.SUCCESS)); + summary.put("failedCount", countByStatus(allTasks, MediaTaskStatus.FAILED)); + summary.put("metrics", MediaTask.aggregateMetrics(allTasks)); + summary.put("workers", workerHeartbeats); + return summary; + } + + private long countByStatus(List allTasks, MediaTaskStatus status) { + return allTasks.stream().filter(task -> task.getStatus() == status).count(); + } + + private MediaTask required(String taskId) { + MediaTask task = tasks.get(taskId); + if (task == null) { + throw new IllegalArgumentException("Media task not found: " + taskId); + } + return task; + } + + private String normalize(String platform) { + return platform == null ? "" : platform.trim().toLowerCase(Locale.ROOT); + } +} diff --git a/src/main/java/com/company/agentfoundation/controller/JumpRopeAiController.java b/src/main/java/com/company/agentfoundation/miniprogram/jumprope/controller/JumpRopeAiController.java similarity index 74% rename from src/main/java/com/company/agentfoundation/controller/JumpRopeAiController.java rename to src/main/java/com/company/agentfoundation/miniprogram/jumprope/controller/JumpRopeAiController.java index 369574b..c5b21c1 100644 --- a/src/main/java/com/company/agentfoundation/controller/JumpRopeAiController.java +++ b/src/main/java/com/company/agentfoundation/miniprogram/jumprope/controller/JumpRopeAiController.java @@ -1,9 +1,9 @@ -package com.company.agentfoundation.controller; +package com.company.agentfoundation.miniprogram.jumprope.controller; -import com.company.agentfoundation.miniprogram.jumprope.JumpRopeAiResponse; -import com.company.agentfoundation.miniprogram.jumprope.JumpRopeAiService; -import com.company.agentfoundation.miniprogram.jumprope.JumpRopeAnalyzeRequest; -import com.company.agentfoundation.miniprogram.jumprope.JumpRopeChatRequest; +import com.company.agentfoundation.miniprogram.jumprope.dto.JumpRopeAiResponse; +import com.company.agentfoundation.miniprogram.jumprope.service.JumpRopeAiService; +import com.company.agentfoundation.miniprogram.jumprope.dto.JumpRopeAnalyzeRequest; +import com.company.agentfoundation.miniprogram.jumprope.dto.JumpRopeChatRequest; import jakarta.validation.Valid; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; diff --git a/src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeAiResponse.java b/src/main/java/com/company/agentfoundation/miniprogram/jumprope/dto/JumpRopeAiResponse.java similarity index 65% rename from src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeAiResponse.java rename to src/main/java/com/company/agentfoundation/miniprogram/jumprope/dto/JumpRopeAiResponse.java index 2fbe858..839761e 100644 --- a/src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeAiResponse.java +++ b/src/main/java/com/company/agentfoundation/miniprogram/jumprope/dto/JumpRopeAiResponse.java @@ -1,4 +1,6 @@ -package com.company.agentfoundation.miniprogram.jumprope; +package com.company.agentfoundation.miniprogram.jumprope.dto; + +import com.company.agentfoundation.miniprogram.jumprope.model.JumpRopeVideoRecommendation; import java.util.List; diff --git a/src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeAnalyzeRequest.java b/src/main/java/com/company/agentfoundation/miniprogram/jumprope/dto/JumpRopeAnalyzeRequest.java similarity index 80% rename from src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeAnalyzeRequest.java rename to src/main/java/com/company/agentfoundation/miniprogram/jumprope/dto/JumpRopeAnalyzeRequest.java index 221e423..320118d 100644 --- a/src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeAnalyzeRequest.java +++ b/src/main/java/com/company/agentfoundation/miniprogram/jumprope/dto/JumpRopeAnalyzeRequest.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.miniprogram.jumprope; +package com.company.agentfoundation.miniprogram.jumprope.dto; import jakarta.validation.constraints.NotNull; diff --git a/src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeChatRequest.java b/src/main/java/com/company/agentfoundation/miniprogram/jumprope/dto/JumpRopeChatRequest.java similarity index 72% rename from src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeChatRequest.java rename to src/main/java/com/company/agentfoundation/miniprogram/jumprope/dto/JumpRopeChatRequest.java index bc16164..427f399 100644 --- a/src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeChatRequest.java +++ b/src/main/java/com/company/agentfoundation/miniprogram/jumprope/dto/JumpRopeChatRequest.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.miniprogram.jumprope; +package com.company.agentfoundation.miniprogram.jumprope.dto; import jakarta.validation.constraints.NotBlank; diff --git a/src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeExerciseData.java b/src/main/java/com/company/agentfoundation/miniprogram/jumprope/model/JumpRopeExerciseData.java similarity index 87% rename from src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeExerciseData.java rename to src/main/java/com/company/agentfoundation/miniprogram/jumprope/model/JumpRopeExerciseData.java index b2a5446..966e66d 100644 --- a/src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeExerciseData.java +++ b/src/main/java/com/company/agentfoundation/miniprogram/jumprope/model/JumpRopeExerciseData.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.miniprogram.jumprope; +package com.company.agentfoundation.miniprogram.jumprope.model; import java.time.LocalDateTime; diff --git a/src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeMessage.java b/src/main/java/com/company/agentfoundation/miniprogram/jumprope/model/JumpRopeMessage.java similarity index 66% rename from src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeMessage.java rename to src/main/java/com/company/agentfoundation/miniprogram/jumprope/model/JumpRopeMessage.java index 7cbef6e..1a61a2a 100644 --- a/src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeMessage.java +++ b/src/main/java/com/company/agentfoundation/miniprogram/jumprope/model/JumpRopeMessage.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.miniprogram.jumprope; +package com.company.agentfoundation.miniprogram.jumprope.model; public record JumpRopeMessage( Long id, diff --git a/src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeSession.java b/src/main/java/com/company/agentfoundation/miniprogram/jumprope/model/JumpRopeSession.java similarity index 79% rename from src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeSession.java rename to src/main/java/com/company/agentfoundation/miniprogram/jumprope/model/JumpRopeSession.java index af35b6a..81bee79 100644 --- a/src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeSession.java +++ b/src/main/java/com/company/agentfoundation/miniprogram/jumprope/model/JumpRopeSession.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.miniprogram.jumprope; +package com.company.agentfoundation.miniprogram.jumprope.model; public record JumpRopeSession( Long id, diff --git a/src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeVideoRecommendation.java b/src/main/java/com/company/agentfoundation/miniprogram/jumprope/model/JumpRopeVideoRecommendation.java similarity index 76% rename from src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeVideoRecommendation.java rename to src/main/java/com/company/agentfoundation/miniprogram/jumprope/model/JumpRopeVideoRecommendation.java index fc964e2..b95a9d8 100644 --- a/src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeVideoRecommendation.java +++ b/src/main/java/com/company/agentfoundation/miniprogram/jumprope/model/JumpRopeVideoRecommendation.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.miniprogram.jumprope; +package com.company.agentfoundation.miniprogram.jumprope.model; public record JumpRopeVideoRecommendation( Integer resourceId, diff --git a/src/main/java/com/company/agentfoundation/miniprogram/jumprope/MiniProgramUser.java b/src/main/java/com/company/agentfoundation/miniprogram/jumprope/model/MiniProgramUser.java similarity index 68% rename from src/main/java/com/company/agentfoundation/miniprogram/jumprope/MiniProgramUser.java rename to src/main/java/com/company/agentfoundation/miniprogram/jumprope/model/MiniProgramUser.java index 9faa0fb..58d6c9e 100644 --- a/src/main/java/com/company/agentfoundation/miniprogram/jumprope/MiniProgramUser.java +++ b/src/main/java/com/company/agentfoundation/miniprogram/jumprope/model/MiniProgramUser.java @@ -1,4 +1,4 @@ -package com.company.agentfoundation.miniprogram.jumprope; +package com.company.agentfoundation.miniprogram.jumprope.model; public record MiniProgramUser( Integer userId, diff --git a/src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeAiRepository.java b/src/main/java/com/company/agentfoundation/miniprogram/jumprope/repository/JumpRopeAiRepository.java similarity index 95% rename from src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeAiRepository.java rename to src/main/java/com/company/agentfoundation/miniprogram/jumprope/repository/JumpRopeAiRepository.java index 6569f9a..8471c6b 100644 --- a/src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeAiRepository.java +++ b/src/main/java/com/company/agentfoundation/miniprogram/jumprope/repository/JumpRopeAiRepository.java @@ -1,4 +1,10 @@ -package com.company.agentfoundation.miniprogram.jumprope; +package com.company.agentfoundation.miniprogram.jumprope.repository; + +import com.company.agentfoundation.miniprogram.jumprope.model.JumpRopeExerciseData; +import com.company.agentfoundation.miniprogram.jumprope.model.JumpRopeMessage; +import com.company.agentfoundation.miniprogram.jumprope.model.JumpRopeSession; +import com.company.agentfoundation.miniprogram.jumprope.model.JumpRopeVideoRecommendation; +import com.company.agentfoundation.miniprogram.jumprope.model.MiniProgramUser; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.jdbc.core.JdbcTemplate; diff --git a/src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeAiService.java b/src/main/java/com/company/agentfoundation/miniprogram/jumprope/service/JumpRopeAiService.java similarity index 93% rename from src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeAiService.java rename to src/main/java/com/company/agentfoundation/miniprogram/jumprope/service/JumpRopeAiService.java index f2a4f56..e1570d7 100644 --- a/src/main/java/com/company/agentfoundation/miniprogram/jumprope/JumpRopeAiService.java +++ b/src/main/java/com/company/agentfoundation/miniprogram/jumprope/service/JumpRopeAiService.java @@ -1,7 +1,17 @@ -package com.company.agentfoundation.miniprogram.jumprope; +package com.company.agentfoundation.miniprogram.jumprope.service; -import com.company.agentfoundation.domain.ChatRequest; -import com.company.agentfoundation.domain.ChatResponse; +import com.company.agentfoundation.miniprogram.jumprope.dto.JumpRopeAiResponse; +import com.company.agentfoundation.miniprogram.jumprope.dto.JumpRopeAnalyzeRequest; +import com.company.agentfoundation.miniprogram.jumprope.dto.JumpRopeChatRequest; +import com.company.agentfoundation.miniprogram.jumprope.model.JumpRopeExerciseData; +import com.company.agentfoundation.miniprogram.jumprope.model.JumpRopeMessage; +import com.company.agentfoundation.miniprogram.jumprope.model.JumpRopeSession; +import com.company.agentfoundation.miniprogram.jumprope.model.JumpRopeVideoRecommendation; +import com.company.agentfoundation.miniprogram.jumprope.model.MiniProgramUser; +import com.company.agentfoundation.miniprogram.jumprope.repository.JumpRopeAiRepository; + +import com.company.agentfoundation.dto.chat.ChatRequest; +import com.company.agentfoundation.dto.chat.ChatResponse; import com.company.agentfoundation.security.AgentContext; import com.company.agentfoundation.service.ChatService; import com.fasterxml.jackson.core.JsonProcessingException; diff --git a/src/main/java/com/company/agentfoundation/service/ChatService.java b/src/main/java/com/company/agentfoundation/service/ChatService.java index 3df5a74..ee7991b 100644 --- a/src/main/java/com/company/agentfoundation/service/ChatService.java +++ b/src/main/java/com/company/agentfoundation/service/ChatService.java @@ -1,9 +1,9 @@ package com.company.agentfoundation.service; -import com.company.agentfoundation.api.DeepseekChatRequest; -import com.company.agentfoundation.api.DeepseekChatResponse; +import com.company.agentfoundation.api.dto.DeepseekChatRequest; +import com.company.agentfoundation.api.dto.DeepseekChatResponse; import com.company.agentfoundation.api.DeepseekClient; -import com.company.agentfoundation.domain.ChatRequest; -import com.company.agentfoundation.domain.ChatResponse; +import com.company.agentfoundation.dto.chat.ChatRequest; +import com.company.agentfoundation.dto.chat.ChatResponse; import com.company.agentfoundation.security.AgentContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/start_local_client.bat b/start_local_client.bat new file mode 100644 index 0000000..64d42c6 --- /dev/null +++ b/start_local_client.bat @@ -0,0 +1,5 @@ +@echo off +setlocal +cd /d "%~dp0" +powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0scripts\start_local_client.ps1" +pause diff --git a/start_yuedong_client.bat b/start_yuedong_client.bat new file mode 100644 index 0000000..df335ef --- /dev/null +++ b/start_yuedong_client.bat @@ -0,0 +1,5 @@ +@echo off +setlocal +cd /d "%~dp0" +powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0scripts\start_local_client.ps1" -BaseUrl "http://agent.yuedongsports.com" -RemoteBackend +pause