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