菜品规划
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-04 22:42:44 +08:00
parent 4ed7b82139
commit 4dc47ed186
10 changed files with 543 additions and 0 deletions

View File

@@ -26,6 +26,10 @@
android:name=".HistoryActivity"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name=".MealPlanningActivity"
android:exported="false"
android:screenOrientation="portrait" />
<provider
android:name="androidx.core.content.FileProvider"

View File

@@ -48,6 +48,9 @@ public class MainActivity extends AppCompatActivity {
if (binding.btnHistory != null) {
binding.btnHistory.setOnClickListener(v -> openHistory());
}
if (binding.btnMealPlanning != null) {
binding.btnMealPlanning.setOnClickListener(v -> startActivity(new Intent(this, MealPlanningActivity.class)));
}
observeViewModel();
showApiKeyHintIfNeeded();
}

View File

@@ -0,0 +1,141 @@
package com.ruilaizi.example;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ProgressBar;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModelProvider;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.textfield.TextInputEditText;
import com.ruilaizi.example.data.meal.MealPlanGenerator;
import com.ruilaizi.example.databinding.ActivityMealPlanningBinding;
import com.ruilaizi.example.ui.MealPlanningViewModel;
/**
* 智能饭菜规划:本地调用 DeepSeek 生成饭菜清单,与 example 提示词优化同构。
*/
public class MealPlanningActivity extends AppCompatActivity {
private ActivityMealPlanningBinding binding;
private MealPlanningViewModel viewModel;
private static final String[] BUDGET_VALUES = {"50", "100", "150", "200", "300", "500", "1000"};
private static final String[] DINER_VALUES = {"1", "2", "3", "4", "5", "6", "8", "10"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMealPlanningBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
viewModel = new ViewModelProvider(this, new ViewModelProvider.AndroidViewModelFactory(getApplication())).get(MealPlanningViewModel.class);
setupSpinners();
binding.btnBack.setOnClickListener(v -> finish());
binding.btnGenerate.setOnClickListener(v -> doGenerate());
binding.btnCopy.setOnClickListener(v -> copyResult());
observeViewModel();
}
private void setupSpinners() {
ArrayAdapter<CharSequence> regionAdapter = ArrayAdapter.createFromResource(this, R.array.meal_region_types, android.R.layout.simple_spinner_item);
regionAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
binding.spinnerRegion.setAdapter(regionAdapter);
ArrayAdapter<CharSequence> dinerAdapter = ArrayAdapter.createFromResource(this, R.array.meal_diner_counts, android.R.layout.simple_spinner_item);
dinerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
binding.spinnerDiner.setAdapter(dinerAdapter);
binding.spinnerDiner.setSelection(1); // 2人
ArrayAdapter<CharSequence> mealTypeAdapter = ArrayAdapter.createFromResource(this, R.array.meal_types, android.R.layout.simple_spinner_item);
mealTypeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
binding.spinnerMealType.setAdapter(mealTypeAdapter);
binding.spinnerMealType.setSelection(1); // 午餐
ArrayAdapter<CharSequence> budgetAdapter = ArrayAdapter.createFromResource(this, R.array.meal_budgets, android.R.layout.simple_spinner_item);
budgetAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
binding.spinnerBudget.setAdapter(budgetAdapter);
binding.spinnerBudget.setSelection(1); // 50-100元
}
private void doGenerate() {
String hometown = getText(binding.etHometown);
if (hometown.isEmpty()) {
Toast.makeText(this, "请输入用餐者家乡", Toast.LENGTH_SHORT).show();
return;
}
MealPlanGenerator.MealPlanParams params = new MealPlanGenerator.MealPlanParams();
params.setRegionType(getSpinnerValue(binding.spinnerRegion, getResources().getStringArray(R.array.meal_region_types)));
params.setDinerCount(DINER_VALUES[binding.spinnerDiner.getSelectedItemPosition()]);
params.setMealType(getSpinnerValue(binding.spinnerMealType, getResources().getStringArray(R.array.meal_types)));
params.setHometown(hometown);
params.setPreferences(getText(binding.etPreferences));
params.setDietaryRestrictions(getText(binding.etDietary));
params.setBudget(BUDGET_VALUES[binding.spinnerBudget.getSelectedItemPosition()]);
viewModel.generate(params);
}
private static String getSpinnerValue(Spinner spinner, String[] displayItems) {
int pos = spinner.getSelectedItemPosition();
if (pos >= 0 && pos < displayItems.length) {
return displayItems[pos];
}
return "";
}
private String getText(TextInputEditText et) {
return et.getText() != null ? et.getText().toString().trim() : "";
}
private void copyResult() {
String text = binding.tvResult.getText() != null ? binding.tvResult.getText().toString() : "";
if (text.isEmpty() || getString(R.string.meal_planning_result_placeholder).equals(text)) {
Snackbar.make(binding.getRoot(), "暂无内容可复制", Snackbar.LENGTH_SHORT).show();
return;
}
ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
if (cm != null) {
cm.setPrimaryClip(ClipData.newPlainText("饭菜规划", text));
Snackbar.make(binding.getRoot(), "已复制到剪贴板", Snackbar.LENGTH_SHORT).show();
}
}
private void observeViewModel() {
viewModel.getResultLiveData().observe(this, text -> {
if (text != null) {
binding.tvResult.setText(text);
binding.tvResult.setTextColor(getResources().getColor(R.color.colorTextDark, getTheme()));
}
});
viewModel.getLoadingLiveData().observe(this, loading -> {
binding.progressBar.setVisibility(loading != null && loading ? View.VISIBLE : View.GONE);
binding.btnGenerate.setEnabled(loading == null || !loading);
});
viewModel.getErrorLiveData().observe(this, error -> {
if (error != null && !error.isEmpty()) {
new AlertDialog.Builder(this)
.setMessage(error)
.setPositiveButton(android.R.string.ok, (d, w) -> viewModel.clearError())
.show();
}
});
viewModel.getSnackbarLiveData().observe(this, msg -> {
if (msg != null && !msg.isEmpty()) {
Snackbar.make(binding.getRoot(), msg, Snackbar.LENGTH_SHORT).show();
viewModel.clearSnackbar();
}
});
}
}

View File

@@ -0,0 +1,91 @@
package com.ruilaizi.example.data.meal;
import com.ruilaizi.example.data.api.ApiKeyProvider;
import com.ruilaizi.example.data.api.OpenAICompletionService;
/**
* 智能饭菜规划生成器。本地调用 DeepSeek与 Flask 端 meal_planning 逻辑一致。
*/
public class MealPlanGenerator {
private static final float TEMPERATURE = 0.7f;
private static final int MAX_TOKENS = 2000;
private final OpenAICompletionService completionService;
private final ApiKeyProvider apiKeyProvider;
public MealPlanGenerator(OpenAICompletionService completionService, ApiKeyProvider apiKeyProvider) {
this.completionService = completionService;
this.apiKeyProvider = apiKeyProvider;
}
/**
* 生成饭菜规划。需在后台线程调用。
*/
public String generate(MealPlanParams params) throws Exception {
String apiKey = apiKeyProvider.getApiKey();
if (apiKey == null || apiKey.isEmpty()) {
throw new Exception("请先在设置中配置 API Key");
}
String regionType = params.getRegionType() != null ? params.getRegionType() : "全国";
String systemPrompt = "你是一位专业的" + regionType + "智能饭菜清单规划师和营养搭配专家。\n\n"
+ "请按以下**固定结构**输出 Markdown便于用户查阅\n\n"
+ "**一、菜品推荐与搭配思路**\n"
+ "用一段话说明本餐的搭配思路(结合家乡风味、人数、预算等)。\n\n"
+ "**二、推荐菜品详情**\n"
+ "每道菜单独成块,格式如下:\n"
+ "- 用 **加粗标题** 写菜品名(如:**1. 肉夹馍(经典主食)**\n"
+ "- **菜品特色**:一句话或要点\n"
+ "- **食材清单**:列表写出食材与用量、预估价格(如:五花肉 300g (18元)\n"
+ "- **制作步骤**:编号步骤\n"
+ "- **营养信息**:简要说明\n"
+ "- **预算估算**单道菜金额29元\n\n"
+ "**三、总预算与营养总结**\n"
+ "- **总预算**:总金额与简要说明\n"
+ "- **碳水化合物/蛋白质/维生素与纤维/脂肪**:各一句话\n"
+ "- **过敏信息**:如有需提示\n\n"
+ "约束:营养均衡、符合个人喜好与禁忌、控制预算、体现家乡特色。";
String hometown = params.getHometown() != null ? params.getHometown().trim() : "";
if (hometown.isEmpty()) {
throw new Exception("请输入用餐者家乡");
}
String dinerCount = params.getDinerCount() != null ? params.getDinerCount() : "2";
String mealType = params.getMealType() != null ? params.getMealType() : "午餐";
String preferences = params.getPreferences() != null ? params.getPreferences().trim() : "";
String dietaryRestrictions = params.getDietaryRestrictions() != null ? params.getDietaryRestrictions().trim() : "";
String budget = params.getBudget() != null ? params.getBudget() : "100";
String userPrompt = "" + dinerCount + "人制定" + mealType + "饭菜清单。家乡:" + hometown
+ "。喜好:" + preferences + "。禁忌:" + dietaryRestrictions + "。预算:" + budget
+ "元。请按「一、二、三」结构输出,每道菜包含菜品特色、食材清单、制作步骤、营养信息、预算估算。";
return completionService.chat(systemPrompt, userPrompt, TEMPERATURE, MAX_TOKENS, apiKey);
}
/** 饭菜规划参数,与 Web 端一致 */
public static class MealPlanParams {
private String regionType;
private String dinerCount;
private String mealType;
private String hometown;
private String preferences;
private String dietaryRestrictions;
private String budget;
public String getRegionType() { return regionType; }
public void setRegionType(String regionType) { this.regionType = regionType; }
public String getDinerCount() { return dinerCount; }
public void setDinerCount(String dinerCount) { this.dinerCount = dinerCount; }
public String getMealType() { return mealType; }
public void setMealType(String mealType) { this.mealType = mealType; }
public String getHometown() { return hometown; }
public void setHometown(String hometown) { this.hometown = hometown; }
public String getPreferences() { return preferences; }
public void setPreferences(String preferences) { this.preferences = preferences; }
public String getDietaryRestrictions() { return dietaryRestrictions; }
public void setDietaryRestrictions(String dietaryRestrictions) { this.dietaryRestrictions = dietaryRestrictions; }
public String getBudget() { return budget; }
public void setBudget(String budget) { this.budget = budget; }
}
}

View File

@@ -0,0 +1,32 @@
package com.ruilaizi.example.data.meal;
import android.content.Context;
import com.ruilaizi.example.data.api.ApiKeyProvider;
import com.ruilaizi.example.data.api.OpenAICompletionService;
/**
* 饭菜规划仓库:本地调用 DeepSeek 生成饭菜清单,与 example 项目提示词优化/生成同构。
*/
public class MealPlanningRepository {
private final ApiKeyProvider apiKeyProvider;
private final MealPlanGenerator generator;
public MealPlanningRepository(Context context) {
apiKeyProvider = new ApiKeyProvider(context);
OpenAICompletionService completionService = new OpenAICompletionService(null);
generator = new MealPlanGenerator(completionService, apiKeyProvider);
}
public String getApiKey() {
return apiKeyProvider.getApiKey();
}
/**
* 生成饭菜规划。需在后台线程调用。
*/
public String generateMealPlan(MealPlanGenerator.MealPlanParams params) throws Exception {
return generator.generate(params);
}
}

View File

@@ -0,0 +1,77 @@
package com.ruilaizi.example.ui;
import android.app.Application;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import com.ruilaizi.example.data.meal.MealPlanGenerator;
import com.ruilaizi.example.data.meal.MealPlanningRepository;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 饭菜规划页 ViewModel参数、生成、结果、加载与错误。
*/
public class MealPlanningViewModel extends AndroidViewModel {
private final MealPlanningRepository repository;
private final ExecutorService executor = Executors.newSingleThreadExecutor();
private final MutableLiveData<String> resultLiveData = new MutableLiveData<>("");
private final MutableLiveData<Boolean> loadingLiveData = new MutableLiveData<>(false);
private final MutableLiveData<String> errorLiveData = new MutableLiveData<>();
private final MutableLiveData<String> snackbarLiveData = new MutableLiveData<>();
public MealPlanningViewModel(@NonNull Application application) {
super(application);
repository = new MealPlanningRepository(application);
}
public LiveData<String> getResultLiveData() { return resultLiveData; }
public LiveData<Boolean> getLoadingLiveData() { return loadingLiveData; }
public LiveData<String> getErrorLiveData() { return errorLiveData; }
public LiveData<String> getSnackbarLiveData() { return snackbarLiveData; }
/**
* 生成饭菜规划。参数由调用方从界面读取并传入。
*/
public void generate(MealPlanGenerator.MealPlanParams params) {
if (repository.getApiKey() == null || repository.getApiKey().isEmpty()) {
errorLiveData.setValue("请先在主界面设置中配置 API Key");
return;
}
loadingLiveData.setValue(true);
resultLiveData.setValue("");
errorLiveData.setValue(null);
executor.execute(() -> {
try {
final String plan = repository.generateMealPlan(params);
resultLiveData.postValue(plan);
snackbarLiveData.postValue("饭菜规划生成完成");
} catch (Exception e) {
errorLiveData.postValue(e != null ? e.getMessage() : "生成失败");
} finally {
loadingLiveData.postValue(false);
}
});
}
public void clearError() {
errorLiveData.setValue(null);
}
public void clearSnackbar() {
snackbarLiveData.setValue(null);
}
@Override
protected void onCleared() {
executor.shutdown();
super.onCleared();
}
}

View File

@@ -28,6 +28,13 @@
android:textColor="@color/colorPrimary"
android:textSize="24sp"
android:textStyle="bold" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_meal_planning"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/btn_meal_planning"
android:layout_marginEnd="8dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_history"
style="@style/Widget.MaterialComponents.Button.TextButton"

View File

@@ -0,0 +1,165 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorSurface"
android:fillViewport="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<!-- 标题栏:返回 + 标题 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginBottom="16dp">
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_back"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="返回" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/meal_planning_title"
android:textColor="@color/colorPrimary"
android:textSize="20sp"
android:textStyle="bold"
android:gravity="center" />
<View android:layout_width="48dp" android:layout_height="wrap_content" />
</LinearLayout>
<!-- 规划参数卡片 -->
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardBackgroundColor="@color/colorCard"
app:cardElevation="2dp"
app:cardCornerRadius="12dp"
android:layout_marginBottom="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="规划参数"
android:textColor="@color/colorPrimary"
android:textSize="14sp"
android:textStyle="bold"
android:layout_marginBottom="12dp" />
<!-- 地区类型 -->
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="地区类型" android:textSize="12sp" android:textColor="@color/colorTextSecondary" android:layout_marginBottom="4dp" />
<Spinner android:id="@+id/spinner_region" android:layout_width="match_parent" android:layout_height="48dp" android:layout_marginBottom="12dp" />
<!-- 就餐人数 -->
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="就餐人数" android:textSize="12sp" android:textColor="@color/colorTextSecondary" android:layout_marginBottom="4dp" />
<Spinner android:id="@+id/spinner_diner" android:layout_width="match_parent" android:layout_height="48dp" android:layout_marginBottom="12dp" />
<!-- 用餐类型 -->
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="用餐类型" android:textSize="12sp" android:textColor="@color/colorTextSecondary" android:layout_marginBottom="4dp" />
<Spinner android:id="@+id/spinner_meal_type" android:layout_width="match_parent" android:layout_height="48dp" android:layout_marginBottom="12dp" />
<!-- 用餐者家乡 * -->
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="用餐者家乡 *" android:textSize="12sp" android:textColor="@color/colorTextSecondary" android:layout_marginBottom="4dp" />
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_hometown"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="如:四川成都"
android:inputType="text"
android:layout_marginBottom="12dp" />
<!-- 个人喜好 -->
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="个人喜好" android:textSize="12sp" android:textColor="@color/colorTextSecondary" android:layout_marginBottom="4dp" />
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_preferences"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minLines="2"
android:hint="如:喜欢辣味、偏爱素食"
android:inputType="textMultiLine"
android:layout_marginBottom="12dp" />
<!-- 饮食禁忌 -->
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="饮食禁忌" android:textSize="12sp" android:textColor="@color/colorTextSecondary" android:layout_marginBottom="4dp" />
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_dietary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minLines="2"
android:hint="如:不吃猪肉、对花生过敏"
android:inputType="textMultiLine"
android:layout_marginBottom="12dp" />
<!-- 预算 -->
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="预算范围(元)" android:textSize="12sp" android:textColor="@color/colorTextSecondary" android:layout_marginBottom="4dp" />
<Spinner android:id="@+id/spinner_budget" android:layout_width="match_parent" android:layout_height="48dp" android:layout_marginBottom="16dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_generate"
android:layout_width="match_parent"
android:layout_height="48dp"
android:text="@string/meal_planning_btn_generate"
app:backgroundTint="@color/colorPrimary"
android:textColor="@color/colorWhite" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone"
android:layout_marginBottom="12dp" />
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="规划结果" android:textSize="12sp" android:textColor="@color/colorTextSecondary" android:layout_marginBottom="8dp" />
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardBackgroundColor="@color/colorCard"
app:cardElevation="2dp"
app:cardCornerRadius="12dp"
android:minHeight="100dp"
android:layout_marginBottom="12dp">
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fillViewport="true"
android:padding="16dp">
<TextView
android:id="@+id/tv_result"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/meal_planning_result_placeholder"
android:textColor="@color/colorText"
android:textSize="14sp"
android:lineSpacingMultiplier="1.2"
tools:text="一、菜品推荐与搭配思路\n…" />
</ScrollView>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_copy"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/btn_copy" />
</LinearLayout>
</ScrollView>

View File

@@ -1,5 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- 饭菜规划:地区类型 -->
<string-array name="meal_region_types">
<item>全国</item><item>北方</item><item>南方</item><item>川菜</item><item>粤菜</item>
<item>鲁菜</item><item>苏菜</item><item>浙菜</item><item>闽菜</item><item>湘菜</item><item>徽菜</item>
</string-array>
<!-- 就餐人数 -->
<string-array name="meal_diner_counts">
<item>1人</item><item>2人</item><item>3人</item><item>4人</item><item>5人</item>
<item>6人</item><item>8人</item><item>10人</item>
</string-array>
<!-- 用餐类型 -->
<string-array name="meal_types">
<item>早餐</item><item>午餐</item><item>晚餐</item><item>全天</item>
</string-array>
<!-- 预算(元) -->
<string-array name="meal_budgets">
<item>50元以下</item><item>50-100元</item><item>100-150元</item><item>150-200元</item>
<item>200-300元</item><item>300-500元</item><item>500元以上</item>
</string-array>
<declare-styleable name="CircleImageView">
<attr name="border_width" format="dimension" />
<attr name="border_color" format="color" />

View File

@@ -32,4 +32,8 @@
<string name="history_empty_generation">暂无生成记录</string>
<string name="history_empty_direct_answer">暂无直答记录</string>
<string name="history_use">使用</string>
<string name="meal_planning_title">智能饭菜规划</string>
<string name="meal_planning_btn_generate">生成饭菜规划</string>
<string name="meal_planning_result_placeholder">填写左侧参数后点击「生成饭菜规划」</string>
<string name="btn_meal_planning">饭菜规划</string>
</resources>