aia
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-05 00:21:57 +08:00
parent de4c3db2d3
commit 18cb2e6614
57 changed files with 2169 additions and 7 deletions

View File

@@ -54,6 +54,26 @@
android:name=".MeetingMinutesActivity"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name=".TravelPlanningHistoryActivity"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name=".WeeklyReportHistoryActivity"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name=".MeetingMinutesHistoryActivity"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name=".PoetryHistoryActivity"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name=".ResumeOptimizationHistoryActivity"
android:exported="false"
android:screenOrientation="portrait" />
<provider
android:name="androidx.core.content.FileProvider"

View File

@@ -51,10 +51,9 @@ public class AIServicesActivity extends AppCompatActivity {
entries.add(new AIServiceEntry("旅行攻略规划", "根据目的地与天数AI 生成行程与实用建议", "出行", TravelPlanningActivity.class));
entries.add(new AIServiceEntry("智能周报生成", "输入工作要点,一键生成规范周报/日报", "办公", WeeklyReportActivity.class));
entries.add(new AIServiceEntry("会议纪要整理", "粘贴会议转写或要点,一键生成结构化会议纪要", "办公", MeetingMinutesActivity.class));
// 占位:点击进入 PlaceholderActivity 显示「敬请期待」
entries.add(new AIServiceEntry("古诗词解析", "深度解析古诗词意境,感受中华文化之美", "文化", null));
entries.add(new AIServiceEntry("古诗词解析", "深度解析古诗词意境,感受中华文化之美", "文化", PoetryActivity.class));
entries.add(new AIServiceEntry("古诗词收藏", "收藏喜爱的古诗词,建立个人文化宝库", "收藏", null));
entries.add(new AIServiceEntry("简历/求职信优化", "润色简历或根据岗位生成针对性求职信", "求职", null));
entries.add(new AIServiceEntry("简历/求职信优化", "润色简历或根据岗位生成针对性求职信", "求职", ResumeOptimizationActivity.class));
entries.add(new AIServiceEntry("2号专家", "需求分析 + 领域专家生成高质量提示词", "专家", null));
entries.add(new AIServiceEntry("3号专家", "需求分析 + 领域专家生成,含历史记录", "专家", null));
entries.add(new AIServiceEntry("读书笔记", "生成摘要、金句、思维导图式大纲或读后感", "文化", null));

View File

@@ -29,6 +29,9 @@ public class MeetingMinutesActivity extends AppCompatActivity {
viewModel = new ViewModelProvider(this, new ViewModelProvider.AndroidViewModelFactory(getApplication())).get(MeetingMinutesViewModel.class);
binding.btnBack.setOnClickListener(v -> finish());
if (binding.btnHistory != null) {
binding.btnHistory.setOnClickListener(v -> startActivity(new android.content.Intent(this, MeetingMinutesHistoryActivity.class)));
}
binding.btnGenerate.setOnClickListener(v -> doGenerate());
binding.btnCopy.setOnClickListener(v -> copyResult());
observeViewModel();

View File

@@ -0,0 +1,39 @@
package com.ruilaizi.example;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.ruilaizi.example.adapter.MeetingMinutesHistoryAdapter;
import com.ruilaizi.example.data.meeting.MeetingMinutesRepository;
import com.ruilaizi.example.databinding.ActivityMeetingMinutesHistoryBinding;
public class MeetingMinutesHistoryActivity extends AppCompatActivity {
private ActivityMeetingMinutesHistoryBinding binding;
private MeetingMinutesHistoryAdapter adapter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMeetingMinutesHistoryBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
adapter = new MeetingMinutesHistoryAdapter();
adapter.setOnUseListener(content -> {
setResult(RESULT_OK);
finish();
});
binding.recycler.setLayoutManager(new LinearLayoutManager(this));
binding.recycler.setAdapter(adapter);
binding.btnBack.setOnClickListener(v -> finish());
MeetingMinutesRepository repo = new MeetingMinutesRepository(getApplication());
repo.getRecentRecords().observe(this, list -> {
adapter.setData(list);
binding.emptyText.setVisibility(list == null || list.isEmpty() ? View.VISIBLE : View.GONE);
});
}
}

View File

@@ -0,0 +1,89 @@
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.Toast;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModelProvider;
import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.textfield.TextInputEditText;
import com.ruilaizi.example.databinding.ActivityPoetryBinding;
import com.ruilaizi.example.ui.PoetryViewModel;
public class PoetryActivity extends AppCompatActivity {
private ActivityPoetryBinding binding;
private PoetryViewModel viewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityPoetryBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
viewModel = new ViewModelProvider(this, new ViewModelProvider.AndroidViewModelFactory(getApplication())).get(PoetryViewModel.class);
binding.btnBack.setOnClickListener(v -> finish());
if (binding.btnHistory != null) {
binding.btnHistory.setOnClickListener(v -> startActivity(new android.content.Intent(this, PoetryHistoryActivity.class)));
}
binding.btnGenerate.setOnClickListener(v -> doGenerate());
binding.btnCopy.setOnClickListener(v -> copyResult());
observeViewModel();
}
private void doGenerate() {
String title = getText(binding.etPoetryTitle);
if (title.isEmpty()) {
Toast.makeText(this, "请输入诗词标题", Toast.LENGTH_SHORT).show();
return;
}
viewModel.generate(title, getText(binding.etAuthor), getText(binding.etDynasty), getText(binding.etExtra));
}
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()) {
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,38 @@
package com.ruilaizi.example;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.ruilaizi.example.adapter.PoetryHistoryAdapter;
import com.ruilaizi.example.data.poetry.PoetryRepository;
import com.ruilaizi.example.databinding.ActivityPoetryHistoryBinding;
public class PoetryHistoryActivity extends AppCompatActivity {
private ActivityPoetryHistoryBinding binding;
private PoetryHistoryAdapter adapter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityPoetryHistoryBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
adapter = new PoetryHistoryAdapter();
adapter.setOnUseListener(content -> {
setResult(RESULT_OK);
finish();
});
binding.recycler.setLayoutManager(new LinearLayoutManager(this));
binding.recycler.setAdapter(adapter);
binding.btnBack.setOnClickListener(v -> finish());
PoetryRepository repo = new PoetryRepository(getApplication());
repo.getRecentRecords().observe(this, list -> {
adapter.setData(list);
binding.emptyText.setVisibility(list == null || list.isEmpty() ? View.VISIBLE : View.GONE);
});
}
}

View File

@@ -0,0 +1,93 @@
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.Toast;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModelProvider;
import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.textfield.TextInputEditText;
import com.ruilaizi.example.databinding.ActivityResumeOptimizationBinding;
import com.ruilaizi.example.ui.ResumeOptimizationViewModel;
public class ResumeOptimizationActivity extends AppCompatActivity {
private ActivityResumeOptimizationBinding binding;
private ResumeOptimizationViewModel viewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityResumeOptimizationBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
viewModel = new ViewModelProvider(this, new ViewModelProvider.AndroidViewModelFactory(getApplication())).get(ResumeOptimizationViewModel.class);
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.resume_opt_types, android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
binding.spinnerType.setAdapter(adapter);
binding.btnBack.setOnClickListener(v -> finish());
if (binding.btnHistory != null) {
binding.btnHistory.setOnClickListener(v -> startActivity(new android.content.Intent(this, ResumeOptimizationHistoryActivity.class)));
}
binding.btnGenerate.setOnClickListener(v -> doGenerate());
binding.btnCopy.setOnClickListener(v -> copyResult());
observeViewModel();
}
private void doGenerate() {
String original = getText(binding.etOriginal);
if (original.isEmpty()) {
Toast.makeText(this, "请填写简历内容或求职信要点", Toast.LENGTH_SHORT).show();
return;
}
String optType = binding.spinnerType.getSelectedItemPosition() == 0 ? "resume" : "cover_letter";
viewModel.generate(optType, original, getText(binding.etJobDesc));
}
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()) {
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,38 @@
package com.ruilaizi.example;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.ruilaizi.example.adapter.ResumeOptimizationHistoryAdapter;
import com.ruilaizi.example.data.resume.ResumeOptimizationRepository;
import com.ruilaizi.example.databinding.ActivityResumeOptimizationHistoryBinding;
public class ResumeOptimizationHistoryActivity extends AppCompatActivity {
private ActivityResumeOptimizationHistoryBinding binding;
private ResumeOptimizationHistoryAdapter adapter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityResumeOptimizationHistoryBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
adapter = new ResumeOptimizationHistoryAdapter();
adapter.setOnUseListener(content -> {
setResult(RESULT_OK);
finish();
});
binding.recycler.setLayoutManager(new LinearLayoutManager(this));
binding.recycler.setAdapter(adapter);
binding.btnBack.setOnClickListener(v -> finish());
ResumeOptimizationRepository repo = new ResumeOptimizationRepository(getApplication());
repo.getRecentRecords().observe(this, list -> {
adapter.setData(list);
binding.emptyText.setVisibility(list == null || list.isEmpty() ? View.VISIBLE : View.GONE);
});
}
}

View File

@@ -24,6 +24,9 @@ public class TravelPlanningActivity extends AppCompatActivity {
setContentView(binding.getRoot());
viewModel = new ViewModelProvider(this, new ViewModelProvider.AndroidViewModelFactory(getApplication())).get(TravelPlanningViewModel.class);
binding.btnBack.setOnClickListener(v -> finish());
if (binding.btnHistory != null) {
binding.btnHistory.setOnClickListener(v -> startActivity(new android.content.Intent(this, TravelPlanningHistoryActivity.class)));
}
binding.btnGenerate.setOnClickListener(v -> doGenerate());
binding.btnCopy.setOnClickListener(v -> copyResult());
observeViewModel();

View File

@@ -0,0 +1,39 @@
package com.ruilaizi.example;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.ruilaizi.example.adapter.TravelHistoryAdapter;
import com.ruilaizi.example.data.travel.TravelPlanningRepository;
import com.ruilaizi.example.databinding.ActivityTravelPlanningHistoryBinding;
public class TravelPlanningHistoryActivity extends AppCompatActivity {
private ActivityTravelPlanningHistoryBinding binding;
private TravelHistoryAdapter adapter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityTravelPlanningHistoryBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
adapter = new TravelHistoryAdapter();
adapter.setOnUseListener(content -> {
setResult(RESULT_OK);
finish();
});
binding.recycler.setLayoutManager(new LinearLayoutManager(this));
binding.recycler.setAdapter(adapter);
binding.btnBack.setOnClickListener(v -> finish());
TravelPlanningRepository repo = new TravelPlanningRepository(getApplication());
repo.getRecentRecords().observe(this, list -> {
adapter.setData(list);
binding.emptyText.setVisibility(list == null || list.isEmpty() ? View.VISIBLE : View.GONE);
});
}
}

View File

@@ -29,6 +29,9 @@ public class WeeklyReportActivity extends AppCompatActivity {
viewModel = new ViewModelProvider(this, new ViewModelProvider.AndroidViewModelFactory(getApplication())).get(WeeklyReportViewModel.class);
binding.btnBack.setOnClickListener(v -> finish());
if (binding.btnHistory != null) {
binding.btnHistory.setOnClickListener(v -> startActivity(new android.content.Intent(this, WeeklyReportHistoryActivity.class)));
}
binding.btnGenerate.setOnClickListener(v -> doGenerate());
binding.btnCopy.setOnClickListener(v -> copyResult());
observeViewModel();

View File

@@ -0,0 +1,39 @@
package com.ruilaizi.example;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.ruilaizi.example.adapter.WeeklyReportHistoryAdapter;
import com.ruilaizi.example.data.weekly.WeeklyReportRepository;
import com.ruilaizi.example.databinding.ActivityWeeklyReportHistoryBinding;
public class WeeklyReportHistoryActivity extends AppCompatActivity {
private ActivityWeeklyReportHistoryBinding binding;
private WeeklyReportHistoryAdapter adapter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityWeeklyReportHistoryBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
adapter = new WeeklyReportHistoryAdapter();
adapter.setOnUseListener(content -> {
setResult(RESULT_OK);
finish();
});
binding.recycler.setLayoutManager(new LinearLayoutManager(this));
binding.recycler.setAdapter(adapter);
binding.btnBack.setOnClickListener(v -> finish());
WeeklyReportRepository repo = new WeeklyReportRepository(getApplication());
repo.getRecentRecords().observe(this, list -> {
adapter.setData(list);
binding.emptyText.setVisibility(list == null || list.isEmpty() ? View.VISIBLE : View.GONE);
});
}
}

View File

@@ -0,0 +1,95 @@
package com.ruilaizi.example.adapter;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.button.MaterialButton;
import com.ruilaizi.example.R;
import com.ruilaizi.example.data.db.MeetingMinutesRecord;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
public class MeetingMinutesHistoryAdapter extends RecyclerView.Adapter<MeetingMinutesHistoryAdapter.ViewHolder> {
private final List<MeetingMinutesRecord> list = new ArrayList<>();
private OnUseListener onUseListener;
public interface OnUseListener {
void onUse(String summaryContent);
}
public void setOnUseListener(OnUseListener listener) {
this.onUseListener = listener;
}
public void setData(List<MeetingMinutesRecord> data) {
list.clear();
if (data != null) list.addAll(data);
notifyDataSetChanged();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_meeting_minutes_history, parent, false);
return new ViewHolder(v);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
MeetingMinutesRecord r = list.get(position);
holder.tvSummary.setText(r.title != null && !r.title.isEmpty() ? r.title : "会议纪要");
holder.tvContentPreview.setText(truncate(r.summaryContent, 80));
holder.tvTime.setText(formatTime(r.createdAt));
holder.btnUse.setOnClickListener(v -> {
if (onUseListener != null && r.summaryContent != null) onUseListener.onUse(r.summaryContent);
});
holder.btnCopy.setOnClickListener(v -> {
if (r.summaryContent != null && !r.summaryContent.isEmpty()) {
ClipboardManager cm = (ClipboardManager) holder.itemView.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
if (cm != null) cm.setPrimaryClip(ClipData.newPlainText("会议纪要", r.summaryContent));
}
});
}
@Override
public int getItemCount() {
return list.size();
}
private static String truncate(String s, int maxLen) {
if (s == null) return "";
s = s.trim();
return s.length() <= maxLen ? s : s.substring(0, maxLen) + "";
}
private static String formatTime(long timestamp) {
try {
return new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault()).format(new Date(timestamp));
} catch (Exception e) { return ""; }
}
static class ViewHolder extends RecyclerView.ViewHolder {
TextView tvSummary, tvContentPreview, tvTime;
MaterialButton btnUse, btnCopy;
ViewHolder(View itemView) {
super(itemView);
tvSummary = itemView.findViewById(R.id.tv_summary);
tvContentPreview = itemView.findViewById(R.id.tv_content_preview);
tvTime = itemView.findViewById(R.id.tv_time);
btnUse = itemView.findViewById(R.id.btn_use);
btnCopy = itemView.findViewById(R.id.btn_copy);
}
}
}

View File

@@ -0,0 +1,95 @@
package com.ruilaizi.example.adapter;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.button.MaterialButton;
import com.ruilaizi.example.R;
import com.ruilaizi.example.data.db.PoetryRecord;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
public class PoetryHistoryAdapter extends RecyclerView.Adapter<PoetryHistoryAdapter.ViewHolder> {
private final List<PoetryRecord> list = new ArrayList<>();
private OnUseListener onUseListener;
public interface OnUseListener {
void onUse(String summaryContent);
}
public void setOnUseListener(OnUseListener listener) {
this.onUseListener = listener;
}
public void setData(List<PoetryRecord> data) {
list.clear();
if (data != null) list.addAll(data);
notifyDataSetChanged();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_poetry_history, parent, false);
return new ViewHolder(v);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
PoetryRecord r = list.get(position);
holder.tvSummary.setText(r.poetryTitle != null ? r.poetryTitle : "古诗词");
holder.tvContentPreview.setText(truncate(r.analysisContent, 80));
holder.tvTime.setText(formatTime(r.createdAt));
holder.btnUse.setOnClickListener(v -> {
if (onUseListener != null && r.analysisContent != null) onUseListener.onUse(r.analysisContent);
});
holder.btnCopy.setOnClickListener(v -> {
if (r.analysisContent != null && !r.analysisContent.isEmpty()) {
ClipboardManager cm = (ClipboardManager) holder.itemView.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
if (cm != null) cm.setPrimaryClip(ClipData.newPlainText("古诗词解析", r.analysisContent));
}
});
}
@Override
public int getItemCount() {
return list.size();
}
private static String truncate(String s, int maxLen) {
if (s == null) return "";
s = s.trim();
return s.length() <= maxLen ? s : s.substring(0, maxLen) + "";
}
private static String formatTime(long timestamp) {
try {
return new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault()).format(new Date(timestamp));
} catch (Exception e) { return ""; }
}
static class ViewHolder extends RecyclerView.ViewHolder {
TextView tvSummary, tvContentPreview, tvTime;
MaterialButton btnUse, btnCopy;
ViewHolder(View itemView) {
super(itemView);
tvSummary = itemView.findViewById(R.id.tv_summary);
tvContentPreview = itemView.findViewById(R.id.tv_content_preview);
tvTime = itemView.findViewById(R.id.tv_time);
btnUse = itemView.findViewById(R.id.btn_use);
btnCopy = itemView.findViewById(R.id.btn_copy);
}
}
}

View File

@@ -0,0 +1,95 @@
package com.ruilaizi.example.adapter;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.button.MaterialButton;
import com.ruilaizi.example.R;
import com.ruilaizi.example.data.db.ResumeOptimizationRecord;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
public class ResumeOptimizationHistoryAdapter extends RecyclerView.Adapter<ResumeOptimizationHistoryAdapter.ViewHolder> {
private final List<ResumeOptimizationRecord> list = new ArrayList<>();
private OnUseListener onUseListener;
public interface OnUseListener {
void onUse(String summaryContent);
}
public void setOnUseListener(OnUseListener listener) {
this.onUseListener = listener;
}
public void setData(List<ResumeOptimizationRecord> data) {
list.clear();
if (data != null) list.addAll(data);
notifyDataSetChanged();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_resume_history, parent, false);
return new ViewHolder(v);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
ResumeOptimizationRecord r = list.get(position);
holder.tvSummary.setText("cover_letter".equals(r.optType) ? "求职信" : "简历优化");
holder.tvContentPreview.setText(truncate(r.optimizedContent, 80));
holder.tvTime.setText(formatTime(r.createdAt));
holder.btnUse.setOnClickListener(v -> {
if (onUseListener != null && r.optimizedContent != null) onUseListener.onUse(r.optimizedContent);
});
holder.btnCopy.setOnClickListener(v -> {
if (r.optimizedContent != null && !r.optimizedContent.isEmpty()) {
ClipboardManager cm = (ClipboardManager) holder.itemView.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
if (cm != null) cm.setPrimaryClip(ClipData.newPlainText("简历优化", r.optimizedContent));
}
});
}
@Override
public int getItemCount() {
return list.size();
}
private static String truncate(String s, int maxLen) {
if (s == null) return "";
s = s.trim();
return s.length() <= maxLen ? s : s.substring(0, maxLen) + "";
}
private static String formatTime(long timestamp) {
try {
return new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault()).format(new Date(timestamp));
} catch (Exception e) { return ""; }
}
static class ViewHolder extends RecyclerView.ViewHolder {
TextView tvSummary, tvContentPreview, tvTime;
MaterialButton btnUse, btnCopy;
ViewHolder(View itemView) {
super(itemView);
tvSummary = itemView.findViewById(R.id.tv_summary);
tvContentPreview = itemView.findViewById(R.id.tv_content_preview);
tvTime = itemView.findViewById(R.id.tv_time);
btnUse = itemView.findViewById(R.id.btn_use);
btnCopy = itemView.findViewById(R.id.btn_copy);
}
}
}

View File

@@ -0,0 +1,107 @@
package com.ruilaizi.example.adapter;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.button.MaterialButton;
import com.ruilaizi.example.R;
import com.ruilaizi.example.data.db.TravelPlanRecord;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
public class TravelHistoryAdapter extends RecyclerView.Adapter<TravelHistoryAdapter.ViewHolder> {
private final List<TravelPlanRecord> list = new ArrayList<>();
private OnUseListener onUseListener;
public interface OnUseListener {
void onUse(String planContent);
}
public void setOnUseListener(OnUseListener listener) {
this.onUseListener = listener;
}
public void setData(List<TravelPlanRecord> data) {
list.clear();
if (data != null) {
list.addAll(data);
}
notifyDataSetChanged();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_travel_history, parent, false);
return new ViewHolder(v);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
TravelPlanRecord r = list.get(position);
String summary = safe(r.destination) + " | " + safe(r.days) + "天 | " + safe(r.people) + "";
holder.tvSummary.setText(summary);
holder.tvContentPreview.setText(truncate(r.planContent, 80));
holder.tvTime.setText(formatTime(r.createdAt));
holder.btnUse.setOnClickListener(v -> {
if (onUseListener != null && r.planContent != null) onUseListener.onUse(r.planContent);
});
holder.btnCopy.setOnClickListener(v -> {
if (r.planContent != null && !r.planContent.isEmpty()) {
ClipboardManager cm = (ClipboardManager) holder.itemView.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
if (cm != null) cm.setPrimaryClip(ClipData.newPlainText("旅行攻略", r.planContent));
}
});
}
@Override
public int getItemCount() {
return list.size();
}
private static String safe(String s) {
return s != null ? s : "";
}
private static String truncate(String s, int maxLen) {
if (s == null) return "";
s = s.trim();
if (s.length() <= maxLen) return s;
return s.substring(0, maxLen) + "";
}
private static String formatTime(long timestamp) {
try {
return new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault()).format(new Date(timestamp));
} catch (Exception e) {
return "";
}
}
static class ViewHolder extends RecyclerView.ViewHolder {
TextView tvSummary, tvContentPreview, tvTime;
MaterialButton btnUse, btnCopy;
ViewHolder(View itemView) {
super(itemView);
tvSummary = itemView.findViewById(R.id.tv_summary);
tvContentPreview = itemView.findViewById(R.id.tv_content_preview);
tvTime = itemView.findViewById(R.id.tv_time);
btnUse = itemView.findViewById(R.id.btn_use);
btnCopy = itemView.findViewById(R.id.btn_copy);
}
}
}

View File

@@ -0,0 +1,107 @@
package com.ruilaizi.example.adapter;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.button.MaterialButton;
import com.ruilaizi.example.R;
import com.ruilaizi.example.data.db.WeeklyReportRecord;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
public class WeeklyReportHistoryAdapter extends RecyclerView.Adapter<WeeklyReportHistoryAdapter.ViewHolder> {
private final List<WeeklyReportRecord> list = new ArrayList<>();
private OnUseListener onUseListener;
public interface OnUseListener {
void onUse(String reportContent);
}
public void setOnUseListener(OnUseListener listener) {
this.onUseListener = listener;
}
public void setData(List<WeeklyReportRecord> data) {
list.clear();
if (data != null) {
list.addAll(data);
}
notifyDataSetChanged();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_weekly_report_history, parent, false);
return new ViewHolder(v);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
WeeklyReportRecord r = list.get(position);
String summary = "weekly".equals(r.reportType) ? "周报" : "日报";
holder.tvSummary.setText(summary);
holder.tvContentPreview.setText(truncate(r.reportContent, 80));
holder.tvTime.setText(formatTime(r.createdAt));
holder.btnUse.setOnClickListener(v -> {
if (onUseListener != null && r.reportContent != null) onUseListener.onUse(r.reportContent);
});
holder.btnCopy.setOnClickListener(v -> {
if (r.reportContent != null && !r.reportContent.isEmpty()) {
ClipboardManager cm = (ClipboardManager) holder.itemView.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
if (cm != null) cm.setPrimaryClip(ClipData.newPlainText("周报", r.reportContent));
}
});
}
@Override
public int getItemCount() {
return list.size();
}
private static String safe(String s) {
return s != null ? s : "";
}
private static String truncate(String s, int maxLen) {
if (s == null) return "";
s = s.trim();
if (s.length() <= maxLen) return s;
return s.substring(0, maxLen) + "";
}
private static String formatTime(long timestamp) {
try {
return new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault()).format(new Date(timestamp));
} catch (Exception e) {
return "";
}
}
static class ViewHolder extends RecyclerView.ViewHolder {
TextView tvSummary, tvContentPreview, tvTime;
MaterialButton btnUse, btnCopy;
ViewHolder(View itemView) {
super(itemView);
tvSummary = itemView.findViewById(R.id.tv_summary);
tvContentPreview = itemView.findViewById(R.id.tv_content_preview);
tvTime = itemView.findViewById(R.id.tv_time);
btnUse = itemView.findViewById(R.id.btn_use);
btnCopy = itemView.findViewById(R.id.btn_copy);
}
}
}

View File

@@ -6,7 +6,9 @@ import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;
@Database(entities = {GenerationRecord.class, OptimizeRecord.class, DirectAnswerRecord.class, MealPlanRecord.class}, version = 4, exportSchema = false)
@Database(entities = {GenerationRecord.class, OptimizeRecord.class, DirectAnswerRecord.class, MealPlanRecord.class,
TravelPlanRecord.class, WeeklyReportRecord.class, MeetingMinutesRecord.class,
PoetryRecord.class, ResumeOptimizationRecord.class}, version = 6, exportSchema = false)
public abstract class AppDatabase extends RoomDatabase {
private static volatile AppDatabase INSTANCE;
@@ -30,4 +32,9 @@ public abstract class AppDatabase extends RoomDatabase {
public abstract OptimizeRecordDao optimizeRecordDao();
public abstract DirectAnswerRecordDao directAnswerRecordDao();
public abstract MealPlanRecordDao mealPlanRecordDao();
public abstract TravelPlanRecordDao travelPlanRecordDao();
public abstract WeeklyReportRecordDao weeklyReportRecordDao();
public abstract MeetingMinutesRecordDao meetingMinutesRecordDao();
public abstract PoetryRecordDao poetryRecordDao();
public abstract ResumeOptimizationRecordDao resumeOptimizationRecordDao();
}

View File

@@ -0,0 +1,25 @@
package com.ruilaizi.example.data.db;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
/** 会议纪要历史记录 */
@Entity(tableName = "meeting_minutes_records")
public class MeetingMinutesRecord {
@PrimaryKey(autoGenerate = true)
public long id;
public String title;
public String rawContent;
public String summaryContent;
public long createdAt;
public MeetingMinutesRecord() {}
public MeetingMinutesRecord(String title, String rawContent, String summaryContent) {
this.title = title;
this.rawContent = rawContent;
this.summaryContent = summaryContent;
this.createdAt = System.currentTimeMillis();
}
}

View File

@@ -0,0 +1,21 @@
package com.ruilaizi.example.data.db;
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
import java.util.List;
@Dao
public interface MeetingMinutesRecordDao {
@Insert
long insert(MeetingMinutesRecord record);
@Query("SELECT * FROM meeting_minutes_records ORDER BY createdAt DESC LIMIT 50")
LiveData<List<MeetingMinutesRecord>> getRecentRecords();
@Query("DELETE FROM meeting_minutes_records WHERE id NOT IN (SELECT id FROM meeting_minutes_records ORDER BY createdAt DESC LIMIT 50)")
void keepOnlyRecent();
}

View File

@@ -0,0 +1,26 @@
package com.ruilaizi.example.data.db;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
@Entity(tableName = "poetry_records")
public class PoetryRecord {
@PrimaryKey(autoGenerate = true)
public long id;
public String poetryTitle;
public String author;
public String dynasty;
public String analysisContent;
public long createdAt;
public PoetryRecord() {}
public PoetryRecord(String poetryTitle, String author, String dynasty, String analysisContent) {
this.poetryTitle = poetryTitle;
this.author = author;
this.dynasty = dynasty;
this.analysisContent = analysisContent;
this.createdAt = System.currentTimeMillis();
}
}

View File

@@ -0,0 +1,21 @@
package com.ruilaizi.example.data.db;
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
import java.util.List;
@Dao
public interface PoetryRecordDao {
@Insert
long insert(PoetryRecord record);
@Query("SELECT * FROM poetry_records ORDER BY createdAt DESC LIMIT 50")
LiveData<List<PoetryRecord>> getRecentRecords();
@Query("DELETE FROM poetry_records WHERE id NOT IN (SELECT id FROM poetry_records ORDER BY createdAt DESC LIMIT 50)")
void keepOnlyRecent();
}

View File

@@ -0,0 +1,27 @@
package com.ruilaizi.example.data.db;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
/** 简历/求职信优化历史记录 */
@Entity(tableName = "resume_optimization_records")
public class ResumeOptimizationRecord {
@PrimaryKey(autoGenerate = true)
public long id;
public String optType; // resume | cover_letter
public String originalContent;
public String jobDescription;
public String optimizedContent;
public long createdAt;
public ResumeOptimizationRecord() {}
public ResumeOptimizationRecord(String optType, String originalContent, String jobDescription, String optimizedContent) {
this.optType = optType;
this.originalContent = originalContent;
this.jobDescription = jobDescription;
this.optimizedContent = optimizedContent;
this.createdAt = System.currentTimeMillis();
}
}

View File

@@ -0,0 +1,21 @@
package com.ruilaizi.example.data.db;
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
import java.util.List;
@Dao
public interface ResumeOptimizationRecordDao {
@Insert
long insert(ResumeOptimizationRecord record);
@Query("SELECT * FROM resume_optimization_records ORDER BY createdAt DESC LIMIT 50")
LiveData<List<ResumeOptimizationRecord>> getRecentRecords();
@Query("DELETE FROM resume_optimization_records WHERE id NOT IN (SELECT id FROM resume_optimization_records ORDER BY createdAt DESC LIMIT 50)")
void keepOnlyRecent();
}

View File

@@ -0,0 +1,31 @@
package com.ruilaizi.example.data.db;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
/** 旅行攻略历史记录 */
@Entity(tableName = "travel_plan_records")
public class TravelPlanRecord {
@PrimaryKey(autoGenerate = true)
public long id;
public String destination;
public String days;
public String people;
public String preferences;
public String budget;
public String planContent;
public long createdAt;
public TravelPlanRecord() {}
public TravelPlanRecord(String destination, String days, String people, String preferences, String budget, String planContent) {
this.destination = destination;
this.days = days;
this.people = people;
this.preferences = preferences;
this.budget = budget;
this.planContent = planContent;
this.createdAt = System.currentTimeMillis();
}
}

View File

@@ -0,0 +1,21 @@
package com.ruilaizi.example.data.db;
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
import java.util.List;
@Dao
public interface TravelPlanRecordDao {
@Insert
long insert(TravelPlanRecord record);
@Query("SELECT * FROM travel_plan_records ORDER BY createdAt DESC LIMIT 50")
LiveData<List<TravelPlanRecord>> getRecentRecords();
@Query("DELETE FROM travel_plan_records WHERE id NOT IN (SELECT id FROM travel_plan_records ORDER BY createdAt DESC LIMIT 50)")
void keepOnlyRecent();
}

View File

@@ -0,0 +1,25 @@
package com.ruilaizi.example.data.db;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
/** 周报/日报历史记录 */
@Entity(tableName = "weekly_report_records")
public class WeeklyReportRecord {
@PrimaryKey(autoGenerate = true)
public long id;
public String reportType; // weekly | daily
public String inputContent;
public String reportContent;
public long createdAt;
public WeeklyReportRecord() {}
public WeeklyReportRecord(String reportType, String inputContent, String reportContent) {
this.reportType = reportType;
this.inputContent = inputContent;
this.reportContent = reportContent;
this.createdAt = System.currentTimeMillis();
}
}

View File

@@ -0,0 +1,21 @@
package com.ruilaizi.example.data.db;
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
import java.util.List;
@Dao
public interface WeeklyReportRecordDao {
@Insert
long insert(WeeklyReportRecord record);
@Query("SELECT * FROM weekly_report_records ORDER BY createdAt DESC LIMIT 50")
LiveData<List<WeeklyReportRecord>> getRecentRecords();
@Query("DELETE FROM weekly_report_records WHERE id NOT IN (SELECT id FROM weekly_report_records ORDER BY createdAt DESC LIMIT 50)")
void keepOnlyRecent();
}

View File

@@ -2,19 +2,39 @@ package com.ruilaizi.example.data.meeting;
import android.content.Context;
import androidx.lifecycle.LiveData;
import com.ruilaizi.example.data.api.ApiKeyProvider;
import com.ruilaizi.example.data.api.OpenAICompletionService;
import com.ruilaizi.example.data.db.AppDatabase;
import com.ruilaizi.example.data.db.MeetingMinutesRecord;
import com.ruilaizi.example.data.db.MeetingMinutesRecordDao;
import java.util.List;
public class MeetingMinutesRepository {
private final MeetingMinutesGenerator generator;
private final MeetingMinutesRecordDao dao;
public MeetingMinutesRepository(Context context) {
ApiKeyProvider apiKeyProvider = new ApiKeyProvider(context);
generator = new MeetingMinutesGenerator(new OpenAICompletionService(null), apiKeyProvider);
dao = AppDatabase.getInstance(context).meetingMinutesRecordDao();
}
public String generate(String rawContent, String title) throws Exception {
return generator.generate(rawContent, title);
}
public void saveRecord(MeetingMinutesRecord record) {
new Thread(() -> {
dao.insert(record);
dao.keepOnlyRecent();
}).start();
}
public LiveData<List<MeetingMinutesRecord>> getRecentRecords() {
return dao.getRecentRecords();
}
}

View File

@@ -0,0 +1,26 @@
package com.ruilaizi.example.data.poetry;
import com.ruilaizi.example.data.api.ApiKeyProvider;
import com.ruilaizi.example.data.api.OpenAICompletionService;
public class PoetryGenerator {
private final OpenAICompletionService completionService;
private final ApiKeyProvider apiKeyProvider;
public PoetryGenerator(OpenAICompletionService completionService, ApiKeyProvider apiKeyProvider) {
this.completionService = completionService;
this.apiKeyProvider = apiKeyProvider;
}
public String generate(String poetryTitle, String author, String dynasty, String extraRequirements) throws Exception {
String apiKey = apiKeyProvider.getApiKey();
if (apiKey == null || apiKey.isEmpty()) throw new Exception("请先在设置中配置 API Key");
if (poetryTitle == null || poetryTitle.trim().isEmpty()) throw new Exception("请输入诗词标题");
String systemPrompt = "你是一位专业的古典文学专家和古诗词翻译家。请根据用户提供的古诗词信息,提供完整的原文、现代译文、详细注释和深度解读。使用 Markdown 格式输出,标题用 # ## ###,重要内容用粗体。";
String userPrompt = "诗词标题:" + (poetryTitle != null ? poetryTitle.trim() : "") + "\n作者" + (author != null ? author : "") + "\n朝代" + (dynasty != null ? dynasty : "");
if (extraRequirements != null && !extraRequirements.isEmpty()) userPrompt += "\n其他要求" + extraRequirements;
userPrompt += "\n\n请提供完整的古诗词解析。";
return completionService.chat(systemPrompt, userPrompt, 0.7f, 2000, apiKey);
}
}

View File

@@ -0,0 +1,40 @@
package com.ruilaizi.example.data.poetry;
import android.content.Context;
import androidx.lifecycle.LiveData;
import com.ruilaizi.example.data.api.ApiKeyProvider;
import com.ruilaizi.example.data.api.OpenAICompletionService;
import com.ruilaizi.example.data.db.AppDatabase;
import com.ruilaizi.example.data.db.PoetryRecord;
import com.ruilaizi.example.data.db.PoetryRecordDao;
import java.util.List;
public class PoetryRepository {
private final PoetryGenerator generator;
private final PoetryRecordDao dao;
public PoetryRepository(Context context) {
ApiKeyProvider apiKeyProvider = new ApiKeyProvider(context);
generator = new PoetryGenerator(new OpenAICompletionService(null), apiKeyProvider);
dao = AppDatabase.getInstance(context).poetryRecordDao();
}
public String generate(String poetryTitle, String author, String dynasty, String extraRequirements) throws Exception {
return generator.generate(poetryTitle, author, dynasty, extraRequirements);
}
public void saveRecord(PoetryRecord record) {
new Thread(() -> {
dao.insert(record);
dao.keepOnlyRecent();
}).start();
}
public LiveData<List<PoetryRecord>> getRecentRecords() {
return dao.getRecentRecords();
}
}

View File

@@ -0,0 +1,34 @@
package com.ruilaizi.example.data.resume;
import com.ruilaizi.example.data.api.ApiKeyProvider;
import com.ruilaizi.example.data.api.OpenAICompletionService;
/**
* 简历/求职信优化生成器,与 Flask resume_optimization 逻辑一致。
*/
public class ResumeOptimizationGenerator {
private final OpenAICompletionService completionService;
private final ApiKeyProvider apiKeyProvider;
public ResumeOptimizationGenerator(OpenAICompletionService completionService, ApiKeyProvider apiKeyProvider) {
this.completionService = completionService;
this.apiKeyProvider = apiKeyProvider;
}
public String generate(String optType, String originalContent, String jobDescription) throws Exception {
String apiKey = apiKeyProvider.getApiKey();
if (apiKey == null || apiKey.isEmpty()) throw new Exception("请先在设置中配置 API Key");
if (originalContent == null || originalContent.trim().isEmpty()) throw new Exception("请填写简历内容或求职信要点");
String systemPrompt;
String userPrompt;
if ("cover_letter".equals(optType)) {
systemPrompt = "你是一位专业的求职顾问,擅长根据简历和岗位描述撰写针对性的求职信。要求:根据用户提供的简历要点和岗位描述(如有),写一封简洁、得体的求职信。结构建议:开头称呼与应聘意向、与岗位匹配的经历与能力、结尾表达意愿与感谢。语气专业、诚恳,篇幅适中(一般 300500 字)。";
userPrompt = "请根据以下内容撰写求职信:\n\n【简历/个人要点】\n" + originalContent.trim() + "\n\n" + (jobDescription != null && !jobDescription.isEmpty() ? "【岗位描述】\n" + jobDescription + "\n" : "");
} else {
systemPrompt = "你是一位专业的简历优化师,擅长在保持真实的前提下提升简历的呈现效果。要求:根据用户提供的简历内容进行优化:润色表述、突出成果与关键词、调整结构层次。若用户提供了岗位描述,请使经历与能力描述更贴合该岗位。输出使用 Markdown 格式,保持条理清晰。不要编造经历或数据,仅做表述优化。";
userPrompt = "请优化以下简历内容:\n\n" + originalContent.trim() + "\n\n" + (jobDescription != null && !jobDescription.isEmpty() ? "【岗位描述(供针对性优化)】\n" + jobDescription + "\n" : "");
}
return completionService.chat(systemPrompt, userPrompt, 0.5f, 2000, apiKey);
}
}

View File

@@ -0,0 +1,40 @@
package com.ruilaizi.example.data.resume;
import android.content.Context;
import androidx.lifecycle.LiveData;
import com.ruilaizi.example.data.api.ApiKeyProvider;
import com.ruilaizi.example.data.api.OpenAICompletionService;
import com.ruilaizi.example.data.db.AppDatabase;
import com.ruilaizi.example.data.db.ResumeOptimizationRecord;
import com.ruilaizi.example.data.db.ResumeOptimizationRecordDao;
import java.util.List;
public class ResumeOptimizationRepository {
private final ResumeOptimizationGenerator generator;
private final ResumeOptimizationRecordDao dao;
public ResumeOptimizationRepository(Context context) {
ApiKeyProvider apiKeyProvider = new ApiKeyProvider(context);
generator = new ResumeOptimizationGenerator(new OpenAICompletionService(null), apiKeyProvider);
dao = AppDatabase.getInstance(context).resumeOptimizationRecordDao();
}
public String generate(String optType, String originalContent, String jobDescription) throws Exception {
return generator.generate(optType, originalContent, jobDescription);
}
public void saveRecord(ResumeOptimizationRecord record) {
new Thread(() -> {
dao.insert(record);
dao.keepOnlyRecent();
}).start();
}
public LiveData<List<ResumeOptimizationRecord>> getRecentRecords() {
return dao.getRecentRecords();
}
}

View File

@@ -2,19 +2,39 @@ package com.ruilaizi.example.data.travel;
import android.content.Context;
import androidx.lifecycle.LiveData;
import com.ruilaizi.example.data.api.ApiKeyProvider;
import com.ruilaizi.example.data.api.OpenAICompletionService;
import com.ruilaizi.example.data.db.AppDatabase;
import com.ruilaizi.example.data.db.TravelPlanRecord;
import com.ruilaizi.example.data.db.TravelPlanRecordDao;
import java.util.List;
public class TravelPlanningRepository {
private final TravelPlanGenerator generator;
private final TravelPlanRecordDao dao;
public TravelPlanningRepository(Context context) {
ApiKeyProvider apiKeyProvider = new ApiKeyProvider(context);
generator = new TravelPlanGenerator(new OpenAICompletionService(null), apiKeyProvider);
dao = AppDatabase.getInstance(context).travelPlanRecordDao();
}
public String generate(String destination, String days, String people, String preferences, String budget) throws Exception {
return generator.generate(destination, days, people, preferences, budget);
}
public void saveRecord(TravelPlanRecord record) {
new Thread(() -> {
dao.insert(record);
dao.keepOnlyRecent();
}).start();
}
public LiveData<List<TravelPlanRecord>> getRecentRecords() {
return dao.getRecentRecords();
}
}

View File

@@ -2,19 +2,39 @@ package com.ruilaizi.example.data.weekly;
import android.content.Context;
import androidx.lifecycle.LiveData;
import com.ruilaizi.example.data.api.ApiKeyProvider;
import com.ruilaizi.example.data.api.OpenAICompletionService;
import com.ruilaizi.example.data.db.AppDatabase;
import com.ruilaizi.example.data.db.WeeklyReportRecord;
import com.ruilaizi.example.data.db.WeeklyReportRecordDao;
import java.util.List;
public class WeeklyReportRepository {
private final WeeklyReportGenerator generator;
private final WeeklyReportRecordDao dao;
public WeeklyReportRepository(Context context) {
ApiKeyProvider apiKeyProvider = new ApiKeyProvider(context);
generator = new WeeklyReportGenerator(new OpenAICompletionService(null), apiKeyProvider);
dao = AppDatabase.getInstance(context).weeklyReportRecordDao();
}
public String generate(String reportType, String content) throws Exception {
return generator.generate(reportType, content);
}
public void saveRecord(WeeklyReportRecord record) {
new Thread(() -> {
dao.insert(record);
dao.keepOnlyRecent();
}).start();
}
public LiveData<List<WeeklyReportRecord>> getRecentRecords() {
return dao.getRecentRecords();
}
}

View File

@@ -39,6 +39,9 @@ public class MeetingMinutesViewModel extends AndroidViewModel {
try {
String summary = repository.generate(rawContent, title);
resultLiveData.postValue(summary);
if (summary != null && !summary.isEmpty()) {
repository.saveRecord(new com.ruilaizi.example.data.db.MeetingMinutesRecord(title, rawContent, summary));
}
snackbarLiveData.postValue("会议纪要生成完成");
} catch (Exception e) {
errorLiveData.postValue(e != null ? e.getMessage() : "生成失败");

View File

@@ -0,0 +1,63 @@
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.db.PoetryRecord;
import com.ruilaizi.example.data.poetry.PoetryRepository;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class PoetryViewModel extends AndroidViewModel {
private final PoetryRepository 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 PoetryViewModel(@NonNull Application app) {
super(app);
repository = new PoetryRepository(app);
}
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(String poetryTitle, String author, String dynasty, String extraRequirements) {
loadingLiveData.setValue(true);
resultLiveData.setValue("");
errorLiveData.setValue(null);
executor.execute(() -> {
try {
String content = repository.generate(poetryTitle, author, dynasty, extraRequirements);
resultLiveData.postValue(content);
if (content != null && !content.isEmpty()) {
repository.saveRecord(new PoetryRecord(poetryTitle, author, dynasty, content));
}
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

@@ -0,0 +1,63 @@
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.db.ResumeOptimizationRecord;
import com.ruilaizi.example.data.resume.ResumeOptimizationRepository;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ResumeOptimizationViewModel extends AndroidViewModel {
private final ResumeOptimizationRepository 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 ResumeOptimizationViewModel(@NonNull Application app) {
super(app);
repository = new ResumeOptimizationRepository(app);
}
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(String optType, String originalContent, String jobDescription) {
loadingLiveData.setValue(true);
resultLiveData.setValue("");
errorLiveData.setValue(null);
executor.execute(() -> {
try {
String content = repository.generate(optType, originalContent, jobDescription);
resultLiveData.postValue(content);
if (content != null && !content.isEmpty()) {
repository.saveRecord(new ResumeOptimizationRecord(optType, originalContent, jobDescription, content));
}
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

@@ -39,6 +39,9 @@ public class TravelPlanningViewModel extends AndroidViewModel {
try {
String plan = repository.generate(destination, days, people, preferences, budget);
resultLiveData.postValue(plan);
if (plan != null && !plan.isEmpty()) {
repository.saveRecord(new com.ruilaizi.example.data.db.TravelPlanRecord(destination, days, people, preferences, budget, plan));
}
snackbarLiveData.postValue("旅行攻略生成完成");
} catch (Exception e) {
errorLiveData.postValue(e != null ? e.getMessage() : "生成失败");

View File

@@ -39,6 +39,9 @@ public class WeeklyReportViewModel extends AndroidViewModel {
try {
String report = repository.generate(reportType, content);
resultLiveData.postValue(report);
if (report != null && !report.isEmpty()) {
repository.saveRecord(new com.ruilaizi.example.data.db.WeeklyReportRecord(reportType, content, report));
}
snackbarLiveData.postValue("周报/日报生成完成");
} catch (Exception e) {
errorLiveData.postValue(e != null ? e.getMessage() : "生成失败");

View File

@@ -33,7 +33,12 @@
android:textStyle="bold"
android:textColor="@color/colorPrimary"
android:gravity="center" />
<View android:layout_width="48dp" android:layout_height="wrap_content" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_history"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/btn_history" />
</LinearLayout>
<com.google.android.material.card.MaterialCardView

View File

@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
android:background="@color/colorSurface">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:padding="16dp"
android:background="@color/colorCard">
<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="@string/history_back"
android:textColor="@color/colorPrimary" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/meeting_minutes_history_title"
android:textColor="@color/colorPrimary"
android:textSize="20sp"
android:textStyle="bold" />
</LinearLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="12dp"
android:clipToPadding="false"
tools:listitem="@layout/item_meeting_minutes_history" />
<TextView
android:id="@+id/empty_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/meeting_minutes_history_empty"
android:textColor="@color/colorTextSecondary"
android:visibility="gone" />
</FrameLayout>
</LinearLayout>

View File

@@ -0,0 +1,112 @@
<?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: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="古诗词解析"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="@color/colorPrimary"
android:gravity="center" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_history"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/btn_history" />
</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:textSize="12sp" android:textColor="@color/colorTextSecondary" android:layout_marginBottom="4dp" />
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_poetry_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="如:静夜思"
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_author"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="如:李白"
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_dynasty"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="如:唐"
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_extra"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minLines="2"
android:hint="翻译风格、解读深度等"
android:inputType="textMultiLine"
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="生成解析"
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="80dp"
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:textSize="14sp" android:lineSpacingMultiplier="1.2" android:textColor="@color/colorText" />
</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

@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
android:background="@color/colorSurface">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:padding="16dp"
android:background="@color/colorCard">
<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="@string/history_back"
android:textColor="@color/colorPrimary" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/poetry_history_title"
android:textColor="@color/colorPrimary"
android:textSize="20sp"
android:textStyle="bold" />
</LinearLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="12dp"
android:clipToPadding="false"
tools:listitem="@layout/item_poetry_history" />
<TextView
android:id="@+id/empty_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/poetry_history_empty"
android:textColor="@color/colorTextSecondary"
android:visibility="gone" />
</FrameLayout>
</LinearLayout>

View File

@@ -0,0 +1,106 @@
<?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: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="简历/求职信优化"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="@color/colorPrimary"
android:gravity="center" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_history"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/btn_history" />
</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:textSize="12sp" android:textColor="@color/colorTextSecondary" android:layout_marginBottom="4dp" />
<Spinner
android:id="@+id/spinner_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_original"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minLines="4"
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_job_desc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minLines="2"
android:hint="岗位描述便于针对性优化"
android:inputType="textMultiLine"
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="生成优化"
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="80dp"
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:textSize="14sp" android:lineSpacingMultiplier="1.2" android:textColor="@color/colorText" />
</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

@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
android:background="@color/colorSurface">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:padding="16dp"
android:background="@color/colorCard">
<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="@string/history_back"
android:textColor="@color/colorPrimary" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/resume_history_title"
android:textColor="@color/colorPrimary"
android:textSize="20sp"
android:textStyle="bold" />
</LinearLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="12dp"
android:clipToPadding="false"
tools:listitem="@layout/item_resume_history" />
<TextView
android:id="@+id/empty_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/resume_history_empty"
android:textColor="@color/colorTextSecondary"
android:visibility="gone" />
</FrameLayout>
</LinearLayout>

View File

@@ -35,7 +35,12 @@
android:textSize="20sp"
android:textStyle="bold"
android:gravity="center" />
<View android:layout_width="48dp" android:layout_height="wrap_content" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_history"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/btn_history" />
</LinearLayout>
<!-- 旅行参数卡片 -->

View File

@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
android:background="@color/colorSurface">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:padding="16dp"
android:background="@color/colorCard">
<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="@string/history_back"
android:textColor="@color/colorPrimary" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/travel_history_title"
android:textColor="@color/colorPrimary"
android:textSize="20sp"
android:textStyle="bold" />
</LinearLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="12dp"
android:clipToPadding="false"
tools:listitem="@layout/item_travel_history" />
<TextView
android:id="@+id/empty_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/travel_history_empty"
android:textColor="@color/colorTextSecondary"
android:visibility="gone" />
</FrameLayout>
</LinearLayout>

View File

@@ -31,7 +31,12 @@
android:textStyle="bold"
android:textColor="@color/colorPrimary"
android:gravity="center" />
<View android:layout_width="48dp" android:layout_height="wrap_content" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_history"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/btn_history" />
</LinearLayout>
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"

View File

@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
android:background="@color/colorSurface">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:padding="16dp"
android:background="@color/colorCard">
<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="@string/history_back"
android:textColor="@color/colorPrimary" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/weekly_report_history_title"
android:textColor="@color/colorPrimary"
android:textSize="20sp"
android:textStyle="bold" />
</LinearLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="12dp"
android:clipToPadding="false"
tools:listitem="@layout/item_weekly_report_history" />
<TextView
android:id="@+id/empty_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/weekly_report_history_empty"
android:textColor="@color/colorTextSecondary"
android:visibility="gone" />
</FrameLayout>
</LinearLayout>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView 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="wrap_content"
app:cardBackgroundColor="@color/colorCard"
app:cardElevation="2dp"
app:cardCornerRadius="12dp"
android:layout_marginBottom="12dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView android:id="@+id/tv_summary" android:layout_width="match_parent" android:layout_height="wrap_content" android:textColor="@color/colorTextDark" android:textSize="14sp" android:maxLines="2" android:ellipsize="end" tools:text="产品需求评审" />
<TextView android:id="@+id/tv_content_preview" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="6dp" android:textColor="@color/colorText" android:textSize="12sp" android:maxLines="3" android:ellipsize="end" tools:text="一、会议概要…" />
<TextView android:id="@+id/tv_time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="6dp" android:textColor="@color/colorTextSecondary" android:textSize="11sp" tools:text="2025-03-02 14:30" />
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:layout_marginTop="8dp">
<com.google.android.material.button.MaterialButton android:id="@+id/btn_use" style="@style/Widget.MaterialComponents.Button.TextButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/history_use" />
<com.google.android.material.button.MaterialButton android:id="@+id/btn_copy" style="@style/Widget.MaterialComponents.Button.TextButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/btn_copy" />
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView 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="wrap_content"
app:cardBackgroundColor="@color/colorCard"
app:cardElevation="2dp"
app:cardCornerRadius="12dp"
android:layout_marginBottom="12dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView android:id="@+id/tv_summary" android:layout_width="match_parent" android:layout_height="wrap_content" android:textColor="@color/colorTextDark" android:textSize="14sp" android:maxLines="2" android:ellipsize="end" tools:text="产品需求评审" />
<TextView android:id="@+id/tv_content_preview" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="6dp" android:textColor="@color/colorText" android:textSize="12sp" android:maxLines="3" android:ellipsize="end" tools:text="一、会议概要…" />
<TextView android:id="@+id/tv_time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="6dp" android:textColor="@color/colorTextSecondary" android:textSize="11sp" tools:text="2025-03-02 14:30" />
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:layout_marginTop="8dp">
<com.google.android.material.button.MaterialButton android:id="@+id/btn_use" style="@style/Widget.MaterialComponents.Button.TextButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/history_use" />
<com.google.android.material.button.MaterialButton android:id="@+id/btn_copy" style="@style/Widget.MaterialComponents.Button.TextButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/btn_copy" />
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView 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="wrap_content"
app:cardBackgroundColor="@color/colorCard"
app:cardElevation="2dp"
app:cardCornerRadius="12dp"
android:layout_marginBottom="12dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView android:id="@+id/tv_summary" android:layout_width="match_parent" android:layout_height="wrap_content" android:textColor="@color/colorTextDark" android:textSize="14sp" android:maxLines="2" android:ellipsize="end" tools:text="产品需求评审" />
<TextView android:id="@+id/tv_content_preview" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="6dp" android:textColor="@color/colorText" android:textSize="12sp" android:maxLines="3" android:ellipsize="end" tools:text="一、会议概要…" />
<TextView android:id="@+id/tv_time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="6dp" android:textColor="@color/colorTextSecondary" android:textSize="11sp" tools:text="2025-03-02 14:30" />
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:layout_marginTop="8dp">
<com.google.android.material.button.MaterialButton android:id="@+id/btn_use" style="@style/Widget.MaterialComponents.Button.TextButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/history_use" />
<com.google.android.material.button.MaterialButton android:id="@+id/btn_copy" style="@style/Widget.MaterialComponents.Button.TextButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/btn_copy" />
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>

View File

@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView 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="wrap_content"
app:cardBackgroundColor="@color/colorCard"
app:cardElevation="2dp"
app:cardCornerRadius="12dp"
android:layout_marginBottom="12dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/tv_summary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/colorTextDark"
android:textSize="14sp"
android:maxLines="2"
android:ellipsize="end"
tools:text="家乡:四川 | 午餐 | 2人 | 50-100元" />
<TextView
android:id="@+id/tv_content_preview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:textColor="@color/colorText"
android:textSize="12sp"
android:maxLines="3"
android:ellipsize="end"
tools:text="一、菜品推荐与搭配思路…" />
<TextView
android:id="@+id/tv_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:textColor="@color/colorTextSecondary"
android:textSize="11sp"
tools:text="2025-03-02 14:30" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="8dp">
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_use"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/history_use" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_copy"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/btn_copy" />
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView 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="wrap_content"
app:cardBackgroundColor="@color/colorCard"
app:cardElevation="2dp"
app:cardCornerRadius="12dp"
android:layout_marginBottom="12dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView android:id="@+id/tv_summary" android:layout_width="match_parent" android:layout_height="wrap_content" android:textColor="@color/colorTextDark" android:textSize="14sp" android:maxLines="2" android:ellipsize="end" tools:text="周报" />
<TextView android:id="@+id/tv_content_preview" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="6dp" android:textColor="@color/colorText" android:textSize="12sp" android:maxLines="3" android:ellipsize="end" tools:text="一、本周工作完成情况…" />
<TextView android:id="@+id/tv_time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="6dp" android:textColor="@color/colorTextSecondary" android:textSize="11sp" tools:text="2025-03-02 14:30" />
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:layout_marginTop="8dp">
<com.google.android.material.button.MaterialButton android:id="@+id/btn_use" style="@style/Widget.MaterialComponents.Button.TextButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/history_use" />
<com.google.android.material.button.MaterialButton android:id="@+id/btn_copy" style="@style/Widget.MaterialComponents.Button.TextButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/btn_copy" />
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>

View File

@@ -19,6 +19,9 @@
<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>
<string-array name="resume_opt_types">
<item>简历优化</item><item>求职信</item>
</string-array>
<declare-styleable name="CircleImageView">
<attr name="border_width" format="dimension" />
<attr name="border_color" format="color" />

View File

@@ -39,6 +39,16 @@
<string name="btn_ai_services">AI应用</string>
<string name="meal_planning_history_title">规划历史</string>
<string name="meal_planning_history_empty">暂无规划记录</string>
<string name="travel_history_title">旅行攻略历史</string>
<string name="travel_history_empty">暂无旅行攻略记录</string>
<string name="weekly_report_history_title">周报/日报历史</string>
<string name="weekly_report_history_empty">暂无周报记录</string>
<string name="meeting_minutes_history_title">会议纪要历史</string>
<string name="meeting_minutes_history_empty">暂无会议纪要记录</string>
<string name="poetry_history_title">古诗词解析历史</string>
<string name="poetry_history_empty">暂无解析记录</string>
<string name="resume_history_title">简历优化历史</string>
<string name="resume_history_empty">暂无优化记录</string>
<string name="placeholder_coming_soon">敬请期待</string>
<string name="ai_services_title">AI 智能应用</string>
<string name="ai_services_subtitle">探索更多 AI 应用</string>