diff --git a/exampleAiApp01/README.md b/exampleAiApp01/README.md
index 935743b..603a37e 100644
--- a/exampleAiApp01/README.md
+++ b/exampleAiApp01/README.md
@@ -1,17 +1,29 @@
-# 提示词智能优化 Android App
+# 提示词智能优化(本地模型)Android App
-调用提示词大师 3 号专家接口,实现智能提示词生成。
+基于《生成专业提示词代码逻辑分析》,本地调用 LLM 实现智能提示词生成,**不依赖远程 Flask API**。
+
+## 功能
+
+- **两阶段专家逻辑**:先做意图分析(技术/创意/分析/咨询),再按领域选择专家模板生成提示词
+- **本地模型调用**:通过 OpenAI 兼容接口(如 Ollama)本地推理
+- **配置灵活**:支持设置 API 地址和模型名称
## 环境
-- Android Studio(推荐使用 AGP 8.7+、Gradle 8.9)
+- Android Studio(推荐 AGP 8.7+、Gradle 8.9)
- minSdk 24,targetSdk 34
- Java 17
-## 接口
+## 使用前准备
-- 地址:`POST http://101.43.95.130:5002/api/open/expert-generate-3`
-- 参数:`input_text`(必填)、`temperature`、`max_tokens`、`timeout`、`uid`
+1. **安装 Ollama(或其他 OpenAI 兼容服务)**
+ - 官网:https://ollama.ai
+ - 运行:`ollama run qwen2:0.5b`(或其它兼容模型)
+
+2. **配置 API 地址**
+ - 模拟器:`http://10.0.2.2:11434/v1/`(10.0.2.2 指向宿主机 localhost)
+ - 真机:`http://<电脑局域网 IP>:11434/v1/`(如 `http://192.168.1.100:11434/v1/`)
+ - 在 App 内点击「设置」可修改地址和模型名
## 构建
@@ -24,6 +36,6 @@ cd exampleAiApp01
## 注意事项
-- 需配置网络权限,并允许明文 HTTP(101.43.95.130 为 HTTP)
-- 确保设备可访问 `101.43.95.130:5002`
+- 需配置网络权限,并允许明文 HTTP(`network_security_config.xml` 已配置)
+- 确保设备与 Ollama 所在电脑处于同一局域网(真机)或使用模拟器
- 如缺少启动图标,可在 Android Studio 中通过 `File > New > Image Asset` 生成
diff --git a/exampleAiApp01/app/src/main/AndroidManifest.xml b/exampleAiApp01/app/src/main/AndroidManifest.xml
index 6a1e966..2a49317 100644
--- a/exampleAiApp01/app/src/main/AndroidManifest.xml
+++ b/exampleAiApp01/app/src/main/AndroidManifest.xml
@@ -10,6 +10,10 @@
android:supportsRtl="true"
android:theme="@style/Theme.PromptOptimizer"
android:usesCleartextTraffic="true">
+
doGenerate());
btnCopy.setOnClickListener(v -> copyPrompt());
+ if (btnSettings != null) {
+ btnSettings.setOnClickListener(v -> startActivity(new Intent(this, SettingsActivity.class)));
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ llmClient.resetApi();
}
private void doGenerate() {
@@ -76,45 +93,27 @@ public class MainActivity extends AppCompatActivity {
btnGenerate.setEnabled(false);
cardResult.setVisibility(View.GONE);
- ExpertGenerate3Request request = new ExpertGenerate3Request(input);
- ExpertGenerate3Api api = RetrofitClient.getApi();
-
- api.generate(request).enqueue(new Callback() {
- @Override
- public void onResponse(Call call,
- Response response) {
- isSubmitting = false;
- progressBar.setVisibility(View.GONE);
- tvLoading.setVisibility(View.GONE);
- btnGenerate.setEnabled(true);
-
- if (response.isSuccessful() && response.body() != null) {
- ExpertGenerate3Response body = response.body();
- if (body.getCode() == 200 && body.getData() != null) {
- showResult(body);
- } else {
- String msg = body.getMessage() != null ? body.getMessage() : getString(R.string.error_network);
- Log.w(TAG, "API 业务错误: code=" + body.getCode() + ", message=" + msg);
- Toast.makeText(MainActivity.this, msg, Toast.LENGTH_LONG).show();
- }
- } else {
- String err = "HTTP " + response.code() + (response.errorBody() != null ? ": " + response.message() : "");
- Log.e(TAG, "API 请求失败: " + err);
- Toast.makeText(MainActivity.this, getString(R.string.error_network) + " " + err,
+ executor.execute(() -> {
+ try {
+ ExpertGenerate3Response result = promptGenerator.generate(input, 0.7f, 1000);
+ runOnUiThread(() -> {
+ isSubmitting = false;
+ progressBar.setVisibility(View.GONE);
+ tvLoading.setVisibility(View.GONE);
+ btnGenerate.setEnabled(true);
+ showResult(result);
+ });
+ } catch (Exception e) {
+ Log.e(TAG, "本地生成失败", e);
+ runOnUiThread(() -> {
+ isSubmitting = false;
+ progressBar.setVisibility(View.GONE);
+ tvLoading.setVisibility(View.GONE);
+ btnGenerate.setEnabled(true);
+ Toast.makeText(MainActivity.this,
+ getString(R.string.error_network) + ": " + e.getMessage(),
Toast.LENGTH_LONG).show();
- }
- }
-
- @Override
- public void onFailure(Call call, Throwable t) {
- isSubmitting = false;
- progressBar.setVisibility(View.GONE);
- tvLoading.setVisibility(View.GONE);
- btnGenerate.setEnabled(true);
- String detail = t != null ? t.getMessage() : "unknown";
- Log.e(TAG, "网络请求失败: " + detail, t);
- Toast.makeText(MainActivity.this, getString(R.string.error_network) + ": " + detail,
- Toast.LENGTH_LONG).show();
+ });
}
});
}
@@ -151,4 +150,10 @@ public class MainActivity extends AppCompatActivity {
Toast.makeText(this, R.string.toast_copied, Toast.LENGTH_SHORT).show();
}
}
+
+ @Override
+ protected void onDestroy() {
+ executor.shutdown();
+ super.onDestroy();
+ }
}
diff --git a/exampleAiApp01/app/src/main/java/com/example/promptoptimizer/SettingsActivity.java b/exampleAiApp01/app/src/main/java/com/example/promptoptimizer/SettingsActivity.java
new file mode 100644
index 0000000..e15a6a3
--- /dev/null
+++ b/exampleAiApp01/app/src/main/java/com/example/promptoptimizer/SettingsActivity.java
@@ -0,0 +1,49 @@
+package com.example.promptoptimizer;
+
+import android.os.Bundle;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.Toast;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.example.promptoptimizer.llm.LlmConfig;
+import com.google.android.material.textfield.TextInputEditText;
+
+/**
+ * 本地 LLM 配置
+ */
+public class SettingsActivity extends AppCompatActivity {
+
+ private TextInputEditText etBaseUrl;
+ private TextInputEditText etModel;
+ private Button btnSave;
+ private LlmConfig config;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_settings);
+
+ config = new LlmConfig(this);
+ etBaseUrl = findViewById(R.id.etBaseUrl);
+ etModel = findViewById(R.id.etModel);
+ btnSave = findViewById(R.id.btnSave);
+
+ etBaseUrl.setText(config.getBaseUrl());
+ etModel.setText(config.getModel());
+
+ btnSave.setOnClickListener(v -> {
+ String url = etBaseUrl.getText() != null ? etBaseUrl.getText().toString().trim() : "";
+ String model = etModel.getText() != null ? etModel.getText().toString().trim() : "";
+ if (url.isEmpty()) {
+ Toast.makeText(this, "请填写 API 地址", Toast.LENGTH_SHORT).show();
+ return;
+ }
+ config.setBaseUrl(url);
+ config.setModel(model.isEmpty() ? "qwen2:0.5b" : model);
+ Toast.makeText(this, "已保存", Toast.LENGTH_SHORT).show();
+ finish();
+ });
+ }
+}
diff --git a/exampleAiApp01/app/src/main/java/com/example/promptoptimizer/llm/ChatCompletionRequest.java b/exampleAiApp01/app/src/main/java/com/example/promptoptimizer/llm/ChatCompletionRequest.java
new file mode 100644
index 0000000..b461b54
--- /dev/null
+++ b/exampleAiApp01/app/src/main/java/com/example/promptoptimizer/llm/ChatCompletionRequest.java
@@ -0,0 +1,48 @@
+package com.example.promptoptimizer.llm;
+
+import com.google.gson.annotations.SerializedName;
+import java.util.List;
+
+/**
+ * OpenAI 兼容的 Chat Completions 请求
+ */
+public class ChatCompletionRequest {
+
+ @SerializedName("model")
+ private String model;
+
+ @SerializedName("messages")
+ private List messages;
+
+ @SerializedName("temperature")
+ private Float temperature;
+
+ @SerializedName("max_tokens")
+ private Integer maxTokens;
+
+ @SerializedName("stream")
+ private Boolean stream = false;
+
+ public ChatCompletionRequest(String model, List messages, Float temperature, Integer maxTokens) {
+ this.model = model;
+ this.messages = messages;
+ this.temperature = temperature;
+ this.maxTokens = maxTokens;
+ }
+
+ public static class Message {
+ @SerializedName("role")
+ private String role;
+
+ @SerializedName("content")
+ private String content;
+
+ public Message(String role, String content) {
+ this.role = role;
+ this.content = content;
+ }
+
+ public String getRole() { return role; }
+ public String getContent() { return content; }
+ }
+}
diff --git a/exampleAiApp01/app/src/main/java/com/example/promptoptimizer/llm/ChatCompletionResponse.java b/exampleAiApp01/app/src/main/java/com/example/promptoptimizer/llm/ChatCompletionResponse.java
new file mode 100644
index 0000000..f879715
--- /dev/null
+++ b/exampleAiApp01/app/src/main/java/com/example/promptoptimizer/llm/ChatCompletionResponse.java
@@ -0,0 +1,37 @@
+package com.example.promptoptimizer.llm;
+
+import com.google.gson.annotations.SerializedName;
+import java.util.List;
+
+/**
+ * OpenAI 兼容的 Chat Completions 响应
+ */
+public class ChatCompletionResponse {
+
+ @SerializedName("choices")
+ private List choices;
+
+ public String getContent() {
+ if (choices != null && !choices.isEmpty()) {
+ Choice c = choices.get(0);
+ if (c != null && c.getMessage() != null) {
+ return c.getMessage().getContent();
+ }
+ }
+ return null;
+ }
+
+ public static class Choice {
+ @SerializedName("message")
+ private Message message;
+
+ public Message getMessage() { return message; }
+ }
+
+ public static class Message {
+ @SerializedName("content")
+ private String content;
+
+ public String getContent() { return content; }
+ }
+}
diff --git a/exampleAiApp01/app/src/main/java/com/example/promptoptimizer/llm/LlmConfig.java b/exampleAiApp01/app/src/main/java/com/example/promptoptimizer/llm/LlmConfig.java
new file mode 100644
index 0000000..6effb9e
--- /dev/null
+++ b/exampleAiApp01/app/src/main/java/com/example/promptoptimizer/llm/LlmConfig.java
@@ -0,0 +1,41 @@
+package com.example.promptoptimizer.llm;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+/**
+ * 本地 LLM 配置
+ */
+public class LlmConfig {
+
+ private static final String PREFS = "llm_config";
+ private static final String KEY_BASE_URL = "base_url";
+ private static final String KEY_MODEL = "model";
+
+ // 默认:Ollama 本地。模拟器用 10.0.2.2 访问宿主机
+ private static final String DEFAULT_BASE_URL = "http://10.0.2.2:11434/v1/";
+ private static final String DEFAULT_MODEL = "qwen2:0.5b";
+
+ private final SharedPreferences prefs;
+
+ public LlmConfig(Context context) {
+ prefs = context.getApplicationContext().getSharedPreferences(PREFS, Context.MODE_PRIVATE);
+ }
+
+ public String getBaseUrl() {
+ return prefs.getString(KEY_BASE_URL, DEFAULT_BASE_URL);
+ }
+
+ public void setBaseUrl(String url) {
+ if (url != null && !url.endsWith("/")) url = url + "/";
+ prefs.edit().putString(KEY_BASE_URL, url).apply();
+ }
+
+ public String getModel() {
+ return prefs.getString(KEY_MODEL, DEFAULT_MODEL);
+ }
+
+ public void setModel(String model) {
+ prefs.edit().putString(KEY_MODEL, model != null ? model : DEFAULT_MODEL).apply();
+ }
+}
diff --git a/exampleAiApp01/app/src/main/java/com/example/promptoptimizer/llm/LocalLlmApi.java b/exampleAiApp01/app/src/main/java/com/example/promptoptimizer/llm/LocalLlmApi.java
new file mode 100644
index 0000000..b3209c1
--- /dev/null
+++ b/exampleAiApp01/app/src/main/java/com/example/promptoptimizer/llm/LocalLlmApi.java
@@ -0,0 +1,14 @@
+package com.example.promptoptimizer.llm;
+
+import retrofit2.Call;
+import retrofit2.http.Body;
+import retrofit2.http.POST;
+
+/**
+ * OpenAI 兼容的 LLM API(Ollama / 本地部署等)
+ */
+public interface LocalLlmApi {
+
+ @POST("chat/completions")
+ Call chat(@Body ChatCompletionRequest request);
+}
diff --git a/exampleAiApp01/app/src/main/java/com/example/promptoptimizer/llm/LocalLlmClient.java b/exampleAiApp01/app/src/main/java/com/example/promptoptimizer/llm/LocalLlmClient.java
new file mode 100644
index 0000000..5b068e2
--- /dev/null
+++ b/exampleAiApp01/app/src/main/java/com/example/promptoptimizer/llm/LocalLlmClient.java
@@ -0,0 +1,52 @@
+package com.example.promptoptimizer.llm;
+
+import android.content.Context;
+
+import java.util.concurrent.TimeUnit;
+
+import okhttp3.OkHttpClient;
+import okhttp3.logging.HttpLoggingInterceptor;
+import retrofit2.Retrofit;
+import retrofit2.converter.gson.GsonConverterFactory;
+
+/**
+ * 本地 LLM 客户端(支持 Ollama 等 OpenAI 兼容 API)
+ */
+public class LocalLlmClient {
+
+ private final LlmConfig config;
+ private LocalLlmApi api;
+
+ public LocalLlmClient(Context context) {
+ config = new LlmConfig(context);
+ }
+
+ public LocalLlmApi getApi() {
+ if (api == null) {
+ HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
+ interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
+
+ OkHttpClient client = new OkHttpClient.Builder()
+ .connectTimeout(120, TimeUnit.SECONDS)
+ .readTimeout(120, TimeUnit.SECONDS)
+ .writeTimeout(120, TimeUnit.SECONDS)
+ .addInterceptor(interceptor)
+ .build();
+
+ Retrofit retrofit = new Retrofit.Builder()
+ .baseUrl(config.getBaseUrl())
+ .client(client)
+ .addConverterFactory(GsonConverterFactory.create())
+ .build();
+ api = retrofit.create(LocalLlmApi.class);
+ }
+ return api;
+ }
+
+ public void resetApi() {
+ api = null;
+ }
+
+ public String getBaseUrl() { return config.getBaseUrl(); }
+ public String getModel() { return config.getModel(); }
+}
diff --git a/exampleAiApp01/app/src/main/java/com/example/promptoptimizer/llm/PromptGenerator.java b/exampleAiApp01/app/src/main/java/com/example/promptoptimizer/llm/PromptGenerator.java
new file mode 100644
index 0000000..41a34bb
--- /dev/null
+++ b/exampleAiApp01/app/src/main/java/com/example/promptoptimizer/llm/PromptGenerator.java
@@ -0,0 +1,186 @@
+package com.example.promptoptimizer.llm;
+
+import android.util.Log;
+
+import com.example.promptoptimizer.model.ExpertGenerate3Response;
+import com.google.gson.Gson;
+import com.google.gson.JsonSyntaxException;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import retrofit2.Response;
+
+/**
+ * 两阶段专家提示词生成(参考《生成专业提示词代码逻辑分析》)
+ * 第一阶段:意图分析 → 第二阶段:领域专家生成
+ */
+public class PromptGenerator {
+
+ private static final String TAG = "PromptGenerator";
+
+ private static final String INTENT_PROMPT = "你是一位资深的意图分析专家,请分析用户输入的意图和需求。\n\n" +
+ "你必须严格按照以下JSON格式返回,不要添加任何其他内容:\n" +
+ "{\n" +
+ " \"core_intent\": \"技术\",\n" +
+ " \"domain\": \"具体专业领域\",\n" +
+ " \"key_requirements\": [\"需求1\", \"需求2\"],\n" +
+ " \"expected_output\": \"期望输出的具体形式\",\n" +
+ " \"constraints\": [\"约束1\", \"约束2\"],\n" +
+ " \"keywords\": [\"关键词1\", \"关键词2\"]\n" +
+ "}\n\n" +
+ "注意:\n" +
+ "1. 严格遵守JSON格式\n" +
+ "2. core_intent必须是以下之一:技术、创意、分析、咨询\n" +
+ "3. 数组至少包含1个元素\n" +
+ "4. 所有字段都必须存在\n" +
+ "5. 不要包含注释\n" +
+ "6. 不要添加任何额外的文本";
+
+ private static final Map EXPERT_TEMPLATES = new HashMap<>();
+ static {
+ EXPERT_TEMPLATES.put("技术", "你是一位专业的技术领域提示工程师。基于以下意图分析,生成一个专业的技术任务提示词:\n\n意图分析:\n{analysis}\n\n请生成的提示词包含:\n1. 明确的技术背景和上下文\n2. 具体的技术要求和规范\n3. 性能和质量标准\n4. 技术约束条件\n5. 预期交付成果\n6. 评估标准\n\n使用专业技术术语,确保提示词的可执行性和可验证性。");
+ EXPERT_TEMPLATES.put("创意", "你是一位专业的创意领域提示工程师。基于以下意图分析,生成一个创意设计提示词:\n\n意图分析:\n{analysis}\n\n请生成的提示词包含:\n1. 创意方向和灵感来源\n2. 风格和氛围要求\n3. 目标受众定义\n4. 设计元素规范\n5. 创意表现形式\n6. 评估标准\n\n使用专业创意术语,确保提示词的创新性和可执行性。");
+ EXPERT_TEMPLATES.put("分析", "你是一位专业的数据分析提示工程师。基于以下意图分析,生成一个数据分析提示词:\n\n意图分析:\n{analysis}\n\n请生成的提示词包含:\n1. 分析目标和范围\n2. 数据要求和规范\n3. 分析方法和工具\n4. 输出格式要求\n5. 关键指标定义\n6. 质量控制标准\n\n使用专业分析术语,确保提示词的科学性和可操作性。");
+ EXPERT_TEMPLATES.put("咨询", "你是一位专业的咨询领域提示工程师。基于以下意图分析,生成一个咨询服务提示词:\n\n意图分析:\n{analysis}\n\n请生成的提示词包含:\n1. 咨询问题界定\n2. 背景信息要求\n3. 分析框架设定\n4. 建议输出格式\n5. 实施考虑因素\n6. 效果评估标准\n\n使用专业咨询术语,确保提示词的专业性和实用性。");
+ }
+
+ private static final String FALLBACK_TEMPLATE = "你是一位专业的通用领域提示工程师。基于以下意图分析,生成一个专业的提示词:\n\n意图分析:\n{analysis}\n\n请生成的提示词包含:\n1. 明确的目标定义\n2. 具体要求和规范\n3. 质量标准\n4. 约束条件\n5. 预期输出\n6. 评估标准\n\n确保提示词的清晰性和可执行性。";
+
+ private final LocalLlmClient llmClient;
+ private final Gson gson = new Gson();
+
+ public PromptGenerator(LocalLlmClient llmClient) {
+ this.llmClient = llmClient;
+ }
+
+ /**
+ * 同步执行两阶段生成(需在后台线程调用)
+ */
+ public ExpertGenerate3Response generate(String userInput, float temperature, int maxTokens) throws Exception {
+ String model = llmClient.getModel();
+ LocalLlmApi api = llmClient.getApi();
+
+ // 第一阶段:意图分析
+ List intentMessages = Arrays.asList(
+ new ChatCompletionRequest.Message("system", INTENT_PROMPT),
+ new ChatCompletionRequest.Message("user", userInput)
+ );
+ ChatCompletionRequest intentReq = new ChatCompletionRequest(model, intentMessages, 0.1f, 1024);
+ Response intentResp = api.chat(intentReq).execute();
+ if (!intentResp.isSuccessful() || intentResp.body() == null) {
+ throw new Exception("意图分析请求失败: " + (intentResp.errorBody() != null ? intentResp.errorBody().string() : intentResp.code()));
+ }
+ String intentRaw = intentResp.body().getContent();
+ if (intentRaw == null || intentRaw.isEmpty()) {
+ throw new Exception("意图分析返回为空");
+ }
+ intentRaw = intentRaw.replace("```json", "").replace("```", "").trim();
+
+ IntentAnalysis intent = parseIntent(intentRaw);
+ if (intent == null) {
+ throw new Exception("意图分析解析失败");
+ }
+
+ // 第二阶段:领域专家生成
+ String template = EXPERT_TEMPLATES.getOrDefault(intent.coreIntent, FALLBACK_TEMPLATE);
+ String analysisStr = gson.toJson(intent.toMap());
+ String expertPrompt = template.replace("{analysis}", analysisStr);
+
+ List expertMessages = Arrays.asList(
+ new ChatCompletionRequest.Message("system", expertPrompt),
+ new ChatCompletionRequest.Message("user", userInput)
+ );
+ ChatCompletionRequest expertReq = new ChatCompletionRequest(model, expertMessages, temperature, maxTokens);
+ Response expertResp = api.chat(expertReq).execute();
+ if (!expertResp.isSuccessful() || expertResp.body() == null) {
+ throw new Exception("提示词生成请求失败: " + expertResp.code());
+ }
+ String generated = expertResp.body().getContent();
+ if (generated == null || generated.isEmpty()) {
+ throw new Exception("生成结果为空");
+ }
+
+ ExpertGenerate3Response result = new ExpertGenerate3Response();
+ result.setCode(200);
+ result.setMessage("success");
+ ExpertGenerate3Response.ResponseData data = new ExpertGenerate3Response.ResponseData();
+ data.setIntentAnalysis(intent.toIntentAnalysis());
+ data.setGeneratedPrompt(generated.trim());
+ result.setData(data);
+ return result;
+ }
+
+ @SuppressWarnings("unchecked")
+ private IntentAnalysis parseIntent(String json) {
+ try {
+ Map map = gson.fromJson(json, Map.class);
+ if (map == null) return null;
+
+ IntentAnalysis ia = new IntentAnalysis();
+ ia.coreIntent = safeStr(map.get("core_intent"), "技术");
+ if (!Arrays.asList("技术", "创意", "分析", "咨询").contains(ia.coreIntent)) {
+ ia.coreIntent = "技术";
+ }
+ ia.domain = safeStr(map.get("domain"), "未指定");
+ ia.expectedOutput = safeStr(map.get("expected_output"), "未指定");
+ ia.keyRequirements = toStrList(map.get("key_requirements"));
+ ia.constraints = toStrList(map.get("constraints"));
+ ia.keywords = toStrList(map.get("keywords"));
+ return ia;
+ } catch (JsonSyntaxException e) {
+ Log.e(TAG, "JSON 解析失败: " + e.getMessage());
+ return null;
+ }
+ }
+
+ private static String safeStr(Object o, String def) {
+ return o != null ? String.valueOf(o).trim() : def;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static String[] toStrList(Object o) {
+ if (o instanceof List) {
+ List> list = (List>) o;
+ List out = new ArrayList<>();
+ for (Object item : list) {
+ out.add(String.valueOf(item));
+ }
+ return out.isEmpty() ? new String[]{"未指定"} : out.toArray(new String[0]);
+ }
+ return new String[]{"未指定"};
+ }
+
+ private static class IntentAnalysis {
+ String coreIntent;
+ String domain;
+ String expectedOutput;
+ String[] keyRequirements;
+ String[] constraints;
+ String[] keywords;
+
+ Map toMap() {
+ Map m = new HashMap<>();
+ m.put("core_intent", coreIntent);
+ m.put("domain", domain);
+ m.put("expected_output", expectedOutput);
+ m.put("key_requirements", keyRequirements != null ? Arrays.asList(keyRequirements) : new ArrayList<>());
+ m.put("constraints", constraints != null ? Arrays.asList(constraints) : new ArrayList<>());
+ m.put("keywords", keywords != null ? Arrays.asList(keywords) : new ArrayList<>());
+ return m;
+ }
+
+ ExpertGenerate3Response.IntentAnalysis toIntentAnalysis() {
+ ExpertGenerate3Response.IntentAnalysis out = new ExpertGenerate3Response.IntentAnalysis();
+ out.setCoreIntent(coreIntent);
+ out.setDomain(domain);
+ out.setExpectedOutput(expectedOutput);
+ out.setKeyRequirements(keyRequirements != null ? keyRequirements : new String[]{"未指定"});
+ out.setConstraints(constraints != null ? constraints : new String[]{"未指定"});
+ return out;
+ }
+ }
+}
diff --git a/exampleAiApp01/app/src/main/res/layout/activity_main.xml b/exampleAiApp01/app/src/main/res/layout/activity_main.xml
index cb597d3..36af6ee 100644
--- a/exampleAiApp01/app/src/main/res/layout/activity_main.xml
+++ b/exampleAiApp01/app/src/main/res/layout/activity_main.xml
@@ -12,14 +12,27 @@
android:orientation="vertical"
android:padding="16dp">
-
+ android:orientation="horizontal"
+ android:gravity="center_vertical"
+ android:layout_marginBottom="16dp">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/exampleAiApp01/gradle/wrapper/gradle-wrapper.jar b/exampleAiApp01/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..f6b961f
Binary files /dev/null and b/exampleAiApp01/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/exampleAiApp01/gradlew b/exampleAiApp01/gradlew
new file mode 100755
index 0000000..cccdd3d
--- /dev/null
+++ b/exampleAiApp01/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/logs/gunicorn_error.log b/logs/gunicorn_error.log
index d1fb6ad..ab37d0e 100644
--- a/logs/gunicorn_error.log
+++ b/logs/gunicorn_error.log
@@ -10661,3 +10661,4 @@ werkzeug.routing.exceptions.BuildError: Could not build url for endpoint 'expert
[2026-03-01 23:45:03 +0800] [28566] [INFO] Booting worker with pid: 28566
[2026-03-01 23:45:03 +0800] [28566] [INFO] 工作进程 28566 已启动
[2026-03-01 23:45:03 +0800] [28566] [INFO] 工作进程 28566 初始化完成
+[2026-03-02 00:11:37 +0800] [28391] [INFO] Handling signal: winch