Skip to content

Commit 3eb7518

Browse files
committed
feat: prepare v0.3.2 release
1 parent 95ed0c9 commit 3eb7518

18 files changed

Lines changed: 172 additions & 18 deletions

.github/workflows/release.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ on:
44
workflow_dispatch:
55
inputs:
66
version:
7-
description: "发布版本,例如 v0.3.1"
7+
description: "发布版本,例如 v0.3.2"
88
required: true
9-
default: "v0.3.1"
9+
default: "v0.3.2"
1010
source_ref:
1111
description: "构建所用 Git ref,例如 main 或某个 commit/tag"
1212
required: false

LimePet.xcodeproj/project.pbxproj

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
C20000010000000000000008 /* PetCharacterRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C20000020000000000000009 /* PetCharacterRenderer.swift */; };
1818
C20000010000000000000009 /* character-library.json in Resources */ = {isa = PBXBuildFile; fileRef = C2000002000000000000000A /* character-library.json */; };
1919
C2000001000000000000000A /* dewy-lime-shadow.png in Resources */ = {isa = PBXBuildFile; fileRef = C2000002000000000000000B /* dewy-lime-shadow.png */; };
20+
C2000001000000000000000B /* PetConversationPrompt.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2000002000000000000000C /* PetConversationPrompt.swift */; };
21+
C2000001000000000000000C /* PetSpeechCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2000002000000000000000D /* PetSpeechCoordinator.swift */; };
2022
/* End PBXBuildFile section */
2123

2224
/* Begin PBXFileReference section */
@@ -31,6 +33,8 @@
3133
C20000020000000000000009 /* PetCharacterRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PetCharacterRenderer.swift; sourceTree = "<group>"; };
3234
C2000002000000000000000A /* character-library.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "character-library.json"; sourceTree = "<group>"; };
3335
C2000002000000000000000B /* dewy-lime-shadow.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "dewy-lime-shadow.png"; sourceTree = "<group>"; };
36+
C2000002000000000000000C /* PetConversationPrompt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PetConversationPrompt.swift; sourceTree = "<group>"; };
37+
C2000002000000000000000D /* PetSpeechCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PetSpeechCoordinator.swift; sourceTree = "<group>"; };
3438
C20000030000000000000001 /* Lime Pet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Lime Pet.app"; sourceTree = BUILT_PRODUCTS_DIR; };
3539
/* End PBXFileReference section */
3640

@@ -64,6 +68,8 @@
6468
C20000020000000000000007 /* PetPlacement.swift */,
6569
C20000020000000000000008 /* PetCharacterLibrary.swift */,
6670
C20000020000000000000009 /* PetCharacterRenderer.swift */,
71+
C2000002000000000000000C /* PetConversationPrompt.swift */,
72+
C2000002000000000000000D /* PetSpeechCoordinator.swift */,
6773
C20000050000000000000004 /* Resources */,
6874
C20000020000000000000006 /* Info.plist */,
6975
);
@@ -166,6 +172,8 @@
166172
C20000010000000000000006 /* PetPlacement.swift in Sources */,
167173
C20000010000000000000007 /* PetCharacterLibrary.swift in Sources */,
168174
C20000010000000000000008 /* PetCharacterRenderer.swift in Sources */,
175+
C2000001000000000000000B /* PetConversationPrompt.swift in Sources */,
176+
C2000001000000000000000C /* PetSpeechCoordinator.swift in Sources */,
169177
);
170178
runOnlyForDeploymentPostprocessing = 0;
171179
};
@@ -250,7 +258,7 @@
250258
"$(inherited)",
251259
"@executable_path/../Frameworks",
252260
);
253-
MARKETING_VERSION = 0.3.1;
261+
MARKETING_VERSION = 0.3.2;
254262
PRODUCT_BUNDLE_IDENTIFIER = com.lime.pet;
255263
PRODUCT_NAME = "Lime Pet";
256264
SWIFT_EMIT_LOC_STRINGS = YES;
@@ -269,7 +277,7 @@
269277
"$(inherited)",
270278
"@executable_path/../Frameworks",
271279
);
272-
MARKETING_VERSION = 0.3.1;
280+
MARKETING_VERSION = 0.3.2;
273281
PRODUCT_BUNDLE_IDENTIFIER = com.lime.pet;
274282
PRODUCT_NAME = "Lime Pet";
275283
SWIFT_EMIT_LOC_STRINGS = YES;

LimePet/CompanionProtocol.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ let petCompanionCapabilities = [
1515
"provider-overview",
1616
"provider-sync-request",
1717
"open-provider-settings",
18+
"chat-request",
19+
"bubble-speech",
1820
"multi-tap-actions",
1921
"live2d-renderer",
2022
"live2d-expressions"
@@ -47,6 +49,7 @@ enum CompanionEventType: String {
4749
case openProviderSettings = "pet.open_provider_settings"
4850
case requestPetCheer = "pet.request_pet_cheer"
4951
case requestPetNextStep = "pet.request_pet_next_step"
52+
case requestChatReply = "pet.request_chat_reply"
5053
}
5154

5255
enum IncomingCommand {
@@ -141,6 +144,11 @@ struct InteractionPayload: Encodable {
141144
let source: String
142145
}
143146

147+
struct PetChatRequestPayload: Encodable {
148+
let text: String
149+
let source: String
150+
}
151+
144152
struct StateChangedPayload: Decodable {
145153
let state: PetState
146154
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import AppKit
2+
3+
enum PetConversationPrompt {
4+
static func present(characterDisplayName: String) -> String? {
5+
let alert = NSAlert()
6+
alert.messageText = "\(characterDisplayName) 说句话"
7+
alert.informativeText = "这句会发给 Lime,由宿主侧当前可聊天模型生成回复。"
8+
alert.alertStyle = .informational
9+
10+
let inputField = NSTextField(frame: NSRect(x: 0, y: 0, width: 320, height: 24))
11+
inputField.placeholderString = "比如:今天有点累,陪我聊两句"
12+
alert.accessoryView = inputField
13+
14+
alert.addButton(withTitle: "发送")
15+
alert.addButton(withTitle: "取消")
16+
17+
NSApp.activate(ignoringOtherApps: true)
18+
let response = alert.runModal()
19+
guard response == .alertFirstButtonReturn else {
20+
return nil
21+
}
22+
23+
let text = inputField.stringValue.trimmingCharacters(in: .whitespacesAndNewlines)
24+
return text.isEmpty ? nil : text
25+
}
26+
}

LimePet/PetIPCClient.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,13 @@ final class PetIPCClient: NSObject {
6767
send(event: .requestPetNextStep, payload: InteractionPayload(source: source))
6868
}
6969

70+
func requestChatReply(text: String, source: String) {
71+
send(
72+
event: .requestChatReply,
73+
payload: PetChatRequestPayload(text: text, source: source)
74+
)
75+
}
76+
7077
private func sendReadyEvent() {
7178
send(
7279
event: .ready,

LimePet/PetRuntime.swift

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ final class PetCoordinator: NSObject {
185185
private let placementStore = PetPlacementStore.shared
186186
private let characterLibrary = PetCharacterLibrary.shared
187187
private lazy var ipcClient = PetIPCClient(configuration: configuration, delegate: self)
188+
private let speechCoordinator = PetSpeechCoordinator()
188189

189190
private var window: NSWindow?
190191
private var movementTimer: Timer?
@@ -296,6 +297,10 @@ final class PetCoordinator: NSObject {
296297
selectCharacter(id: characterId, announce: true)
297298
}
298299

300+
@objc private func promptConversationMenuAction() {
301+
promptConversation(source: "menu")
302+
}
303+
299304
@objc private func quit() {
300305
NSApp.terminate(nil)
301306
}
@@ -376,6 +381,9 @@ final class PetCoordinator: NSObject {
376381
onCharacterSelected: { [weak self] characterID in
377382
self?.selectCharacter(id: characterID, announce: true)
378383
},
384+
onChatRequested: { [weak self] in
385+
self?.promptConversation(source: "context_menu")
386+
},
379387
onOpenProviderSettingsRequested: { [weak self] in
380388
self?.requestOpenProviderSettings(source: "context_menu")
381389
},
@@ -420,6 +428,12 @@ final class PetCoordinator: NSObject {
420428

421429
let menu = NSMenu()
422430

431+
let chatItem = NSMenuItem(title: "和我说话…", action: #selector(promptConversationMenuAction), keyEquivalent: "t")
432+
chatItem.target = self
433+
menu.addItem(chatItem)
434+
435+
menu.addItem(NSMenuItem.separator())
436+
423437
let reconnectItem = NSMenuItem(title: "重连 Lime", action: #selector(reconnectIPC), keyEquivalent: "r")
424438
reconnectItem.target = self
425439
menu.addItem(reconnectItem)
@@ -990,6 +1004,35 @@ final class PetCoordinator: NSObject {
9901004
ipcClient.requestPetNextStep(source: "triple_tap")
9911005
}
9921006

1007+
private func promptConversation(source: String) {
1008+
sceneModel.setDragging(false)
1009+
sceneModel.markInteraction()
1010+
1011+
guard let text = PetConversationPrompt.present(characterDisplayName: currentCharacter.displayName) else {
1012+
return
1013+
}
1014+
1015+
requestChatReply(text: text, source: source)
1016+
}
1017+
1018+
private func requestChatReply(text: String, source: String) {
1019+
let normalizedText = text.trimmingCharacters(in: .whitespacesAndNewlines)
1020+
guard !normalizedText.isEmpty else {
1021+
sceneModel.showBubble("你先跟我说一句话吧", autoHideMs: 1400)
1022+
return
1023+
}
1024+
1025+
guard sceneModel.isConnected else {
1026+
sceneModel.showBubble("Lime 还没连上,我先等它", autoHideMs: 1400)
1027+
ipcClient.reconnect()
1028+
return
1029+
}
1030+
1031+
sceneModel.showBubble("我来想想怎么回答你…", autoHideMs: 1500)
1032+
resetPatrolCycle(startPaused: true)
1033+
ipcClient.requestChatReply(text: normalizedText, source: source)
1034+
}
1035+
9931036
private func requestOpenProviderSettings(source: String) {
9941037
sceneModel.setDragging(false)
9951038
sceneModel.markInteraction()
@@ -1144,6 +1187,9 @@ extension PetCoordinator: PetIPCClientDelegate {
11441187
updateWindowVisibility()
11451188
case .showBubble(let text, let autoHideMs):
11461189
sceneModel.showBubble(text, autoHideMs: autoHideMs)
1190+
if sceneModel.state != .hidden, (autoHideMs ?? 0) >= 2200 {
1191+
speechCoordinator.speak(text)
1192+
}
11471193
case .openChatAnchor:
11481194
sceneModel.showBubble("点我打开 Lime 对话", autoHideMs: 1600)
11491195
case .providerOverview(let overview):

LimePet/PetSpeechCoordinator.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import AVFoundation
2+
3+
@MainActor
4+
final class PetSpeechCoordinator: NSObject, AVSpeechSynthesizerDelegate {
5+
private let synthesizer = AVSpeechSynthesizer()
6+
7+
override init() {
8+
super.init()
9+
synthesizer.delegate = self
10+
}
11+
12+
func speak(_ text: String) {
13+
let normalized = text.trimmingCharacters(in: .whitespacesAndNewlines)
14+
guard !normalized.isEmpty else {
15+
return
16+
}
17+
18+
if synthesizer.isSpeaking {
19+
synthesizer.stopSpeaking(at: .immediate)
20+
}
21+
22+
let utterance = AVSpeechUtterance(string: normalized)
23+
utterance.rate = 0.47
24+
utterance.pitchMultiplier = 1.04
25+
utterance.volume = 1
26+
utterance.voice =
27+
AVSpeechSynthesisVoice(language: "zh-CN") ??
28+
AVSpeechSynthesisVoice(language: Locale.preferredLanguages.first ?? "zh-CN")
29+
30+
synthesizer.speak(utterance)
31+
}
32+
}

LimePet/PetView.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ struct PetView: View {
77
let onDragChanged: (DragGesture.Value) -> Void
88
let onDragEnded: (DragGesture.Value) -> Void
99
let onCharacterSelected: (String) -> Void
10+
let onChatRequested: () -> Void
1011
let onOpenProviderSettingsRequested: () -> Void
1112
let onSyncProviderOverviewRequested: () -> Void
1213
let onReconnectRequested: () -> Void
@@ -75,6 +76,12 @@ struct PetView: View {
7576

7677
Divider()
7778

79+
Button("和我说话…") {
80+
onChatRequested()
81+
}
82+
83+
Divider()
84+
7885
Menu("Lime Companion 诊断") {
7986
diagnosticReadonlyItem(sceneModel.companionDiagnostic.connectionLine)
8087
diagnosticReadonlyItem(sceneModel.companionDiagnostic.endpointLine)

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
- 本地 `WebSocket` companion 协议客户端
1717
- 点击桌宠后向 Lime 发送 `pet.clicked` / `pet.open_chat`
1818
- 单击 / 双击 / 三击支持不同动作:单击唤起 Lime,双击生成一句青柠鼓励,三击生成一句“下一步建议”
19+
- 支持从桌宠右键或状态栏菜单直接输入一句话,请求 Lime 宿主侧模型生成回复,并在桌宠本地朗读
1920
- 断线时会给出提示气泡并自动重连
2021
- 空闲时会有轻量陪伴气泡,不打断主流程
2122
- 菜单栏支持重连、回到屏幕中央、左/中/右停靠、显示 / 隐藏桌宠
@@ -74,14 +75,14 @@ open "dist/Lime Pet.app"
7475
本地生成 release zip:
7576

7677
```bash
77-
./scripts/package-release.sh --version "0.3.1" --build-number "1"
78+
./scripts/package-release.sh --version "0.3.2" --build-number "1"
7879
```
7980

8081
产物默认会按当前宿主架构输出,例如 Apple Silicon 机器上会得到:
8182

8283
```text
83-
dist/release/LimePet-v0.3.1-macos-arm64-unsigned.zip
84-
dist/release/LimePet-v0.3.1-macos-arm64-unsigned.zip.sha256
84+
dist/release/LimePet-v0.3.2-macos-arm64-unsigned.zip
85+
dist/release/LimePet-v0.3.2-macos-arm64-unsigned.zip.sha256
8586
```
8687

8788
GitHub Actions 发布策略:

WindowsPet/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)