历史记录功能
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
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:
@@ -35,6 +35,7 @@ dependencies {
|
||||
implementation 'com.google.android.material:material:1.11.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
implementation 'androidx.core:core:1.12.0'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.3.2'
|
||||
|
||||
// ViewModel & LiveData
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel:2.7.0'
|
||||
|
||||
@@ -22,6 +22,10 @@
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".HistoryActivity"
|
||||
android:exported="false"
|
||||
android:screenOrientation="portrait" />
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
package com.ruilaizi.example;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.button.MaterialButton;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import com.ruilaizi.example.adapter.GenerationHistoryAdapter;
|
||||
import com.ruilaizi.example.adapter.OptimizeHistoryAdapter;
|
||||
import com.ruilaizi.example.databinding.ActivityHistoryBinding;
|
||||
import com.ruilaizi.example.ui.HistoryViewModel;
|
||||
|
||||
|
||||
/**
|
||||
* 生成历史 / 优化历史 页面。
|
||||
*/
|
||||
public class HistoryActivity extends AppCompatActivity {
|
||||
|
||||
public static final String EXTRA_USE_PROMPT = "use_prompt";
|
||||
|
||||
private ActivityHistoryBinding binding;
|
||||
private HistoryViewModel viewModel;
|
||||
private OptimizeHistoryAdapter optimizeAdapter;
|
||||
private GenerationHistoryAdapter generationAdapter;
|
||||
private int currentTab = 0;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
binding = ActivityHistoryBinding.inflate(getLayoutInflater());
|
||||
setContentView(binding.getRoot());
|
||||
|
||||
viewModel = new ViewModelProvider(this, new ViewModelProvider.AndroidViewModelFactory(getApplication())).get(HistoryViewModel.class);
|
||||
|
||||
setupRecyclers();
|
||||
setupTabs();
|
||||
setupBack();
|
||||
observeData();
|
||||
}
|
||||
|
||||
private void setupRecyclers() {
|
||||
optimizeAdapter = new OptimizeHistoryAdapter();
|
||||
optimizeAdapter.setOnUseOptimizeListener(prompt -> {
|
||||
Intent data = new Intent();
|
||||
data.putExtra(EXTRA_USE_PROMPT, prompt);
|
||||
setResult(RESULT_OK, data);
|
||||
finish();
|
||||
});
|
||||
|
||||
generationAdapter = new GenerationHistoryAdapter();
|
||||
generationAdapter.setOnUseGenerationListener(prompt -> {
|
||||
Intent data = new Intent();
|
||||
data.putExtra(EXTRA_USE_PROMPT, prompt);
|
||||
setResult(RESULT_OK, data);
|
||||
finish();
|
||||
});
|
||||
|
||||
binding.recyclerOptimize.setLayoutManager(new LinearLayoutManager(this));
|
||||
binding.recyclerOptimize.setAdapter(optimizeAdapter);
|
||||
|
||||
binding.recyclerGeneration.setLayoutManager(new LinearLayoutManager(this));
|
||||
binding.recyclerGeneration.setAdapter(generationAdapter);
|
||||
}
|
||||
|
||||
private void setupTabs() {
|
||||
binding.tabHistory.addTab(binding.tabHistory.newTab().setText(R.string.history_tab_optimize));
|
||||
binding.tabHistory.addTab(binding.tabHistory.newTab().setText(R.string.history_tab_generation));
|
||||
|
||||
binding.tabHistory.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
|
||||
@Override
|
||||
public void onTabSelected(TabLayout.Tab tab) {
|
||||
switchTab(tab.getPosition());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabUnselected(TabLayout.Tab tab) {}
|
||||
|
||||
@Override
|
||||
public void onTabReselected(TabLayout.Tab tab) {}
|
||||
});
|
||||
}
|
||||
|
||||
private void switchTab(int index) {
|
||||
currentTab = index;
|
||||
if (index == 0) {
|
||||
binding.recyclerOptimize.setVisibility(View.VISIBLE);
|
||||
binding.recyclerGeneration.setVisibility(View.GONE);
|
||||
updateEmptyVisibility();
|
||||
} else {
|
||||
binding.recyclerOptimize.setVisibility(View.GONE);
|
||||
binding.recyclerGeneration.setVisibility(View.VISIBLE);
|
||||
updateEmptyVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
private void setupBack() {
|
||||
binding.btnBack.setOnClickListener(v -> finish());
|
||||
}
|
||||
|
||||
private void observeData() {
|
||||
viewModel.getOptimizeRecords().observe(this, list -> {
|
||||
optimizeAdapter.setData(list);
|
||||
updateEmptyVisibility();
|
||||
});
|
||||
viewModel.getGenerationRecords().observe(this, list -> {
|
||||
generationAdapter.setData(list);
|
||||
updateEmptyVisibility();
|
||||
});
|
||||
}
|
||||
|
||||
private void updateEmptyVisibility() {
|
||||
boolean optEmpty = optimizeAdapter.getItemCount() == 0;
|
||||
boolean genEmpty = generationAdapter.getItemCount() == 0;
|
||||
binding.emptyOptimize.setVisibility(currentTab == 0 && optEmpty ? View.VISIBLE : View.GONE);
|
||||
binding.emptyGeneration.setVisibility(currentTab == 1 && genEmpty ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.ruilaizi.example;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
@@ -44,6 +45,9 @@ public class MainActivity extends AppCompatActivity {
|
||||
if (binding.btnSettings != null) {
|
||||
binding.btnSettings.setOnClickListener(v -> showApiKeyDialog());
|
||||
}
|
||||
if (binding.btnHistory != null) {
|
||||
binding.btnHistory.setOnClickListener(v -> openHistory());
|
||||
}
|
||||
observeViewModel();
|
||||
showApiKeyHintIfNeeded();
|
||||
}
|
||||
@@ -186,6 +190,28 @@ public class MainActivity extends AppCompatActivity {
|
||||
}
|
||||
}
|
||||
|
||||
private static final int REQ_HISTORY = 1001;
|
||||
|
||||
private void openHistory() {
|
||||
startActivityForResult(new Intent(this, HistoryActivity.class), REQ_HISTORY);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if (requestCode == REQ_HISTORY && resultCode == RESULT_OK && data != null) {
|
||||
String prompt = data.getStringExtra(HistoryActivity.EXTRA_USE_PROMPT);
|
||||
if (prompt != null && !prompt.isEmpty()) {
|
||||
TextInputEditText input = binding.inputPrompt;
|
||||
if (input != null) {
|
||||
input.setText(prompt);
|
||||
viewModel.setPrompt(prompt);
|
||||
}
|
||||
Snackbar.make(binding.getRoot(), "已填入提示词", Snackbar.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void showApiKeyDialog() {
|
||||
android.widget.EditText edit = new android.widget.EditText(this);
|
||||
edit.setHint("API Key(建议使用后端网关,不在此填写)");
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
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.GenerationRecord;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class GenerationHistoryAdapter extends RecyclerView.Adapter<GenerationHistoryAdapter.ViewHolder> {
|
||||
|
||||
private static final String[] LENGTH_LABELS = {"短", "中", "长"};
|
||||
private static final String[] TEMP_LABELS = {"保守", "平衡", "创意"};
|
||||
|
||||
private final List<GenerationRecord> list = new ArrayList<>();
|
||||
private OnUseGenerationListener onUseGenerationListener;
|
||||
|
||||
public interface OnUseGenerationListener {
|
||||
void onUseGeneration(String prompt);
|
||||
}
|
||||
|
||||
public void setOnUseGenerationListener(OnUseGenerationListener listener) {
|
||||
this.onUseGenerationListener = listener;
|
||||
}
|
||||
|
||||
public void setData(List<GenerationRecord> 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_generation_history, parent, false);
|
||||
return new ViewHolder(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
GenerationRecord r = list.get(position);
|
||||
holder.tvPrompt.setText(truncate(r.prompt, 80));
|
||||
holder.tvResult.setText(truncate(r.result, 150));
|
||||
holder.tvParams.setText("长度:" + getLengthLabel(r.maxTokensLevel) + " | 创意:" + getTempLabel(r.temperatureLevel));
|
||||
holder.tvTime.setText(formatTime(r.createdAt));
|
||||
|
||||
holder.btnUse.setOnClickListener(v -> {
|
||||
if (onUseGenerationListener != null && r.prompt != null) {
|
||||
onUseGenerationListener.onUseGeneration(r.prompt);
|
||||
}
|
||||
});
|
||||
|
||||
holder.btnCopy.setOnClickListener(v -> {
|
||||
if (r.result != null && !r.result.isEmpty()) {
|
||||
ClipboardManager cm = (ClipboardManager) holder.itemView.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
if (cm != null) {
|
||||
cm.setPrimaryClip(ClipData.newPlainText("AI生成", r.result));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return list.size();
|
||||
}
|
||||
|
||||
private static String getLengthLabel(int level) {
|
||||
if (level < 0 || level >= LENGTH_LABELS.length) return "中";
|
||||
return LENGTH_LABELS[level];
|
||||
}
|
||||
|
||||
private static String getTempLabel(int level) {
|
||||
if (level < 0 || level >= TEMP_LABELS.length) return "平衡";
|
||||
return TEMP_LABELS[level];
|
||||
}
|
||||
|
||||
private static String truncate(String s, int maxLen) {
|
||||
if (s == null) return "";
|
||||
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 tvPrompt, tvResult, tvParams, tvTime;
|
||||
MaterialButton btnUse, btnCopy;
|
||||
|
||||
ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
tvPrompt = itemView.findViewById(R.id.tv_prompt);
|
||||
tvResult = itemView.findViewById(R.id.tv_result);
|
||||
tvParams = itemView.findViewById(R.id.tv_params);
|
||||
tvTime = itemView.findViewById(R.id.tv_time);
|
||||
btnUse = itemView.findViewById(R.id.btn_use);
|
||||
btnCopy = itemView.findViewById(R.id.btn_copy);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
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.OptimizeRecord;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class OptimizeHistoryAdapter extends RecyclerView.Adapter<OptimizeHistoryAdapter.ViewHolder> {
|
||||
|
||||
private final List<OptimizeRecord> list = new ArrayList<>();
|
||||
private OnUseOptimizeListener onUseOptimizeListener;
|
||||
|
||||
public interface OnUseOptimizeListener {
|
||||
void onUseOptimize(String optimizedPrompt);
|
||||
}
|
||||
|
||||
public void setOnUseOptimizeListener(OnUseOptimizeListener listener) {
|
||||
this.onUseOptimizeListener = listener;
|
||||
}
|
||||
|
||||
public void setData(List<OptimizeRecord> 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_optimize_history, parent, false);
|
||||
return new ViewHolder(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
OptimizeRecord r = list.get(position);
|
||||
holder.tvIntent.setText(r.intentSummary != null ? r.intentSummary : "");
|
||||
holder.tvUserInput.setText("输入:" + truncate(r.userInput, 60));
|
||||
holder.tvOptimizedPrompt.setText(truncate(r.optimizedPrompt, 120));
|
||||
holder.tvTime.setText(formatTime(r.createdAt));
|
||||
|
||||
holder.btnUse.setOnClickListener(v -> {
|
||||
if (onUseOptimizeListener != null && r.optimizedPrompt != null) {
|
||||
onUseOptimizeListener.onUseOptimize(r.optimizedPrompt);
|
||||
}
|
||||
});
|
||||
|
||||
holder.itemView.setOnLongClickListener(v -> {
|
||||
if (r.optimizedPrompt != null && !r.optimizedPrompt.isEmpty()) {
|
||||
ClipboardManager cm = (ClipboardManager) holder.itemView.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
if (cm != null) {
|
||||
cm.setPrimaryClip(ClipData.newPlainText("优化提示词", r.optimizedPrompt));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return list.size();
|
||||
}
|
||||
|
||||
private static String truncate(String s, int maxLen) {
|
||||
if (s == null) return "";
|
||||
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 tvIntent, tvUserInput, tvOptimizedPrompt, tvTime;
|
||||
MaterialButton btnUse;
|
||||
|
||||
ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
tvIntent = itemView.findViewById(R.id.tv_intent);
|
||||
tvUserInput = itemView.findViewById(R.id.tv_user_input);
|
||||
tvOptimizedPrompt = itemView.findViewById(R.id.tv_optimized_prompt);
|
||||
tvTime = itemView.findViewById(R.id.tv_time);
|
||||
btnUse = itemView.findViewById(R.id.btn_use);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import androidx.room.Database;
|
||||
import androidx.room.Room;
|
||||
import androidx.room.RoomDatabase;
|
||||
|
||||
@Database(entities = {GenerationRecord.class}, version = 1, exportSchema = false)
|
||||
@Database(entities = {GenerationRecord.class, OptimizeRecord.class}, version = 2, exportSchema = false)
|
||||
public abstract class AppDatabase extends RoomDatabase {
|
||||
|
||||
private static volatile AppDatabase INSTANCE;
|
||||
@@ -19,7 +19,7 @@ public abstract class AppDatabase extends RoomDatabase {
|
||||
context.getApplicationContext(),
|
||||
AppDatabase.class,
|
||||
"wensiquanyong_db"
|
||||
).build();
|
||||
).fallbackToDestructiveMigration().build();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,4 +27,5 @@ public abstract class AppDatabase extends RoomDatabase {
|
||||
}
|
||||
|
||||
public abstract GenerationRecordDao generationRecordDao();
|
||||
public abstract OptimizeRecordDao optimizeRecordDao();
|
||||
}
|
||||
|
||||
@@ -13,9 +13,9 @@ public interface GenerationRecordDao {
|
||||
@Insert
|
||||
long insert(GenerationRecord record);
|
||||
|
||||
@Query("SELECT * FROM generation_records ORDER BY createdAt DESC LIMIT 10")
|
||||
@Query("SELECT * FROM generation_records ORDER BY createdAt DESC LIMIT 50")
|
||||
LiveData<List<GenerationRecord>> getRecentRecords();
|
||||
|
||||
@Query("DELETE FROM generation_records WHERE id NOT IN (SELECT id FROM generation_records ORDER BY createdAt DESC LIMIT 10)")
|
||||
@Query("DELETE FROM generation_records WHERE id NOT IN (SELECT id FROM generation_records ORDER BY createdAt DESC LIMIT 50)")
|
||||
void keepOnlyRecent();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.ruilaizi.example.data.db;
|
||||
|
||||
import androidx.room.Entity;
|
||||
import androidx.room.Ignore;
|
||||
import androidx.room.PrimaryKey;
|
||||
|
||||
/**
|
||||
* 单条提示词优化记录,用于 Room 缓存。
|
||||
*/
|
||||
@Entity(tableName = "optimize_records")
|
||||
public class OptimizeRecord {
|
||||
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
public long id;
|
||||
|
||||
/** 用户输入的原始需求 */
|
||||
public String userInput;
|
||||
/** 优化后的完整提示词 */
|
||||
public String optimizedPrompt;
|
||||
/** 意图分析摘要(核心意图、领域等) */
|
||||
public String intentSummary;
|
||||
/** 创建时间戳 */
|
||||
public long createdAt;
|
||||
|
||||
public OptimizeRecord() {}
|
||||
|
||||
@Ignore
|
||||
public OptimizeRecord(String userInput, String optimizedPrompt, String intentSummary) {
|
||||
this.userInput = userInput;
|
||||
this.optimizedPrompt = optimizedPrompt;
|
||||
this.intentSummary = intentSummary;
|
||||
this.createdAt = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
@@ -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 OptimizeRecordDao {
|
||||
|
||||
@Insert
|
||||
long insert(OptimizeRecord record);
|
||||
|
||||
@Query("SELECT * FROM optimize_records ORDER BY createdAt DESC LIMIT 50")
|
||||
LiveData<List<OptimizeRecord>> getRecentRecords();
|
||||
|
||||
@Query("DELETE FROM optimize_records WHERE id NOT IN (SELECT id FROM optimize_records ORDER BY createdAt DESC LIMIT 50)")
|
||||
void keepOnlyRecent();
|
||||
}
|
||||
@@ -11,6 +11,8 @@ import com.ruilaizi.example.data.api.StreamCallback;
|
||||
import com.ruilaizi.example.data.db.AppDatabase;
|
||||
import com.ruilaizi.example.data.db.GenerationRecord;
|
||||
import com.ruilaizi.example.data.db.GenerationRecordDao;
|
||||
import com.ruilaizi.example.data.db.OptimizeRecord;
|
||||
import com.ruilaizi.example.data.db.OptimizeRecordDao;
|
||||
import com.ruilaizi.example.data.prompt.PromptGenerator;
|
||||
|
||||
/**
|
||||
@@ -20,13 +22,15 @@ public class GenerationRepository {
|
||||
|
||||
private final AIService aiService;
|
||||
private final ApiKeyProvider apiKeyProvider;
|
||||
private final GenerationRecordDao dao;
|
||||
private final GenerationRecordDao generationDao;
|
||||
private final OptimizeRecordDao optimizeDao;
|
||||
private final PromptGenerator promptGenerator;
|
||||
|
||||
public GenerationRepository(Context context) {
|
||||
apiKeyProvider = new ApiKeyProvider(context);
|
||||
aiService = new com.ruilaizi.example.data.api.OpenAIStreamService(null);
|
||||
dao = AppDatabase.getInstance(context).generationRecordDao();
|
||||
generationDao = AppDatabase.getInstance(context).generationRecordDao();
|
||||
optimizeDao = AppDatabase.getInstance(context).optimizeRecordDao();
|
||||
promptGenerator = new PromptGenerator(new OpenAICompletionService(null), apiKeyProvider);
|
||||
}
|
||||
|
||||
@@ -72,13 +76,24 @@ public class GenerationRepository {
|
||||
|
||||
public void saveRecord(GenerationRecord record) {
|
||||
new Thread(() -> {
|
||||
dao.insert(record);
|
||||
dao.keepOnlyRecent();
|
||||
generationDao.insert(record);
|
||||
generationDao.keepOnlyRecent();
|
||||
}).start();
|
||||
}
|
||||
|
||||
public void saveOptimizeRecord(OptimizeRecord record) {
|
||||
new Thread(() -> {
|
||||
optimizeDao.insert(record);
|
||||
optimizeDao.keepOnlyRecent();
|
||||
}).start();
|
||||
}
|
||||
|
||||
public LiveData<java.util.List<GenerationRecord>> getRecentRecords() {
|
||||
return dao.getRecentRecords();
|
||||
return generationDao.getRecentRecords();
|
||||
}
|
||||
|
||||
public LiveData<java.util.List<OptimizeRecord>> getRecentOptimizeRecords() {
|
||||
return optimizeDao.getRecentRecords();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.ruilaizi.example.ui;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
import androidx.lifecycle.LiveData;
|
||||
|
||||
import com.ruilaizi.example.data.db.GenerationRecord;
|
||||
import com.ruilaizi.example.data.db.OptimizeRecord;
|
||||
import com.ruilaizi.example.data.repository.GenerationRepository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 历史记录 ViewModel。
|
||||
*/
|
||||
public class HistoryViewModel extends AndroidViewModel {
|
||||
|
||||
private final GenerationRepository repository;
|
||||
|
||||
public HistoryViewModel(@NonNull Application application) {
|
||||
super(application);
|
||||
repository = new GenerationRepository(application);
|
||||
}
|
||||
|
||||
public LiveData<List<OptimizeRecord>> getOptimizeRecords() {
|
||||
return repository.getRecentOptimizeRecords();
|
||||
}
|
||||
|
||||
public LiveData<List<GenerationRecord>> getGenerationRecords() {
|
||||
return repository.getRecentRecords();
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import com.ruilaizi.example.data.api.StreamCallback;
|
||||
import com.ruilaizi.example.data.db.GenerationRecord;
|
||||
import com.ruilaizi.example.data.db.OptimizeRecord;
|
||||
import com.ruilaizi.example.data.prompt.PromptGenerator;
|
||||
import com.ruilaizi.example.data.repository.GenerationRepository;
|
||||
|
||||
@@ -95,6 +96,8 @@ public class MainViewModel extends AndroidViewModel {
|
||||
PromptGenerator.OptimizeResult result = repository.optimizePrompt(inputForOptimize, 0.7f, 1000);
|
||||
String display = result.toDisplayText();
|
||||
resultLiveData.postValue(display);
|
||||
String intentSummary = "核心意图:" + result.coreIntent + " | 领域:" + result.domain + " | 预期输出:" + result.expectedOutput;
|
||||
repository.saveOptimizeRecord(new OptimizeRecord(inputForOptimize, result.generatedPrompt, intentSummary));
|
||||
snackbarLiveData.postValue("提示词优化完成");
|
||||
} catch (Exception e) {
|
||||
errorLiveData.postValue(e != null ? e.getMessage() : "优化失败");
|
||||
|
||||
88
example/app/src/main/res/layout/activity_history.xml
Normal file
88
example/app/src/main/res/layout/activity_history.xml
Normal file
@@ -0,0 +1,88 @@
|
||||
<?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/history_title"
|
||||
android:textColor="@color/colorPrimary"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 切换标签 -->
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/tab_history"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/colorCard"
|
||||
app:tabIndicatorColor="@color/colorPrimary"
|
||||
app:tabSelectedTextColor="@color/colorPrimary"
|
||||
app:tabTextColor="@color/colorTextSecondary"
|
||||
app:tabMode="fixed"
|
||||
app:tabGravity="fill" />
|
||||
|
||||
<!-- 列表内容 -->
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_optimize"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="12dp"
|
||||
android:clipToPadding="false"
|
||||
tools:listitem="@layout/item_optimize_history" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_generation"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone"
|
||||
android:padding="12dp"
|
||||
android:clipToPadding="false"
|
||||
tools:listitem="@layout/item_generation_history" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/empty_optimize"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:text="@string/history_empty_optimize"
|
||||
android:textColor="@color/colorTextSecondary"
|
||||
android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/empty_generation"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:text="@string/history_empty_generation"
|
||||
android:textColor="@color/colorTextSecondary"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
@@ -28,6 +28,13 @@
|
||||
android:textColor="@color/colorPrimary"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold" />
|
||||
<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"
|
||||
android:layout_marginEnd="8dp" />
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_settings"
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||
|
||||
76
example/app/src/main/res/layout/item_generation_history.xml
Normal file
76
example/app/src/main/res/layout/item_generation_history.xml
Normal file
@@ -0,0 +1,76 @@
|
||||
<?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_prompt"
|
||||
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_result"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:textColor="@color/colorText"
|
||||
android:textSize="13sp"
|
||||
android:maxLines="4"
|
||||
android:ellipsize="end"
|
||||
tools:text="生成结果:尊敬的..." />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_params"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:textColor="@color/colorTextSecondary"
|
||||
android:textSize="11sp"
|
||||
tools:text="长度:中 | 创意:平衡" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
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>
|
||||
68
example/app/src/main/res/layout/item_optimize_history.xml
Normal file
68
example/app/src/main/res/layout/item_optimize_history.xml
Normal file
@@ -0,0 +1,68 @@
|
||||
<?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_intent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/colorTextSecondary"
|
||||
android:textSize="12sp"
|
||||
android:maxLines="2"
|
||||
android:ellipsize="end"
|
||||
tools:text="核心意图:创意 | 领域:文案" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_user_input"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:textColor="@color/colorTextDark"
|
||||
android:textSize="14sp"
|
||||
android:maxLines="2"
|
||||
android:ellipsize="end"
|
||||
tools:text="用户输入:写一封工作邮件" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_optimized_prompt"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:textColor="@color/colorText"
|
||||
android:textSize="13sp"
|
||||
android:maxLines="4"
|
||||
android:ellipsize="end"
|
||||
tools:text="优化后提示词:你是一位专业的..." />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:textColor="@color/colorTextSecondary"
|
||||
android:textSize="11sp"
|
||||
tools:text="2025-03-02 14:30" />
|
||||
|
||||
<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:layout_gravity="end"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="@string/history_use" />
|
||||
</LinearLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
@@ -21,4 +21,12 @@
|
||||
<string name="btn_copy">复制</string>
|
||||
<string name="btn_regenerate">重新生成</string>
|
||||
<string name="btn_continue">编辑/续写</string>
|
||||
<string name="btn_history">历史</string>
|
||||
<string name="history_title">生成历史</string>
|
||||
<string name="history_back">返回</string>
|
||||
<string name="history_tab_optimize">优化历史</string>
|
||||
<string name="history_tab_generation">生成历史</string>
|
||||
<string name="history_empty_optimize">暂无优化记录</string>
|
||||
<string name="history_empty_generation">暂无生成记录</string>
|
||||
<string name="history_use">使用</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user