diff --git a/androidExampleDemo/(红头)项目编译Debug版本和Release版本apk的方法.txt b/androidExampleDemo/(红头)项目编译Debug版本和Release版本apk的方法.txt new file mode 100644 index 0000000..8f47e29 --- /dev/null +++ b/androidExampleDemo/(红头)项目编译Debug版本和Release版本apk的方法.txt @@ -0,0 +1,155 @@ +# Android 项目编译 Debug 和 Release 版本 APK 的方法 + +## 项目信息 +- **项目名称**: com.xunpaisoft.social +- **项目路径**: D:\zhini\android-im +- **JDK 要求**: Java 11 或更高版本 +- **推荐 JDK**: Android Studio 内置 JDK 21 + +## 环境准备 + +### 1. 检查 Java 版本 +```bash +java -version +``` +**注意**: 如果系统只有 Java 8,需要使用 Android Studio 的 JDK 21 + +### 2. 设置环境变量 +```powershell +# 设置 Android Studio JDK 路径 +$env:JAVA_HOME="C:\Program Files\Android\Android Studio1\jbr" + +# 设置 Android SDK 路径 +$env:ANDROID_HOME="C:\Users\$env:USERNAME\AppData\Local\Android\Sdk" +``` + +## 编译方法 + +### 方法一:使用 Gradle Wrapper(推荐) + +#### 1. Debug 版本编译 +```bash +# 设置环境变量并编译 Debug 版本 +$env:JAVA_HOME="C:\Program Files\Android\Android Studio1\jbr"; $env:ANDROID_HOME="C:\Users\$env:USERNAME\AppData\Local\Android\Sdk"; .\gradlew.bat assembleDebug +``` + +**编译结果**: +- ✅ 编译状态: BUILD SUCCESSFUL +- ⏱️ 编译时间: 约 34 秒 +- 📁 APK 位置: `app\build\outputs\apk\debug\app-debug.apk` + +#### 2. Release 版本编译 +```bash +# 设置环境变量并编译 Release 版本 +$env:JAVA_HOME="C:\Program Files\Android\Android Studio1\jbr"; $env:ANDROID_HOME="C:\Users\$env:USERNAME\AppData\Local\Android\Sdk"; .\gradlew.bat assembleRelease +``` + +**编译结果**: +- ✅ 编译状态: BUILD SUCCESSFUL +- ⏱️ 编译时间: 约 3分48秒 +- 📁 APK 位置: `app\build\outputs\apk\release\app-release.apk` + +### 方法二:使用 Android Studio(最简单) + +1. **打开 Android Studio** +2. **导入项目**: 选择 `D:\zhini\android-im` 目录 +3. **等待 Gradle 同步完成** +4. **编译项目**: + - **Debug 版本**: `Build` → `Build Bundle(s) / APK(s)` → `Build APK(s)` + - **Release 版本**: `Build` → `Generate Signed Bundle / APK` → `APK` + +## 安装 APK 到设备 + +### 安装 Debug 版本 +```bash +adb install -r app\build\outputs\apk\debug\app-debug.apk +``` + +### 安装 Release 版本 +```bash +adb install -r app\build\outputs\apk\release\app-release.apk +``` + +## 版本对比 + +| 特性 | Debug 版本 | Release 版本 | +|------|------------|--------------| +| **编译时间** | 约 34 秒 | 约 3分48秒 | +| **APK 大小** | 较大 | 较小(优化后) | +| **代码混淆** | 否 | 是(ProGuard) | +| **调试信息** | 包含 | 移除 | +| **性能** | 较慢 | 较快 | +| **用途** | 开发测试 | 正式发布 | + +## 常见问题解决 + +### 问题1: Java 版本不兼容 +**错误信息**: `Dependency requires at least JVM runtime version 11. This build uses a Java 8 JVM.` + +**解决方案**: +```bash +# 停止现有 Gradle daemon +.\gradlew.bat --stop + +# 使用 Android Studio JDK 编译 +$env:JAVA_HOME="C:\Program Files\Android\Android Studio1\jbr"; .\gradlew.bat assembleDebug +``` + +### 问题2: Gradle Wrapper 不存在 +**解决方案**: 项目已包含 `gradlew.bat` 文件,如果缺失可以重新创建。 + +### 问题3: 权限重复警告 +**现象**: 编译时出现权限重复警告 +**影响**: 不影响编译,但建议清理重复权限声明 + +## 编译命令总结 + +### 一键编译脚本 +```powershell +# Debug 版本一键编译 +$env:JAVA_HOME="C:\Program Files\Android\Android Studio1\jbr"; $env:ANDROID_HOME="C:\Users\$env:USERNAME\AppData\Local\Android\Sdk"; .\gradlew.bat assembleDebug + +# Release 版本一键编译 +$env:JAVA_HOME="C:\Program Files\Android\Android Studio1\jbr"; $env:ANDROID_HOME="C:\Users\$env:USERNAME\AppData\Local\Android\Sdk"; .\gradlew.bat assembleRelease +``` + +### 清理和重新编译 +```bash +# 清理项目 +.\gradlew.bat clean + +# 重新编译 Debug 版本 +.\gradlew.bat assembleDebug + +# 重新编译 Release 版本 +.\gradlew.bat assembleRelease +``` + +## 注意事项 + +1. **JDK 版本**: 必须使用 Java 11 或更高版本 +2. **环境变量**: 每次编译前需要设置正确的 JAVA_HOME +3. **签名配置**: Release 版本需要配置签名文件 +4. **混淆规则**: Release 版本会应用 ProGuard 混淆规则 +5. **资源优化**: Release 版本会进行资源压缩和优化 + +## 文件位置 + +- **Debug APK**: `app\build\outputs\apk\debug\app-debug.apk` +- **Release APK**: `app\build\outputs\apk\release\app-release.apk` +- **Gradle Wrapper**: `gradlew.bat` +- **构建配置**: `app\build.gradle` + +## 成功标志 + +编译成功后会显示: +``` +BUILD SUCCESSFUL in [时间] +[数量] actionable tasks: [执行数量] executed, [跳过数量] up-to-date +``` + +--- + +**最后更新**: 2025年10月29日 +**适用版本**: Android Gradle Plugin 8.7.3 +**JDK 版本**: Android Studio JDK 21 diff --git a/androidExampleDemo/.gitignore b/androidExampleDemo/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/androidExampleDemo/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/androidExampleDemo/README.md b/androidExampleDemo/README.md new file mode 100644 index 0000000..cf4f8c0 --- /dev/null +++ b/androidExampleDemo/README.md @@ -0,0 +1,230 @@ +# Android Agent调用示例 (Java版本) + +这是一个使用Java开发的Android示例项目,演示如何调用情感分析Agent。 + +## 📋 项目结构 + +``` +androidExampleDemo/ +├── app/ +│ ├── src/ +│ │ └── main/ +│ │ ├── java/com/example/agentclient/ +│ │ │ ├── MainActivity.java +│ │ │ ├── models/ +│ │ │ │ ├── Agent.java +│ │ │ │ ├── AgentExecutionRequest.java +│ │ │ │ ├── ExecutionResponse.java +│ │ │ │ ├── ExecutionStatusResponse.java +│ │ │ │ └── TokenResponse.java +│ │ │ └── utils/ +│ │ │ └── ApiClient.java +│ │ ├── res/ +│ │ │ ├── layout/ +│ │ │ │ └── activity_main.xml +│ │ │ ├── values/ +│ │ │ │ └── strings.xml +│ │ │ └── xml/ +│ │ │ └── network_security_config.xml +│ │ └── AndroidManifest.xml +│ └── build.gradle +├── build.gradle +├── settings.gradle +├── gradle.properties +└── README.md +``` + +## 🚀 快速开始 + +### 1. 配置API地址 + +在 `app/src/main/java/com/example/agentclient/utils/ApiClient.java` 中修改: + +```java +private static final String BASE_URL = "http://your-server-ip:8037"; +``` + +### 2. 配置登录信息 + +在 `MainActivity.java` 中修改: + +```java +private static final String USERNAME = "admin"; +private static final String PASSWORD = "123456"; +private static final String AGENT_NAME = "情感分析Agent"; // Agent名称 +``` + +### 3. 运行项目 + +1. 使用Android Studio打开项目 +2. 同步Gradle依赖 +3. 运行到Android设备或模拟器 + +## 📱 功能特性 + +- ✅ 用户登录 +- ✅ 调用Agent API +- ✅ 实时显示执行状态 +- ✅ 显示Agent回复 +- ✅ 错误处理 + +## 🔧 依赖库 + +- Retrofit2:网络请求 +- OkHttp:HTTP客户端 +- Gson:JSON解析 +- AndroidX:Android支持库 + +## 📝 使用说明 + +1. **登录** + - 应用启动后会自动登录(使用配置的用户名和密码) + - 登录成功后可以开始使用Agent + +2. **发送消息** + - 在输入框中输入文本 + - 点击"发送"按钮 + - 等待Agent处理并返回结果 + +3. **查看结果** + - Agent的回复会显示在消息列表中 + - 执行状态会实时更新 + +## 🔑 API说明 + +### 登录API +``` +POST /api/v1/auth/login +Content-Type: application/x-www-form-urlencoded + +username=admin&password=123456 +``` + +### 执行Agent API +``` +POST /api/v1/executions +Authorization: Bearer +Content-Type: application/json + +{ + "agent_id": "agent-id", + "input_data": { + "query": "用户输入", + "USER_INPUT": "用户输入" + } +} +``` + +### 获取执行状态API +``` +GET /api/v1/executions/{execution_id}/status +Authorization: Bearer +``` + +### 获取执行结果API +``` +GET /api/v1/executions/{execution_id} +Authorization: Bearer +``` + +## 🎯 示例:调用情感分析Agent + +```java +// 1. 登录获取token +Call loginCall = ApiClient.getService().login("admin", "123456"); +loginCall.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + String token = "Bearer " + response.body().getAccessToken(); + + // 2. 通过名称查找Agent + Call> agentsCall = ApiClient.getService() + .getAgents("情感分析Agent", 100, token); + agentsCall.enqueue(new Callback>() { + @Override + public void onResponse(Call> call, Response> response) { + if (response.isSuccessful() && response.body() != null) { + String agentId = response.body().get(0).getId(); + + // 3. 执行Agent + Map inputData = new HashMap<>(); + inputData.put("query", "这个产品真的很棒!"); + inputData.put("USER_INPUT", "这个产品真的很棒!"); + + AgentExecutionRequest request = new AgentExecutionRequest(agentId, inputData); + Call execCall = ApiClient.getService() + .executeAgent(request, token); + execCall.enqueue(new Callback() { + @Override + public void onResponse(Call call, + Response response) { + if (response.isSuccessful()) { + String executionId = response.body().getId(); + // 4. 轮询获取结果(见MainActivity.pollExecutionResult方法) + } + } + + @Override + public void onFailure(Call call, Throwable t) { + // 处理错误 + } + }); + } + } + + @Override + public void onFailure(Call> call, Throwable t) { + // 处理错误 + } + }); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + // 处理错误 + } +}); +``` + +## 📦 构建要求 + +- Android Studio Hedgehog | 2023.1.1 或更高版本 +- JDK 17 或更高版本 +- Android SDK API 24 或更高版本 +- Gradle 8.0 或更高版本 + +## 💻 开发语言 + +- **Java 17** - 主要开发语言 +- 使用Retrofit进行网络请求 +- 使用Gson进行JSON解析 + +## 🔒 安全注意事项 + +1. **不要硬编码密码**:生产环境应该使用安全的认证方式 +2. **使用HTTPS**:生产环境必须使用HTTPS +3. **Token管理**:妥善保管和刷新Token +4. **网络安全配置**:Android 9+需要配置网络安全策略 + +## 🐛 故障排除 + +### 问题1:网络连接失败 +- 检查API地址是否正确 +- 检查设备网络连接 +- 检查服务器是否运行 + +### 问题2:登录失败 +- 检查用户名和密码是否正确 +- 检查服务器认证服务是否正常 + +### 问题3:Agent执行失败 +- 检查Agent ID是否正确 +- 检查Agent是否已发布 +- 查看服务器日志获取详细错误信息 + +## 📚 相关文档 + +- [Agent使用说明](../Agent使用说明.md) +- [API文档](../backend/API_DOCUMENTATION.md) diff --git a/androidExampleDemo/app/.gitignore b/androidExampleDemo/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/androidExampleDemo/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/androidExampleDemo/app/build.gradle b/androidExampleDemo/app/build.gradle new file mode 100644 index 0000000..5f96f69 --- /dev/null +++ b/androidExampleDemo/app/build.gradle @@ -0,0 +1,58 @@ +plugins { + alias(libs.plugins.android.application) +} + +android { + namespace 'com.example.agentclient' + compileSdk 34 + + defaultConfig { + applicationId "com.example.agentclient" + minSdk 26 + targetSdk 34 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } + + buildFeatures { + viewBinding true + } +} + +dependencies { + // Android Core + implementation 'androidx.core:core:1.12.0' + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'com.google.android.material:material:1.10.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + implementation 'androidx.lifecycle:lifecycle-runtime:2.6.2' + implementation 'androidx.activity:activity:1.8.1' + + // Retrofit & OkHttp + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.retrofit2:converter-gson:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.12.0' + implementation 'com.squareup.okhttp3:logging-interceptor:4.12.0' + + // Gson + implementation 'com.google.code.gson:gson:2.10.1' + + // Testing + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.5' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' +} diff --git a/androidExampleDemo/app/proguard-rules.pro b/androidExampleDemo/app/proguard-rules.pro new file mode 100644 index 0000000..695c294 --- /dev/null +++ b/androidExampleDemo/app/proguard-rules.pro @@ -0,0 +1,33 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. + +# Retrofit +-keepattributes Signature, InnerClasses, EnclosingMethod +-keepattributes RuntimeVisibleAnnotations, RuntimeVisibleParameterAnnotations +-keepclassmembers,allowshrinking,allowobfuscation interface * { + @retrofit2.http.* ; +} +-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement +-dontwarn javax.annotation.** +-dontwarn kotlin.Unit +-dontwarn retrofit2.KotlinExtensions +-dontwarn retrofit2.KotlinExtensions$* + +# OkHttp +-dontwarn okhttp3.** +-dontwarn okio.** +-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase + +# Gson +-keepattributes Signature +-keepattributes *Annotation* +-dontwarn sun.misc.** +-keep class com.google.gson.** { *; } +-keep class * implements com.google.gson.TypeAdapter +-keep class * implements com.google.gson.TypeAdapterFactory +-keep class * implements com.google.gson.JsonSerializer +-keep class * implements com.google.gson.JsonDeserializer + +# Keep model classes +-keep class com.example.agentclient.models.** { *; } diff --git a/androidExampleDemo/app/src/androidTest/java/com/example/obd/ExampleInstrumentedTest.java b/androidExampleDemo/app/src/androidTest/java/com/example/obd/ExampleInstrumentedTest.java new file mode 100644 index 0000000..ce711fe --- /dev/null +++ b/androidExampleDemo/app/src/androidTest/java/com/example/obd/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.example.obd; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + assertEquals("com.example.obd", appContext.getPackageName()); + } +} \ No newline at end of file diff --git a/androidExampleDemo/app/src/main/AndroidManifest.xml b/androidExampleDemo/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..f85b76b --- /dev/null +++ b/androidExampleDemo/app/src/main/AndroidManifest.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + diff --git a/androidExampleDemo/app/src/main/java/com/example/agentclient/MainActivity.java b/androidExampleDemo/app/src/main/java/com/example/agentclient/MainActivity.java new file mode 100644 index 0000000..9033e02 --- /dev/null +++ b/androidExampleDemo/app/src/main/java/com/example/agentclient/MainActivity.java @@ -0,0 +1,452 @@ +package com.example.agentclient; + +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ProgressBar; +import android.widget.ScrollView; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.appcompat.app.AppCompatActivity; + +import com.example.agentclient.models.*; +import com.example.agentclient.utils.ApiClient; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +/** + * 主Activity - 调用情感分析Agent示例 + * + * 功能: + * 1. 自动登录 + * 2. 通过Agent名称查找Agent ID + * 3. 发送文本进行情感分析 + * 4. 轮询获取执行结果 + */ +public class MainActivity extends AppCompatActivity { + + // TODO: 修改为你的配置 + private static final String USERNAME = "admin"; + private static final String PASSWORD = "123456"; + private static final String AGENT_NAME = "情感分析Agent"; // Agent名称 + + private TextView tvStatus; + private TextView tvMessages; + private EditText etInput; + private Button btnSend; + private ProgressBar progressBar; + private ScrollView scrollView; + + private String accessToken; + private String agentId; + private Handler mainHandler; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + // 先初始化 Handler,因为 initViews() 中会使用它 + mainHandler = new Handler(Looper.getMainLooper()); + + initViews(); + + // 启动时自动登录 + login(); + } + + private void initViews() { + tvStatus = findViewById(R.id.tvStatus); + tvMessages = findViewById(R.id.tvMessages); + etInput = findViewById(R.id.etInput); + btnSend = findViewById(R.id.btnSend); + progressBar = findViewById(R.id.progressBar); + scrollView = findViewById(R.id.scrollView); + + btnSend.setOnClickListener(v -> { + String input = etInput.getText().toString().trim(); + if (input.isEmpty()) { + Toast.makeText(this, "请输入要分析的文本", Toast.LENGTH_SHORT).show(); + return; + } + sendMessage(input); + }); + + updateStatus("准备登录..."); + addMessage("系统", "正在连接服务器..."); + } + + /** + * 登录 + */ + private void login() { + updateStatus("正在登录..."); + addMessage("系统", "正在登录..."); + + ApiClient.getService().login(USERNAME, PASSWORD).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + accessToken = "Bearer " + response.body().getAccessToken(); + updateStatus("登录成功"); + addMessage("系统", "登录成功!正在查找Agent..."); + + // 登录成功后查找Agent + findAgentByName(AGENT_NAME); + } else { + String errorMsg = "登录失败: " + response.code(); + try { + if (response.errorBody() != null) { + errorMsg += "\n" + response.errorBody().string(); + } + } catch (IOException e) { + e.printStackTrace(); + } + updateStatus("登录失败"); + addMessage("系统", errorMsg); + Toast.makeText(MainActivity.this, errorMsg, Toast.LENGTH_LONG).show(); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + String errorMsg = "登录失败: " + t.getMessage(); + updateStatus("登录失败"); + addMessage("系统", errorMsg); + Toast.makeText(MainActivity.this, errorMsg, Toast.LENGTH_LONG).show(); + } + }); + } + + /** + * 通过名称查找Agent + */ + private void findAgentByName(String agentName) { + updateStatus("正在查找Agent: " + agentName); + + ApiClient.getService().getAgents(agentName, 100, accessToken) + .enqueue(new Callback>() { + @Override + public void onResponse(Call> call, Response> response) { + if (response.isSuccessful() && response.body() != null) { + List agents = response.body(); + + // 精确匹配名称 + Agent foundAgent = null; + for (Agent agent : agents) { + if (agentName.equals(agent.getName())) { + foundAgent = agent; + break; + } + } + + if (foundAgent != null) { + agentId = foundAgent.getId(); + String status = foundAgent.getStatus(); + updateStatus("已找到Agent: " + agentName); + addMessage("系统", String.format( + "找到Agent: %s\nID: %s\n状态: %s", + foundAgent.getName(), + agentId, + status + )); + + if (!"published".equals(status) && !"running".equals(status)) { + addMessage("系统", "警告: Agent状态为 '" + status + "',可能无法执行"); + } + + addMessage("系统", "可以开始发送文本进行情感分析了!"); + } else { + updateStatus("未找到Agent"); + addMessage("系统", "未找到名称为 '" + agentName + "' 的Agent"); + if (!agents.isEmpty()) { + addMessage("系统", "找到以下相似的Agent:"); + for (int i = 0; i < Math.min(5, agents.size()); i++) { + Agent agent = agents.get(i); + addMessage("系统", String.format( + " - %s (ID: %s, 状态: %s)", + agent.getName(), + agent.getId(), + agent.getStatus() + )); + } + } + } + } else { + String errorMsg = "查找Agent失败: " + response.code(); + updateStatus("查找Agent失败"); + addMessage("系统", errorMsg); + } + } + + @Override + public void onFailure(Call> call, Throwable t) { + String errorMsg = "查找Agent失败: " + t.getMessage(); + updateStatus("查找Agent失败"); + addMessage("系统", errorMsg); + } + }); + } + + /** + * 发送消息到Agent + */ + private void sendMessage(String userInput) { + if (agentId == null || agentId.isEmpty()) { + Toast.makeText(this, "Agent未找到,请先查找Agent", Toast.LENGTH_SHORT).show(); + return; + } + + if (accessToken == null || accessToken.isEmpty()) { + Toast.makeText(this, "未登录,请先登录", Toast.LENGTH_SHORT).show(); + login(); + return; + } + + // 禁用发送按钮 + btnSend.setEnabled(false); + progressBar.setVisibility(View.VISIBLE); + + // 显示用户输入 + addMessage("我", userInput); + updateStatus("正在发送请求..."); + + // 构建请求数据 + Map inputData = new HashMap<>(); + inputData.put("query", userInput); + inputData.put("USER_INPUT", userInput); + + AgentExecutionRequest request = new AgentExecutionRequest(agentId, inputData); + + // 执行Agent + ApiClient.getService().executeAgent(request, accessToken) + .enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + ExecutionResponse execution = response.body(); + String executionId = execution.getId(); + updateStatus("请求已发送,执行ID: " + executionId); + addMessage("系统", "正在处理,请稍候..."); + + // 轮询获取结果 + pollExecutionResult(executionId); + } else { + String errorMsg = "执行失败: " + response.code(); + try { + if (response.errorBody() != null) { + errorMsg += "\n" + response.errorBody().string(); + } + } catch (IOException e) { + e.printStackTrace(); + } + updateStatus("执行失败"); + addMessage("系统", errorMsg); + btnSend.setEnabled(true); + progressBar.setVisibility(View.GONE); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + String errorMsg = "执行失败: " + t.getMessage(); + updateStatus("执行失败"); + addMessage("系统", errorMsg); + btnSend.setEnabled(true); + progressBar.setVisibility(View.GONE); + } + }); + } + + /** + * 轮询获取执行结果 + */ + private void pollExecutionResult(String executionId) { + ApiClient.getService().getExecutionStatus(executionId, accessToken) + .enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + ExecutionStatusResponse statusResponse = response.body(); + String status = statusResponse.getStatus(); + + // 显示当前节点信息 + if (statusResponse.getCurrentNode() != null) { + ExecutionStatusResponse.CurrentNode node = statusResponse.getCurrentNode(); + updateStatus(String.format("执行中: %s (%s)", + node.getNodeName() != null ? node.getNodeName() : node.getNodeId(), + node.getNodeType())); + } + + if ("completed".equals(status)) { + // 执行完成,获取结果 + getExecutionResult(executionId); + } else if ("failed".equals(status) || "error".equals(status)) { + // 执行失败 + String errorMsg = statusResponse.getError(); + if (errorMsg == null || errorMsg.isEmpty()) { + errorMsg = "执行失败"; + } + updateStatus("执行失败"); + addMessage("系统", "执行失败: " + errorMsg); + btnSend.setEnabled(true); + progressBar.setVisibility(View.GONE); + } else { + // 继续轮询 + mainHandler.postDelayed(() -> pollExecutionResult(executionId), 1000); + } + } else { + // 继续轮询 + mainHandler.postDelayed(() -> pollExecutionResult(executionId), 1000); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + // 继续轮询 + mainHandler.postDelayed(() -> pollExecutionResult(executionId), 1000); + } + }); + } + + /** + * 获取执行结果 + */ + private void getExecutionResult(String executionId) { + ApiClient.getService().getExecutionResult(executionId, accessToken) + .enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + btnSend.setEnabled(true); + progressBar.setVisibility(View.GONE); + + if (response.isSuccessful() && response.body() != null) { + ExecutionResponse execution = response.body(); + Object outputData = execution.getOutputData(); + + updateStatus("执行完成"); + + // 格式化输出 + String result = formatOutput(outputData); + addMessage("Agent", result); + } else { + String errorMsg = "获取结果失败: " + response.code(); + updateStatus("获取结果失败"); + addMessage("系统", errorMsg); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + btnSend.setEnabled(true); + progressBar.setVisibility(View.GONE); + String errorMsg = "获取结果失败: " + t.getMessage(); + updateStatus("获取结果失败"); + addMessage("系统", errorMsg); + } + }); + } + + /** + * 格式化输出数据 + */ + private String formatOutput(Object outputData) { + if (outputData == null) { + return "无输出"; + } + + if (outputData instanceof String) { + return (String) outputData; + } + + if (outputData instanceof Map) { + @SuppressWarnings("unchecked") + Map map = (Map) outputData; + + // 优先提取output字段 + if (map.containsKey("output") && map.get("output") instanceof String) { + return (String) map.get("output"); + } + + // 如果只有一个key,直接返回其值 + if (map.size() == 1) { + Object value = map.values().iterator().next(); + if (value instanceof String) { + return (String) value; + } + } + + // 否则转换为字符串 + StringBuilder sb = new StringBuilder(); + for (Map.Entry entry : map.entrySet()) { + if (!entry.getKey().startsWith("_")) { + sb.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n"); + } + } + return sb.toString().trim(); + } + + return outputData.toString(); + } + + /** + * 更新状态栏 + */ + private void updateStatus(String status) { + if (mainHandler == null) { + // 如果 Handler 还未初始化,直接在主线程更新 + if (tvStatus != null) { + tvStatus.setText("状态: " + status); + } + return; + } + mainHandler.post(() -> { + if (tvStatus != null) { + tvStatus.setText("状态: " + status); + } + }); + } + + /** + * 添加消息到消息列表 + */ + private void addMessage(String sender, String message) { + if (mainHandler == null) { + // 如果 Handler 还未初始化,直接在主线程更新 + if (tvMessages != null) { + String newMessage = String.format("[%s] %s\n\n", sender, message); + tvMessages.append(newMessage); + + // 自动滚动到底部 + if (scrollView != null) { + scrollView.post(() -> scrollView.fullScroll(View.FOCUS_DOWN)); + } + } + return; + } + mainHandler.post(() -> { + if (tvMessages != null) { + String currentText = tvMessages.getText().toString(); + String newMessage = String.format("[%s] %s\n\n", sender, message); + tvMessages.append(newMessage); + + // 自动滚动到底部 + if (scrollView != null) { + scrollView.post(() -> scrollView.fullScroll(View.FOCUS_DOWN)); + } + } + }); + } +} diff --git a/androidExampleDemo/app/src/main/java/com/example/agentclient/models/Agent.java b/androidExampleDemo/app/src/main/java/com/example/agentclient/models/Agent.java new file mode 100644 index 0000000..92fa7ce --- /dev/null +++ b/androidExampleDemo/app/src/main/java/com/example/agentclient/models/Agent.java @@ -0,0 +1,56 @@ +package com.example.agentclient.models; + +import com.google.gson.annotations.SerializedName; + +/** + * Agent模型 + */ +public class Agent { + private String id; + private String name; + private String description; + private String status; + + @SerializedName("created_at") + private String createdAt; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(String createdAt) { + this.createdAt = createdAt; + } +} diff --git a/androidExampleDemo/app/src/main/java/com/example/agentclient/models/AgentExecutionRequest.java b/androidExampleDemo/app/src/main/java/com/example/agentclient/models/AgentExecutionRequest.java new file mode 100644 index 0000000..e102a32 --- /dev/null +++ b/androidExampleDemo/app/src/main/java/com/example/agentclient/models/AgentExecutionRequest.java @@ -0,0 +1,36 @@ +package com.example.agentclient.models; + +import com.google.gson.annotations.SerializedName; +import java.util.Map; + +/** + * Agent执行请求模型 + */ +public class AgentExecutionRequest { + @SerializedName("agent_id") + private String agentId; + + @SerializedName("input_data") + private Map inputData; + + public AgentExecutionRequest(String agentId, Map inputData) { + this.agentId = agentId; + this.inputData = inputData; + } + + public String getAgentId() { + return agentId; + } + + public void setAgentId(String agentId) { + this.agentId = agentId; + } + + public Map getInputData() { + return inputData; + } + + public void setInputData(Map inputData) { + this.inputData = inputData; + } +} diff --git a/androidExampleDemo/app/src/main/java/com/example/agentclient/models/ExecutionResponse.java b/androidExampleDemo/app/src/main/java/com/example/agentclient/models/ExecutionResponse.java new file mode 100644 index 0000000..c24332e --- /dev/null +++ b/androidExampleDemo/app/src/main/java/com/example/agentclient/models/ExecutionResponse.java @@ -0,0 +1,84 @@ +package com.example.agentclient.models; + +import com.google.gson.annotations.SerializedName; +import java.util.Map; + +/** + * 执行记录响应模型 + */ +public class ExecutionResponse { + private String id; + + @SerializedName("agent_id") + private String agentId; + + @SerializedName("workflow_id") + private String workflowId; + + private String status; + + @SerializedName("input_data") + private Map inputData; + + @SerializedName("output_data") + private Object outputData; + + @SerializedName("created_at") + private String createdAt; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getAgentId() { + return agentId; + } + + public void setAgentId(String agentId) { + this.agentId = agentId; + } + + public String getWorkflowId() { + return workflowId; + } + + public void setWorkflowId(String workflowId) { + this.workflowId = workflowId; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public Map getInputData() { + return inputData; + } + + public void setInputData(Map inputData) { + this.inputData = inputData; + } + + public Object getOutputData() { + return outputData; + } + + public void setOutputData(Object outputData) { + this.outputData = outputData; + } + + public String getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(String createdAt) { + this.createdAt = createdAt; + } +} diff --git a/androidExampleDemo/app/src/main/java/com/example/agentclient/models/ExecutionStatusResponse.java b/androidExampleDemo/app/src/main/java/com/example/agentclient/models/ExecutionStatusResponse.java new file mode 100644 index 0000000..2a59291 --- /dev/null +++ b/androidExampleDemo/app/src/main/java/com/example/agentclient/models/ExecutionStatusResponse.java @@ -0,0 +1,86 @@ +package com.example.agentclient.models; + +import com.google.gson.annotations.SerializedName; + +/** + * 执行状态响应模型 + */ +public class ExecutionStatusResponse { + private String status; + private int progress = 0; + + @SerializedName("current_node") + private CurrentNode currentNode; + + private String error; + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public int getProgress() { + return progress; + } + + public void setProgress(int progress) { + this.progress = progress; + } + + public CurrentNode getCurrentNode() { + return currentNode; + } + + public void setCurrentNode(CurrentNode currentNode) { + this.currentNode = currentNode; + } + + public String getError() { + return error; + } + + public void setError(String error) { + this.error = error; + } + + /** + * 当前执行节点 + */ + public static class CurrentNode { + @SerializedName("node_id") + private String nodeId; + + @SerializedName("node_name") + private String nodeName; + + @SerializedName("node_type") + private String nodeType; + + public String getNodeId() { + return nodeId; + } + + public void setNodeId(String nodeId) { + this.nodeId = nodeId; + } + + public String getNodeName() { + return nodeName; + } + + public void setNodeName(String nodeName) { + this.nodeName = nodeName; + } + + public String getNodeType() { + return nodeType; + } + + public void setNodeType(String nodeType) { + this.nodeType = nodeType; + } + } +} diff --git a/androidExampleDemo/app/src/main/java/com/example/agentclient/models/TokenResponse.java b/androidExampleDemo/app/src/main/java/com/example/agentclient/models/TokenResponse.java new file mode 100644 index 0000000..146680b --- /dev/null +++ b/androidExampleDemo/app/src/main/java/com/example/agentclient/models/TokenResponse.java @@ -0,0 +1,30 @@ +package com.example.agentclient.models; + +import com.google.gson.annotations.SerializedName; + +/** + * Token响应模型 + */ +public class TokenResponse { + @SerializedName("access_token") + private String accessToken; + + @SerializedName("token_type") + private String tokenType = "bearer"; + + public String getAccessToken() { + return accessToken; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public String getTokenType() { + return tokenType; + } + + public void setTokenType(String tokenType) { + this.tokenType = tokenType; + } +} diff --git a/androidExampleDemo/app/src/main/java/com/example/agentclient/utils/ApiClient.java b/androidExampleDemo/app/src/main/java/com/example/agentclient/utils/ApiClient.java new file mode 100644 index 0000000..e98e908 --- /dev/null +++ b/androidExampleDemo/app/src/main/java/com/example/agentclient/utils/ApiClient.java @@ -0,0 +1,126 @@ +package com.example.agentclient.utils; + +import com.example.agentclient.models.*; +import okhttp3.OkHttpClient; +import okhttp3.logging.HttpLoggingInterceptor; +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; +import retrofit2.http.*; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * API客户端配置 + * + * 使用说明: + * 1. 修改 BASE_URL 为你的服务器地址 + * 2. 确保服务器允许跨域请求(CORS) + */ +public class ApiClient { + // TODO: 修改为你的服务器地址 + private static final String BASE_URL = "http://101.43.95.130:8037"; + + private static Retrofit retrofit; + private static AgentService agentService; + + static { + // 创建OkHttp客户端 + HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); + logging.setLevel(HttpLoggingInterceptor.Level.BODY); + + OkHttpClient okHttpClient = new OkHttpClient.Builder() + .connectTimeout(30, TimeUnit.SECONDS) + .readTimeout(60, TimeUnit.SECONDS) + .writeTimeout(60, TimeUnit.SECONDS) + .addInterceptor(logging) + .build(); + + // 创建Retrofit实例 + retrofit = new Retrofit.Builder() + .baseUrl(BASE_URL) + .client(okHttpClient) + .addConverterFactory(GsonConverterFactory.create()) + .build(); + + // 创建API服务 + agentService = retrofit.create(AgentService.class); + } + + public static AgentService getService() { + return agentService; + } + + /** + * Agent API接口 + */ + public interface AgentService { + /** + * 用户登录 + * + * @param username 用户名 + * @param password 密码 + * @return Token响应 + */ + @FormUrlEncoded + @POST("/api/v1/auth/login") + retrofit2.Call login( + @Field("username") String username, + @Field("password") String password + ); + + /** + * 获取Agent列表(支持搜索) + * + * @param search 搜索关键词(Agent名称) + * @param limit 返回数量限制 + * @param token 认证Token + * @return Agent列表 + */ + @GET("/api/v1/agents") + retrofit2.Call> getAgents( + @Query("search") String search, + @Query("limit") Integer limit, + @Header("Authorization") String token + ); + + /** + * 执行Agent + * + * @param request Agent执行请求 + * @param token 认证Token + * @return 执行记录 + */ + @POST("/api/v1/executions") + retrofit2.Call executeAgent( + @Body AgentExecutionRequest request, + @Header("Authorization") String token + ); + + /** + * 获取执行状态 + * + * @param executionId 执行ID + * @param token 认证Token + * @return 执行状态 + */ + @GET("/api/v1/executions/{execution_id}/status") + retrofit2.Call getExecutionStatus( + @Path("execution_id") String executionId, + @Header("Authorization") String token + ); + + /** + * 获取执行结果 + * + * @param executionId 执行ID + * @param token 认证Token + * @return 执行详情 + */ + @GET("/api/v1/executions/{execution_id}") + retrofit2.Call getExecutionResult( + @Path("execution_id") String executionId, + @Header("Authorization") String token + ); + } +} diff --git a/androidExampleDemo/app/src/main/res/drawable/button_outline.xml b/androidExampleDemo/app/src/main/res/drawable/button_outline.xml new file mode 100644 index 0000000..3007ba8 --- /dev/null +++ b/androidExampleDemo/app/src/main/res/drawable/button_outline.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/androidExampleDemo/app/src/main/res/drawable/button_primary.xml b/androidExampleDemo/app/src/main/res/drawable/button_primary.xml new file mode 100644 index 0000000..4c35579 --- /dev/null +++ b/androidExampleDemo/app/src/main/res/drawable/button_primary.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/androidExampleDemo/app/src/main/res/drawable/button_secondary.xml b/androidExampleDemo/app/src/main/res/drawable/button_secondary.xml new file mode 100644 index 0000000..0e9391f --- /dev/null +++ b/androidExampleDemo/app/src/main/res/drawable/button_secondary.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/androidExampleDemo/app/src/main/res/drawable/data_card_background.xml b/androidExampleDemo/app/src/main/res/drawable/data_card_background.xml new file mode 100644 index 0000000..104a453 --- /dev/null +++ b/androidExampleDemo/app/src/main/res/drawable/data_card_background.xml @@ -0,0 +1,14 @@ + + + + + + + diff --git a/androidExampleDemo/app/src/main/res/drawable/ic_launcher_background.xml b/androidExampleDemo/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/androidExampleDemo/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/androidExampleDemo/app/src/main/res/drawable/ic_launcher_foreground.xml b/androidExampleDemo/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/androidExampleDemo/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/androidExampleDemo/app/src/main/res/layout/activity_device_discovery.xml b/androidExampleDemo/app/src/main/res/layout/activity_device_discovery.xml new file mode 100644 index 0000000..d9c0b5d --- /dev/null +++ b/androidExampleDemo/app/src/main/res/layout/activity_device_discovery.xml @@ -0,0 +1,94 @@ + + + + + + + + + + + + +