fix 1
This commit is contained in:
1
src-tauri/.gitignore
vendored
1
src-tauri/.gitignore
vendored
@@ -1,6 +1,7 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
/target*/
|
||||
|
||||
# Generated by Tauri
|
||||
# will have schema files for capabilities auto-completion
|
||||
|
||||
@@ -32,12 +32,19 @@ struct Delta {
|
||||
content: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
struct TranslationChunkEvent {
|
||||
request_id: String,
|
||||
chunk: String,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn translate(
|
||||
app: AppHandle,
|
||||
api_address: String,
|
||||
api_key: String,
|
||||
payload: TranslationPayload,
|
||||
request_id: String,
|
||||
) -> Result<String, String> {
|
||||
let client = Client::new();
|
||||
// Ensure URL doesn't have double slashes if api_address ends with /
|
||||
@@ -55,6 +62,12 @@ async fn translate(
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let status = res.status();
|
||||
if !status.is_success() {
|
||||
let error_text = res.text().await.unwrap_or_else(|_| format!("HTTP {}", status));
|
||||
return Err(format!("HTTP {}: {}", status, error_text));
|
||||
}
|
||||
|
||||
if !payload.stream {
|
||||
let text = res.text().await.map_err(|e| e.to_string())?;
|
||||
return Ok(text);
|
||||
@@ -78,7 +91,11 @@ async fn translate(
|
||||
if let Some(choice) = json.choices.get(0) {
|
||||
if let Some(delta) = &choice.delta {
|
||||
if let Some(content) = &delta.content {
|
||||
app.emit("translation-chunk", content).map_err(|e| e.to_string())?;
|
||||
let event = TranslationChunkEvent {
|
||||
request_id: request_id.clone(),
|
||||
chunk: content.clone(),
|
||||
};
|
||||
app.emit("translation-chunk", event).map_err(|e| e.to_string())?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import { useClipboard } from '../composables/useClipboard';
|
||||
|
||||
const settings = useSettingsStore();
|
||||
const { activeCopyId, copyWithFeedback } = useClipboard();
|
||||
interface TranslationChunkEvent { request_id: string; chunk: string; }
|
||||
|
||||
// UI States
|
||||
const searchQuery = ref('');
|
||||
@@ -101,6 +102,7 @@ const myInput = ref('');
|
||||
const partnerInput = ref('');
|
||||
const isTranslating = ref(false);
|
||||
const currentStreamingMessageId = ref<string | null>(null);
|
||||
const activeStreamRequestId = ref<string | null>(null);
|
||||
|
||||
// Dropdowns
|
||||
const myToneDropdownOpen = ref(false);
|
||||
@@ -115,10 +117,14 @@ const handleCreateSession = () => {
|
||||
|
||||
let unlisten: (() => void) | null = null;
|
||||
onMounted(async () => {
|
||||
unlisten = await listen<string>('translation-chunk', (event) => {
|
||||
if (activeSession.value && currentStreamingMessageId.value) {
|
||||
unlisten = await listen<TranslationChunkEvent>('translation-chunk', (event) => {
|
||||
if (
|
||||
activeSession.value &&
|
||||
currentStreamingMessageId.value &&
|
||||
event.payload.request_id === activeStreamRequestId.value
|
||||
) {
|
||||
settings.updateChatMessage(activeSession.value.id, currentStreamingMessageId.value, {
|
||||
translated: (activeSession.value.messages.find(m => m.id === currentStreamingMessageId.value)?.translated || '') + event.payload
|
||||
translated: (activeSession.value.messages.find(m => m.id === currentStreamingMessageId.value)?.translated || '') + event.payload.chunk
|
||||
});
|
||||
|
||||
// 优化:只有当正在流式输出的消息是最后一条时,才自动滚动到底部
|
||||
@@ -207,14 +213,17 @@ const translateMessage = async (sender: 'me' | 'partner', retranslateId?: string
|
||||
],
|
||||
stream: settings.enableStreaming
|
||||
};
|
||||
const requestId = crypto.randomUUID();
|
||||
|
||||
settings.addLog('request', { type: retranslateId ? 'conversation-retranslate' : 'conversation', ...requestBody }, generateCurl(settings.apiBaseUrl, settings.apiKey, requestBody));
|
||||
|
||||
try {
|
||||
if (settings.enableStreaming) activeStreamRequestId.value = requestId;
|
||||
const response = await invoke<string>('translate', {
|
||||
apiAddress: settings.apiBaseUrl,
|
||||
apiKey: settings.apiKey,
|
||||
payload: requestBody
|
||||
payload: requestBody,
|
||||
requestId
|
||||
});
|
||||
|
||||
if (!settings.enableStreaming) {
|
||||
@@ -232,6 +241,7 @@ const translateMessage = async (sender: 'me' | 'partner', retranslateId?: string
|
||||
} finally {
|
||||
isTranslating.value = false;
|
||||
currentStreamingMessageId.value = null;
|
||||
activeStreamRequestId.value = null;
|
||||
|
||||
// 只有新消息才滚动到底部
|
||||
if (!retranslateId) {
|
||||
@@ -317,6 +327,7 @@ const evaluateMessage = async (messageId: string, force = false) => {
|
||||
],
|
||||
stream: false
|
||||
};
|
||||
const requestId = crypto.randomUUID();
|
||||
|
||||
settings.addLog('request', { type: 'conversation-eval', ...requestBody }, generateCurl(evalApiBaseUrl, evalApiKey, requestBody));
|
||||
|
||||
@@ -324,7 +335,8 @@ const evaluateMessage = async (messageId: string, force = false) => {
|
||||
const response = await invoke<string>('translate', {
|
||||
apiAddress: evalApiBaseUrl,
|
||||
apiKey: evalApiKey,
|
||||
payload: requestBody
|
||||
payload: requestBody,
|
||||
requestId
|
||||
});
|
||||
const fullResponseJson = JSON.parse(response);
|
||||
settings.addLog('response', fullResponseJson);
|
||||
@@ -358,6 +370,7 @@ const refineMessage = async (messageId: string) => {
|
||||
if (selectedSuggestions.length === 0) return;
|
||||
|
||||
const suggestionsText = selectedSuggestions.map((s: any) => `- ${s.text}`).join('\n');
|
||||
const currentTranslation = msg.translated;
|
||||
|
||||
isAuditModalOpen.value = false; // 关闭弹窗开始润色
|
||||
settings.updateChatMessage(activeSession.value.id, messageId, { isRefining: true, translated: '' });
|
||||
@@ -415,18 +428,21 @@ const refineMessage = async (messageId: string) => {
|
||||
model: refineModelName,
|
||||
messages: [
|
||||
{ role: "system", content: systemPrompt },
|
||||
{ role: "user", content: `[Source Text]\n${msg.original}\n\n[Current Translation]\n${msg.translated}\n\n[Suggestions]\n${suggestionsText}` }
|
||||
{ role: "user", content: `[Source Text]\n${msg.original}\n\n[Current Translation]\n${currentTranslation}\n\n[Suggestions]\n${suggestionsText}` }
|
||||
],
|
||||
stream: settings.enableStreaming
|
||||
};
|
||||
const requestId = crypto.randomUUID();
|
||||
|
||||
settings.addLog('request', { type: 'conversation-refine', ...requestBody }, generateCurl(refineApiBaseUrl, refineApiKey, requestBody));
|
||||
|
||||
try {
|
||||
if (settings.enableStreaming) activeStreamRequestId.value = requestId;
|
||||
const response = await invoke<string>('translate', {
|
||||
apiAddress: refineApiBaseUrl,
|
||||
apiKey: refineApiKey,
|
||||
payload: requestBody
|
||||
payload: requestBody,
|
||||
requestId
|
||||
});
|
||||
|
||||
if (!settings.enableStreaming) {
|
||||
@@ -442,6 +458,7 @@ const refineMessage = async (messageId: string) => {
|
||||
} finally {
|
||||
settings.updateChatMessage(activeSession.value.id, messageId, { isRefining: false, evaluation: undefined });
|
||||
currentStreamingMessageId.value = null;
|
||||
activeStreamRequestId.value = null;
|
||||
|
||||
// 只有当润色的是最后一条消息时才滚动到底部
|
||||
const lastMsg = activeSession.value.messages[activeSession.value.messages.length - 1];
|
||||
|
||||
@@ -53,12 +53,14 @@ const currentHistoryId = ref<string | null>(null);
|
||||
|
||||
interface Suggestion { id: number; text: string; importance: number; }
|
||||
interface EvaluationResult { score: number; analysis: string; suggestions?: Suggestion[]; }
|
||||
interface TranslationChunkEvent { request_id: string; chunk: string; }
|
||||
|
||||
const evaluationResult = ref<EvaluationResult | null>(null);
|
||||
const isEvaluating = ref(false);
|
||||
const isRefining = ref(false);
|
||||
const selectedSuggestionIds = ref<number[]>([]);
|
||||
const appliedSuggestionIds = ref<number[]>([]);
|
||||
const activeStreamRequestId = ref<string | null>(null);
|
||||
|
||||
const toggleSuggestion = (id: number) => {
|
||||
if (!selectedSuggestionIds.value) selectedSuggestionIds.value = [];
|
||||
@@ -69,9 +71,9 @@ const toggleSuggestion = (id: number) => {
|
||||
|
||||
let unlisten: (() => void) | null = null;
|
||||
onMounted(async () => {
|
||||
unlisten = await listen<string>('translation-chunk', (event) => {
|
||||
if (isTranslating.value || isRefining.value) {
|
||||
targetText.value += event.payload;
|
||||
unlisten = await listen<TranslationChunkEvent>('translation-chunk', (event) => {
|
||||
if ((isTranslating.value || isRefining.value) && event.payload.request_id === activeStreamRequestId.value) {
|
||||
targetText.value += event.payload.chunk;
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -148,11 +150,12 @@ const evaluateTranslation = async () => {
|
||||
messages: [ { role: "system", content: evaluationSystemPrompt }, { role: "user", content: evaluationUserPrompt } ],
|
||||
stream: false
|
||||
};
|
||||
const requestId = crypto.randomUUID();
|
||||
|
||||
settings.addLog('request', { type: 'evaluation', ...requestBody }, generateCurl(apiBaseUrl, apiKey, requestBody));
|
||||
|
||||
try {
|
||||
const response = await invoke<string>('translate', { apiAddress: apiBaseUrl, apiKey: apiKey, payload: requestBody });
|
||||
const response = await invoke<string>('translate', { apiAddress: apiBaseUrl, apiKey: apiKey, payload: requestBody, requestId });
|
||||
try {
|
||||
// 解析 API 的原始响应 JSON
|
||||
const fullResponseJson = JSON.parse(response);
|
||||
@@ -209,11 +212,13 @@ const refineTranslation = async () => {
|
||||
messages: [ { role: "system", content: refinementSystemPrompt }, { role: "user", content: refinementUserPrompt } ],
|
||||
stream: settings.enableStreaming
|
||||
};
|
||||
const requestId = crypto.randomUUID();
|
||||
|
||||
settings.addLog('request', { type: 'refinement', ...requestBody }, generateCurl(apiBaseUrl, apiKey, requestBody));
|
||||
|
||||
try {
|
||||
const response = await invoke<string>('translate', { apiAddress: apiBaseUrl, apiKey: apiKey, payload: requestBody });
|
||||
if (settings.enableStreaming) activeStreamRequestId.value = requestId;
|
||||
const response = await invoke<string>('translate', { apiAddress: apiBaseUrl, apiKey: apiKey, payload: requestBody, requestId });
|
||||
|
||||
if (settings.enableStreaming) {
|
||||
settings.addLog('response', targetText.value);
|
||||
@@ -239,6 +244,7 @@ const refineTranslation = async () => {
|
||||
targetText.value = `Error: ${errorMsg}`;
|
||||
} finally {
|
||||
isRefining.value = false;
|
||||
activeStreamRequestId.value = null;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -267,11 +273,13 @@ const translate = async () => {
|
||||
messages: [ { role: "system", content: systemMessage }, { role: "user", content: userMessage } ],
|
||||
stream: settings.enableStreaming
|
||||
};
|
||||
const requestId = crypto.randomUUID();
|
||||
|
||||
settings.addLog('request', requestBody, generateCurl(settings.apiBaseUrl, settings.apiKey, requestBody));
|
||||
|
||||
try {
|
||||
const response = await invoke<string>('translate', { apiAddress: settings.apiBaseUrl, apiKey: settings.apiKey, payload: requestBody });
|
||||
if (settings.enableStreaming) activeStreamRequestId.value = requestId;
|
||||
const response = await invoke<string>('translate', { apiAddress: settings.apiBaseUrl, apiKey: settings.apiKey, payload: requestBody, requestId });
|
||||
|
||||
let finalTargetText = '';
|
||||
if (settings.enableStreaming) {
|
||||
@@ -300,6 +308,7 @@ const translate = async () => {
|
||||
targetText.value = `Error: ${errorMsg}`;
|
||||
} finally {
|
||||
isTranslating.value = false;
|
||||
activeStreamRequestId.value = null;
|
||||
}
|
||||
|
||||
if (settings.enableEvaluation) await evaluateTranslation();
|
||||
@@ -656,4 +665,4 @@ const translate = async () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user