历史记录功能
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:
2026-03-04 21:47:16 +08:00
parent 4973b90d91
commit b4fca4a338
245 changed files with 3558 additions and 26770 deletions

View File

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

View File

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

View File

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

View File

@@ -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建议使用后端网关不在此填写");

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

@@ -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() : "优化失败");

View 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>

View File

@@ -28,6 +28,13 @@
android:textColor="@color/colorPrimary"
android:textSize="24sp"
android:textStyle="bold" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_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"

View 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>

View 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>

View File

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