aaa
Some checks failed
Flask 提示词大师 - CI/CD 流水线 / 代码质量检查 (push) Has been cancelled
Flask 提示词大师 - CI/CD 流水线 / 单元测试 (push) Has been cancelled
Flask 提示词大师 - CI/CD 流水线 / 集成测试 (push) Has been cancelled
Flask 提示词大师 - CI/CD 流水线 / 构建Docker镜像 (push) Has been cancelled
Flask 提示词大师 - CI/CD 流水线 / 部署到测试环境 (push) Has been cancelled
Flask 提示词大师 - CI/CD 流水线 / 部署到生产环境 (push) Has been cancelled
Flask 提示词大师 - CI/CD 流水线 / 部署监控系统 (push) Has been cancelled

This commit is contained in:
rjb
2026-03-02 00:12:24 +08:00
parent 99fb05cb5c
commit c27fc21c1a
15 changed files with 759 additions and 60 deletions

View File

@@ -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 24targetSdk 34 - minSdk 24targetSdk 34
- Java 17 - Java 17
## 接口 ## 使用前准备
- 地址:`POST http://101.43.95.130:5002/api/open/expert-generate-3` 1. **安装 Ollama或其他 OpenAI 兼容服务)**
- 参数:`input_text`(必填)、`temperature``max_tokens``timeout``uid` - 官网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
## 注意事项 ## 注意事项
- 需配置网络权限,并允许明文 HTTP101.43.95.130 为 HTTP - 需配置网络权限,并允许明文 HTTP`network_security_config.xml` 已配置
- 确保设备可访问 `101.43.95.130:5002` - 确保设备与 Ollama 所在电脑处于同一局域网(真机)或使用模拟器
- 如缺少启动图标,可在 Android Studio 中通过 `File > New > Image Asset` 生成 - 如缺少启动图标,可在 Android Studio 中通过 `File > New > Image Asset` 生成

View File

@@ -10,6 +10,10 @@
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.PromptOptimizer" android:theme="@style/Theme.PromptOptimizer"
android:usesCleartextTraffic="true"> android:usesCleartextTraffic="true">
<activity
android:name=".SettingsActivity"
android:exported="false"
android:theme="@style/Theme.PromptOptimizer" />
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"

View File

@@ -3,6 +3,7 @@ package com.example.promptoptimizer;
import android.content.ClipData; import android.content.ClipData;
import android.content.ClipboardManager; import android.content.ClipboardManager;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
@@ -13,20 +14,19 @@ import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import com.example.promptoptimizer.api.ExpertGenerate3Api; import com.example.promptoptimizer.llm.LocalLlmClient;
import com.example.promptoptimizer.model.ExpertGenerate3Request; import com.example.promptoptimizer.llm.PromptGenerator;
import com.example.promptoptimizer.model.ExpertGenerate3Response; import com.example.promptoptimizer.model.ExpertGenerate3Response;
import com.google.android.material.button.MaterialButton; import com.google.android.material.button.MaterialButton;
import com.google.android.material.card.MaterialCardView; import com.google.android.material.card.MaterialCardView;
import java.util.Arrays; import java.util.Arrays;
import java.util.concurrent.ExecutorService;
import retrofit2.Call; import java.util.concurrent.Executors;
import retrofit2.Callback;
import retrofit2.Response;
/** /**
* 提示词智能优化 - 主界面 * 提示词智能优化 - 本地模型调用(两阶段专家逻辑)
* 参考《生成专业提示词代码逻辑分析》
*/ */
public class MainActivity extends AppCompatActivity { public class MainActivity extends AppCompatActivity {
@@ -34,6 +34,7 @@ public class MainActivity extends AppCompatActivity {
private EditText etInput; private EditText etInput;
private MaterialButton btnGenerate; private MaterialButton btnGenerate;
private MaterialButton btnSettings;
private ProgressBar progressBar; private ProgressBar progressBar;
private TextView tvLoading; private TextView tvLoading;
private MaterialCardView cardResult; private MaterialCardView cardResult;
@@ -42,14 +43,21 @@ public class MainActivity extends AppCompatActivity {
private MaterialButton btnCopy; private MaterialButton btnCopy;
private boolean isSubmitting = false; private boolean isSubmitting = false;
private LocalLlmClient llmClient;
private PromptGenerator promptGenerator;
private final ExecutorService executor = Executors.newSingleThreadExecutor();
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); setContentView(R.layout.activity_main);
llmClient = new LocalLlmClient(this);
promptGenerator = new PromptGenerator(llmClient);
etInput = findViewById(R.id.etInput); etInput = findViewById(R.id.etInput);
btnGenerate = findViewById(R.id.btnGenerate); btnGenerate = findViewById(R.id.btnGenerate);
btnSettings = findViewById(R.id.btnSettings);
progressBar = findViewById(R.id.progressBar); progressBar = findViewById(R.id.progressBar);
tvLoading = findViewById(R.id.tvLoading); tvLoading = findViewById(R.id.tvLoading);
cardResult = findViewById(R.id.cardResult); cardResult = findViewById(R.id.cardResult);
@@ -59,6 +67,15 @@ public class MainActivity extends AppCompatActivity {
btnGenerate.setOnClickListener(v -> doGenerate()); btnGenerate.setOnClickListener(v -> doGenerate());
btnCopy.setOnClickListener(v -> copyPrompt()); 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() { private void doGenerate() {
@@ -76,45 +93,27 @@ public class MainActivity extends AppCompatActivity {
btnGenerate.setEnabled(false); btnGenerate.setEnabled(false);
cardResult.setVisibility(View.GONE); cardResult.setVisibility(View.GONE);
ExpertGenerate3Request request = new ExpertGenerate3Request(input); executor.execute(() -> {
ExpertGenerate3Api api = RetrofitClient.getApi(); try {
ExpertGenerate3Response result = promptGenerator.generate(input, 0.7f, 1000);
api.generate(request).enqueue(new Callback<ExpertGenerate3Response>() { runOnUiThread(() -> {
@Override
public void onResponse(Call<ExpertGenerate3Response> call,
Response<ExpertGenerate3Response> response) {
isSubmitting = false; isSubmitting = false;
progressBar.setVisibility(View.GONE); progressBar.setVisibility(View.GONE);
tvLoading.setVisibility(View.GONE); tvLoading.setVisibility(View.GONE);
btnGenerate.setEnabled(true); btnGenerate.setEnabled(true);
showResult(result);
if (response.isSuccessful() && response.body() != null) { });
ExpertGenerate3Response body = response.body(); } catch (Exception e) {
if (body.getCode() == 200 && body.getData() != null) { Log.e(TAG, "本地生成失败", e);
showResult(body); runOnUiThread(() -> {
} 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,
Toast.LENGTH_LONG).show();
}
}
@Override
public void onFailure(Call<ExpertGenerate3Response> call, Throwable t) {
isSubmitting = false; isSubmitting = false;
progressBar.setVisibility(View.GONE); progressBar.setVisibility(View.GONE);
tvLoading.setVisibility(View.GONE); tvLoading.setVisibility(View.GONE);
btnGenerate.setEnabled(true); btnGenerate.setEnabled(true);
String detail = t != null ? t.getMessage() : "unknown"; Toast.makeText(MainActivity.this,
Log.e(TAG, "网络请求失败: " + detail, t); getString(R.string.error_network) + ": " + e.getMessage(),
Toast.makeText(MainActivity.this, getString(R.string.error_network) + ": " + detail,
Toast.LENGTH_LONG).show(); Toast.LENGTH_LONG).show();
});
} }
}); });
} }
@@ -151,4 +150,10 @@ public class MainActivity extends AppCompatActivity {
Toast.makeText(this, R.string.toast_copied, Toast.LENGTH_SHORT).show(); Toast.makeText(this, R.string.toast_copied, Toast.LENGTH_SHORT).show();
} }
} }
@Override
protected void onDestroy() {
executor.shutdown();
super.onDestroy();
}
} }

View File

@@ -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();
});
}
}

View File

@@ -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<Message> messages;
@SerializedName("temperature")
private Float temperature;
@SerializedName("max_tokens")
private Integer maxTokens;
@SerializedName("stream")
private Boolean stream = false;
public ChatCompletionRequest(String model, List<Message> 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; }
}
}

View File

@@ -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<Choice> 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; }
}
}

View File

@@ -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();
}
}

View File

@@ -0,0 +1,14 @@
package com.example.promptoptimizer.llm;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.POST;
/**
* OpenAI 兼容的 LLM APIOllama / 本地部署等)
*/
public interface LocalLlmApi {
@POST("chat/completions")
Call<ChatCompletionResponse> chat(@Body ChatCompletionRequest request);
}

View File

@@ -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(); }
}

View File

@@ -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<String, String> 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<ChatCompletionRequest.Message> intentMessages = Arrays.asList(
new ChatCompletionRequest.Message("system", INTENT_PROMPT),
new ChatCompletionRequest.Message("user", userInput)
);
ChatCompletionRequest intentReq = new ChatCompletionRequest(model, intentMessages, 0.1f, 1024);
Response<ChatCompletionResponse> 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<ChatCompletionRequest.Message> expertMessages = Arrays.asList(
new ChatCompletionRequest.Message("system", expertPrompt),
new ChatCompletionRequest.Message("user", userInput)
);
ChatCompletionRequest expertReq = new ChatCompletionRequest(model, expertMessages, temperature, maxTokens);
Response<ChatCompletionResponse> 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<String, Object> 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<String> 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<String, Object> toMap() {
Map<String, Object> 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;
}
}
}

View File

@@ -12,14 +12,27 @@
android:orientation="vertical" android:orientation="vertical"
android:padding="16dp"> android:padding="16dp">
<TextView <LinearLayout
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="提示词智能优化" android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginBottom="16dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="提示词智能优化(本地)"
android:textSize="22sp" android:textSize="22sp"
android:textStyle="bold" android:textStyle="bold"
android:textColor="#1E3A8A" android:textColor="#1E3A8A" />
android:layout_marginBottom="16dp" /> <com.google.android.material.button.MaterialButton
android:id="@+id/btnSettings"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="设置" />
</LinearLayout>
<com.google.android.material.card.MaterialCardView <com.google.android.material.card.MaterialCardView
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="本地模型配置"
android:textSize="20sp"
android:textStyle="bold"
android:layout_marginBottom="24dp" />
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="API 地址Ollama 默认 http://10.0.2.2:11434/v1/"
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
android:layout_marginBottom="16dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etBaseUrl"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textUri" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="模型名称(如 qwen2:0.5b、llama2"
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
android:layout_marginBottom="24dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etModel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/btnSave"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="保存" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="说明:\n1. 请先在电脑安装 Ollama运行 ollama run qwen2:0.5b\n2. 模拟器访问宿主机用 10.0.2.2,真机用电脑局域网 IP\n3. 例如http://192.168.1.100:11434/v1/"
android:textSize="12sp"
android:textColor="#666666"
android:lineSpacingExtra="4dp" />
</LinearLayout>
</ScrollView>

Binary file not shown.

172
exampleAiApp01/gradlew vendored Executable file
View File

@@ -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" "$@"

View File

@@ -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] 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-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