Browse Source

add

newdev_shunjiawei
wolfplus 1 year ago
parent
commit
232c970134
  1. 11
      ios/Podfile.lock
  2. 90
      ios/Runner.xcodeproj/project.pbxproj
  3. 6
      lib/modules/agent/controllers/agent_controller.dart
  4. 81
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt
  5. 128
      local_plugins/agent_service/ios/Classes/AgentServiceImpl.swift
  6. 1
      local_plugins/agent_service/ios/agent_service.podspec
  7. 2
      local_plugins/agent_service/pubspec.yaml
  8. 17
      local_plugins/ble_service/ios/Classes/BleService.swift
  9. 178
      local_plugins/chat_storage/ios/Classes/ChatStorageHelper.swift
  10. 16
      local_plugins/chat_storage/ios/Classes/SwiftChatStoragePlugin.swift
  11. 38
      local_plugins/open_ai_service/ios/Classes/SystemFunctionHandler.swift
  12. 45
      test.md

11
ios/Podfile.lock

@ -2,6 +2,7 @@ PODS:
- agent_service (0.0.1):
- azure_speech
- ble_service
- chat_storage
- Flutter
- open_ai_service
- app_settings (5.1.1):
@ -18,7 +19,7 @@ PODS:
- Flutter
- azure_speech (0.0.1):
- Flutter
- MicrosoftCognitiveServicesSpeech-iOS (~> 1.30.0)
- MicrosoftCognitiveServicesSpeech-iOS (~> 1.37.0)
- speech
- ble_service (0.0.1):
- Flutter
@ -189,7 +190,7 @@ PODS:
- Mantle (2.2.0):
- Mantle/extobjc (= 2.2.0)
- Mantle/extobjc (2.2.0)
- MicrosoftCognitiveServicesSpeech-iOS (1.30.0)
- MicrosoftCognitiveServicesSpeech-iOS (1.37.0)
- music_service (0.0.1):
- Flutter
- open_ai_service (0.0.1):
@ -387,12 +388,12 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/webview_flutter_wkwebview/darwin"
SPEC CHECKSUMS:
agent_service: 306557f82fd69335e216e989498bacff51c0b9e8
agent_service: a95166e1939f18436aa2ae6b80c17751a2f69b89
app_settings: 5127ae0678de1dcc19f2293271c51d37c89428b2
AppAuth: d4f13a8fe0baf391b2108511793e4b479691fb73
audio_session: 9bb7f6c970f21241b19f5a3658097ae459681ba0
audio_waveforms: a6dde7fe7c0ea05f06ffbdb0f7c1b2b2ba6cedcf
azure_speech: 11927e53ab90a2def1370da1d44e4915a97c0290
azure_speech: 23c6573d04e7f84a0839e935e527c87b5f78311f
ble_service: 9f394ad44d435010e718bc81de494d9425295a85
chat_storage: 933a96144157a07a2fef7f6acb28c78ff18465d8
classic_bluetooth: c146e1618e867a47fa7ab79a3bf262e7d69d557d
@ -432,7 +433,7 @@ SPEC CHECKSUMS:
just_audio: 4e391f57b79cad2b0674030a00453ca5ce817eed
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d
MicrosoftCognitiveServicesSpeech-iOS: f53991647f295e18407619816a8064908a8700f4
MicrosoftCognitiveServicesSpeech-iOS: 766852ccd56e0c74bafbe9ea7033083c5f748d04
music_service: 85abecc8fa21599a06907ad50d83f48a58e0dc0d
open_ai_service: d78dff9359ee4c04f20d250d655d6ec75b4c5434
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499

90
ios/Runner.xcodeproj/project.pbxproj

@ -12,12 +12,12 @@
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
430D8C34FB614ED446186262 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9CB0EC76DB044A21D8535AA1 /* Pods_RunnerTests.framework */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
C6017C52003F4D9508A3F089 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ADBF2962D094C0C73A4D70AD /* Pods_Runner.framework */; };
9EB82175436F3A3D593E4423 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E9725ECE141452716001447D /* Pods_Runner.framework */; };
DD1E3297BCF7B8F42676149E /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 41D979DCCB9FC1A2739ED9E2 /* Pods_RunnerTests.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -44,21 +44,20 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
082E420FFD5DDCB63B0AF2D1 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
0A9A0EEC032E92406E64632E /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
0AD944112DD9DB5C00557D21 /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
0AD944132DDA0C0D00557D21 /* AccessorySetupKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AccessorySetupKit.framework; path = System/Library/Frameworks/AccessorySetupKit.framework; sourceTree = SDKROOT; };
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
15653C8102986FFCD3D3A1FB /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
419E468FD967D92BB8F09F89 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
41D979DCCB9FC1A2739ED9E2 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
763E2B7DA86D69DF1C598385 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
8864DCDCFE5ECACAF8C8A05C /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
8960B97871604358D5CA1866 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
932005D16734B31A8CB0583F /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
@ -66,10 +65,11 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
9CB0EC76DB044A21D8535AA1 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
ADBF2962D094C0C73A4D70AD /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
C1F336500F05EC22722503B5 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
BCF37DA3F60C9FDB47151D46 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
D2958658C06A5ADF62D6ABA2 /* Runner.entitlements */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
D2A873E56435FC1342AE0A85 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
D7BFB5FE33B0270C30A1DFAA /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
E9725ECE141452716001447D /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -78,7 +78,7 @@
buildActionMask = 2147483647;
files = (
0A000A2C2DE1EE50005BE0F2 /* Opus in Frameworks */,
C6017C52003F4D9508A3F089 /* Pods_Runner.framework in Frameworks */,
9EB82175436F3A3D593E4423 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -86,7 +86,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
430D8C34FB614ED446186262 /* Pods_RunnerTests.framework in Frameworks */,
DD1E3297BCF7B8F42676149E /* Pods_RunnerTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -96,12 +96,12 @@
1ACB686EC7DC36C6E33D372F /* Pods */ = {
isa = PBXGroup;
children = (
763E2B7DA86D69DF1C598385 /* Pods-Runner.debug.xcconfig */,
419E468FD967D92BB8F09F89 /* Pods-Runner.release.xcconfig */,
8864DCDCFE5ECACAF8C8A05C /* Pods-Runner.profile.xcconfig */,
C1F336500F05EC22722503B5 /* Pods-RunnerTests.debug.xcconfig */,
15653C8102986FFCD3D3A1FB /* Pods-RunnerTests.release.xcconfig */,
082E420FFD5DDCB63B0AF2D1 /* Pods-RunnerTests.profile.xcconfig */,
D2A873E56435FC1342AE0A85 /* Pods-Runner.debug.xcconfig */,
932005D16734B31A8CB0583F /* Pods-Runner.release.xcconfig */,
BCF37DA3F60C9FDB47151D46 /* Pods-Runner.profile.xcconfig */,
0A9A0EEC032E92406E64632E /* Pods-RunnerTests.debug.xcconfig */,
8960B97871604358D5CA1866 /* Pods-RunnerTests.release.xcconfig */,
D7BFB5FE33B0270C30A1DFAA /* Pods-RunnerTests.profile.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
@ -110,8 +110,8 @@
isa = PBXGroup;
children = (
0AD944132DDA0C0D00557D21 /* AccessorySetupKit.framework */,
ADBF2962D094C0C73A4D70AD /* Pods_Runner.framework */,
9CB0EC76DB044A21D8535AA1 /* Pods_RunnerTests.framework */,
E9725ECE141452716001447D /* Pods_Runner.framework */,
41D979DCCB9FC1A2739ED9E2 /* Pods_RunnerTests.framework */,
);
name = Frameworks;
sourceTree = "<group>";
@ -180,7 +180,7 @@
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
B8C792A7B608260344D22724 /* [CP] Check Pods Manifest.lock */,
BF953267BBF1D433B6E02C09 /* [CP] Check Pods Manifest.lock */,
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
B3515CD9628E3332AFC88CD4 /* Frameworks */,
@ -199,15 +199,15 @@
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
4A3EB3E37381171839F14E87 /* [CP] Check Pods Manifest.lock */,
99B1E635188BC8E58C3A1B67 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
D6CD68555C30E62704FE08E1 /* [CP] Embed Pods Frameworks */,
AEAEE9FC319318D86B2EFE75 /* [CP] Copy Pods Resources */,
CC018A1832E51ED5B994765E /* [CP] Embed Pods Frameworks */,
A196696487421EBA40281C38 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@ -299,44 +299,44 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
4A3EB3E37381171839F14E87 /* [CP] Check Pods Manifest.lock */ = {
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
name = "Run Script";
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
99B1E635188BC8E58C3A1B67 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
name = "Run Script";
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
AEAEE9FC319318D86B2EFE75 /* [CP] Copy Pods Resources */ = {
A196696487421EBA40281C38 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@ -353,7 +353,7 @@
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
showEnvVarsInLog = 0;
};
B8C792A7B608260344D22724 /* [CP] Check Pods Manifest.lock */ = {
BF953267BBF1D433B6E02C09 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@ -375,7 +375,7 @@
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
D6CD68555C30E62704FE08E1 /* [CP] Embed Pods Frameworks */ = {
CC018A1832E51ED5B994765E /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@ -521,7 +521,7 @@
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = C1F336500F05EC22722503B5 /* Pods-RunnerTests.debug.xcconfig */;
baseConfigurationReference = 0A9A0EEC032E92406E64632E /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
@ -540,7 +540,7 @@
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 15653C8102986FFCD3D3A1FB /* Pods-RunnerTests.release.xcconfig */;
baseConfigurationReference = 8960B97871604358D5CA1866 /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
@ -557,7 +557,7 @@
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 082E420FFD5DDCB63B0AF2D1 /* Pods-RunnerTests.profile.xcconfig */;
baseConfigurationReference = D7BFB5FE33B0270C30A1DFAA /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;

6
lib/modules/agent/controllers/agent_controller.dart

@ -199,11 +199,13 @@ class AgentController extends GetxController {
isTextInputMode.value = false;
// isTextInputMode.refresh();
// 只标记识别开始,不添加消息
Logger.i(TAG, '识别开始');
break;
case AgentServiceEventType.recognitionStopped:
isListening.value = false;
isTextInputMode.value = true;
// isTextInputMode.refresh();
Logger.i(TAG, '识别停止');
break;
case AgentServiceEventType.recognizing:
@ -250,7 +252,7 @@ class AgentController extends GetxController {
isRecognizing: false,
timestamp: messages[index].timestamp,
);
messages.refresh();
// messages.refresh();
} else {
// 如果没有找到识别中的消息,添加新消息
messages
@ -353,7 +355,7 @@ class AgentController extends GetxController {
// 只有当消息为空或文本太短时才移除
if (messages[index].text.trim().length < 2) {
messages.removeAt(index);
messages.refresh();
// messages.refresh();
} else {
// 否则转换为正式消息
messages[index] = Message(

81
local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt

@ -97,63 +97,19 @@ object AgentService : CoroutineScope {
*/
private fun initSystemPrompt() {
systemPrompt = """
你是小言,一个具备专业能力的智能助手,需根据用户场景灵活切换回答模式,确保服务精准高效。以下是不同场景的具体要求:
一、知识类问题回答规范
核心目标:用简洁语言提供准确信息,兼顾易读性与专业性。
1. 内容细节处理
• 精准性优先:围绕问题主体,核心信息需全面、准确,避免模糊表述或错误。
◦ 例:回答"地球公转周期"时,需明确"约365.25天"而非"一年左右"。
• 背景与类比:对复杂概念(如"量子纠缠"),可补充关键背景("量子力学中的现象")并类比("类似远距离同步的状态"),帮助用户快速理解。
• 需求宽泛时:若问题涉及范围广(如"介绍人工智能"),先概述主要领域(技术、应用、影响),再分点简要说重点,避免信息过载。
• 禁止过度延伸:非用户主动询问,不添加无关内容(如问"光速"时,不展开讨论"相对论")。
2. 格式排版规则
• 标题与重点:章节标题、关键结论需加粗(如一、核心原理),突出层级。
• 列表使用:
◦ 有序列表(1. 2. 3.):用于步骤、顺序类内容(如"实验流程")。
◦ 无序列表(- ):用于并列项(如"优点:- 高效 - 精准")。
◦ 禁止嵌套列表:如需多层级,用**三级标题(###)**分隔,下接一级列表。
• 段落处理:辅助解释用自然段,每段不超过3行,避免大段文字堆砌。
二、文案创作与内容生成规范
核心目标:根据体裁需求,平衡丰富度与阅读体验,确保逻辑流畅、重点突出。
1. 篇幅与内容要求
• 场景适配:
◦ 短文案(如标语、摘要):控制在50字内,用词简练有力。
◦ 长内容(如报告、故事):需结构清晰,每部分围绕核心展开,适当添加案例/数据支撑。
• 避免极端化:既不堆砌空泛词汇,也不遗漏关键信息。
2. 格式与排版
• 默认体裁:无特殊要求时用自然段,适当换行分隔话题。
• 结构化体裁(如策划案、简历):
◦ 用**分级标题**划分板块。
◦ 关键数据/结论加粗。
◦ 并列信息用列表呈现。
三、聊天陪伴与情感互动规范
核心目标:营造轻松友好的对话氛围,成为用户可信赖的交流伙伴。
1. 沟通风格设定
• 人格化表达:避免机械回答,用口语化词汇,适当加入表情符号增强亲切感。
• 主动引导话题:根据用户输入延伸讨论。
• 适度分享:自然提及日常,拉近距离。
2. 情绪识别与回应
• 捕捉潜在需求:识别情绪,先共情再询问细节。
• 针对性建议:结合用户处境提供具体方案。
• 积极价值观:传递乐观态度,避免消极表述。
四、能力与工具使用说明
1. 内容处理能力:图片识别、可执行总结、翻译、分析等任务。
2. 信息搜索能力:实时数据、新闻、专业知识等需搜索时,会明确告知用户。
五、特殊场景处理规则
1. 安全与合规:禁止回答敏感问题,遇此类问题需礼貌拒绝。
2. 用户指令优先:若用户明确要求特定风格,优先遵循其需求。
3. 多轮澄清机制:若问题模糊,需主动询问细节,避免假设性回答。
你是小言,一个亲切而温暖的全能个人助理和伴侣,拥有智能、细致而贴心的服务能力。你的任务不仅是高效处理用户的日常事务,更要陪伴用户,提供情感支持和温暖陪伴。你的能力包括:
1. 日程与任务管理:温柔提醒用户的重要日程、待办事项,让用户感到安心和舒适。
2. 日常信息与建议:亲切、迅速地提供天气、新闻、生活小贴士等实用信息。
3. 个人成长陪伴:鼓励并陪伴用户实现个人目标,提出富有同理心的行动建议。
4. 财务温馨提示:提供贴心的理财建议,帮助用户轻松管理预算和财务。
5. 健康贴心关怀:给出友善的饮食、运动、睡眠和心理健康建议,陪伴用户健康生活。
6. 沟通支持与倾听:协助用户撰写和优化沟通内容,并耐心倾听用户的心情与困扰。
7. 日常技术帮助:轻松解决技术问题,耐心地指导用户掌握新技能。
8. 创意激发与鼓励:温柔启发用户的创造力,为用户提供鼓励和灵感。
9. 旅行与活动贴心安排:细心规划用户的出行和活动,注重每个细节,确保舒适与快乐。
10. 学习与温暖鼓励:耐心推荐学习资源,鼓励用户持续进步,陪伴用户共同成长。
你与用户的交流风格应始终温暖、亲切、充满同理心,随时表达关怀与鼓励,让用户感受到真诚的陪伴与支持。
""".trimIndent()
}
/**
@ -175,13 +131,12 @@ object AgentService : CoroutineScope {
// 设置系统提示词
// val customSystemPrompt = config["systemPrompt"] as? String
// if (!customSystemPrompt.isNullOrBlank()) {
// systemPrompt = customSystemPrompt
// } else {
// initSystemPrompt()
// }
val customSystemPrompt = config["systemPrompt"] as? String
if (!customSystemPrompt.isNullOrBlank()) {
systemPrompt = customSystemPrompt
} else {
initSystemPrompt()
}
// 初始化Azure ASR
azureAsrHelper = AzureAsrHelper(context).apply {
initialize(

128
local_plugins/agent_service/ios/Classes/AgentServiceImpl.swift

@ -7,6 +7,7 @@ import os
import os.log
import agent_service
import open_ai_service
import chat_storage
protocol AgentServiceListener: AnyObject {
func onEvent(eventName: String, data: [String: Any])
@ -37,6 +38,14 @@ class AgentServiceImpl: NSObject {
private var mcpServer: String = ""
// 聊天存储服务(使用单例)
private var chatStorageHelper: ChatStorageHelper {
return ChatStorageHelper.shared
}
// 会话ID,用于区分不同聊天上下文
private let sessionId = "default_agent"
private var isInitialized: Bool = false
private var isRecognizing: Bool = false
private var hasSpeechDetected: Bool = false
@ -59,6 +68,8 @@ class AgentServiceImpl: NSObject {
openAIService = OpenAIService()
// chatStorageHelper 现在是单例,不需要初始化
BleService.shared.setDelegate(self)
}
@ -98,12 +109,24 @@ class AgentServiceImpl: NSObject {
private func initSystemPrompt() {
systemPrompt = """
你是小言,一个具备专业能力的智能助手,需根据用户场景灵活切换回答模式,确保服务精准高效。
你是小言,一个亲切而温暖的智能AI语音助手,拥有智能、细致而贴心的服务能力。你的任务不仅是高效处理用户的日常事务,更要陪伴用户,提供情感支持和温暖陪伴。你的能力包括:
1. 日程与任务管理:温柔提醒用户的重要日程、待办事项,让用户感到安心和舒适。
2. 日常信息与建议:亲切、迅速地提供天气、新闻、生活小贴士等实用信息。
3. 个人成长陪伴:鼓励并陪伴用户实现个人目标,提出富有同理心的行动建议。
4. 财务温馨提示:提供贴心的理财建议,帮助用户轻松管理预算和财务。
5. 健康贴心关怀:给出友善的饮食、运动、睡眠和心理健康建议,陪伴用户健康生活。
6. 沟通支持与倾听:协助用户撰写和优化沟通内容,并耐心倾听用户的心情与困扰。
7. 日常技术帮助:轻松解决技术问题,耐心地指导用户掌握新技能。
8. 创意激发与鼓励:温柔启发用户的创造力,为用户提供鼓励和灵感。
9. 旅行与活动贴心安排:细心规划用户的出行和活动,注重每个细节,确保舒适与快乐。
10. 学习与温暖鼓励:耐心推荐学习资源,鼓励用户持续进步,陪伴用户共同成长。
你与用户的交流风格应始终温暖、亲切、充满同理心,随时表达关怀与鼓励,让用户感受到真诚的陪伴与支持。
"""
}
func initialize(config: [String: Any]) -> Bool {
if isInitialized { return true }
// os_log("initialize: config=%{public}@", log: logger, type: .info, config)
if let openaiApiKey = config["openaiApiKey"] as? String {
self.apiKey = openaiApiKey
@ -125,7 +148,7 @@ class AgentServiceImpl: NSObject {
} else {
initSystemPrompt()
}
initSystemPrompt()
if let azureSpeechKey = config["azureSpeechKey"] as? String {
self.azureSpeechKey = azureSpeechKey
} else {
@ -149,6 +172,11 @@ class AgentServiceImpl: NSObject {
let openaiInitSuccess = initializeOpenAIService()
isInitialized = asrInitSuccess && openaiInitSuccess
if isInitialized {
loadChatHistory()
}
return isInitialized
}
@ -206,9 +234,12 @@ class AgentServiceImpl: NSObject {
guard isRecognizing else { return }
idleTimer = DispatchSource.makeTimerSource(queue: DispatchQueue.main)
// 使用全局队列而不是主队列,避免后台挂起问题
idleTimer = DispatchSource.makeTimerSource(queue: DispatchQueue.global(qos: .utility))
idleTimer?.schedule(deadline: .now() + maxIdleSeconds)
idleTimer?.setEventHandler { [weak self] in
// 使用更安全的方式检查self是否存在
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
if self.isRecognizing && !self.hasSpeechDetected && !self.isSpeaking && !self.isAiStreaming {
@ -219,15 +250,16 @@ class AgentServiceImpl: NSObject {
])
}
}
}
idleTimer?.resume()
}
private func stopIdleCheck() {
guard let timer = idleTimer else { return }
if let timer = idleTimer {
timer.cancel()
idleTimer = nil
}
}
private func restartIdleCheck() {
if isRecognizing {
@ -463,6 +495,74 @@ class AgentServiceImpl: NSObject {
}
}
/**
* 加载最近的聊天历史记录
*/
private func loadChatHistory() {
// 清空当前历史记录
chatHistory.removeAll()
// 获取最近10条消息
let recentMessages = chatStorageHelper.getRecentMessages(sessionId: sessionId, limit: 10)
if recentMessages.isEmpty {
return
}
// 添加消息到历史记录
for message in recentMessages {
guard let sender = message["sender"] as? String,
let content = message["message"] as? String else {
continue
}
if sender == "user" {
if let openAIService = openAIService {
addToHistoryMessages(openAIService.createUserMessage(content: content))
}
} else if sender == "assistant" {
let assistantMessage: [String: Any] = [
"role": "assistant",
"content": content
]
addToHistoryMessages(assistantMessage)
}
}
os_log("已加载%d条历史记录", log: logger, type: .info, recentMessages.count)
}
/**
* 保存聊天消息
*/
internal func saveChatMessage(userMessage: String, assistantMessage: String, metadata: String = "") {
DispatchQueue.global(qos: .utility).async {
// 保存用户消息
let userMessageId = self.chatStorageHelper.saveMessage(
sessionId: self.sessionId,
message: userMessage,
sender: "user",
metadata: nil as String?
)
if userMessageId != -1 {
// 保存AI回复
let assistantMessageId = self.chatStorageHelper.saveMessage(
sessionId: self.sessionId,
message: assistantMessage,
sender: "assistant",
metadata: metadata.isEmpty ? nil : metadata
)
if assistantMessageId == -1 {
os_log("保存助手消息失败", log: self.logger, type: .error)
}
} else {
os_log("保存用户消息失败", log: self.logger, type: .error)
}
}
}
@discardableResult
internal func autoHandleFunctionCallResult(_ functionCallResult: [String: Any]) -> String {
guard let metaStr = functionCallResult["meta"] as? String, !metaStr.isEmpty else {
@ -503,6 +603,14 @@ class AgentServiceImpl: NSObject {
func clearChatHistory() -> Bool {
chatHistory.removeAll()
DispatchQueue.global(qos: .utility).async {
let success = self.chatStorageHelper.deleteMessages(sessionId: self.sessionId, messageIds: nil as [Int]?)
if !success {
os_log("清除聊天历史失败", log: self.logger, type: .error)
}
}
return true
}
@ -528,6 +636,8 @@ class AgentServiceImpl: NSObject {
audioPlayer = nil
// chatStorageHelper 是单例,不需要手动清理
clearListeners()
AudioSessionHub.shared.appDidEnterBackground()
@ -544,6 +654,7 @@ class OpenAIStreamCallback: StreamCallback {
private let displayText: String
private let hasImage: Bool
private var responseBuilder = ""
private var metadata = ""
init(agentService: AgentServiceImpl, speakResponse: Bool, displayText: String, hasImage: Bool) {
self.agentService = agentService
@ -588,6 +699,9 @@ class OpenAIStreamCallback: StreamCallback {
"content": response
]
agentService.addToHistoryMessages(assistantMessage)
// 保存聊天记录
agentService.saveChatMessage(userMessage: displayText, assistantMessage: response, metadata: metadata)
}
agentService.isAiStreaming = false
@ -630,7 +744,7 @@ class OpenAIStreamCallback: StreamCallback {
"result": functionCallResult
])
agentService.autoHandleFunctionCallResult(functionCallResult)
metadata = agentService.autoHandleFunctionCallResult(functionCallResult)
}
}

1
local_plugins/agent_service/ios/agent_service.podspec

@ -20,6 +20,7 @@ Pod::Spec.new do |s|
s.dependency 'azure_speech'
s.dependency 'ble_service'
s.dependency 'open_ai_service'
s.dependency 'chat_storage'
s.platform = :ios, '13.0'
# Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported.

2
local_plugins/agent_service/pubspec.yaml

@ -19,6 +19,8 @@ dependencies:
path: ../ble_service
speech:
path: ../speech
chat_storage:
path: ../chat_storage
dev_dependencies:
flutter_test:

17
local_plugins/ble_service/ios/Classes/BleService.swift

@ -256,6 +256,14 @@ public class BleService: NSObject {
startOpusDecoding()
}
// 清空特征引用,强制重新发现
writeCharacteristic = nil
notifyCharacteristic = nil
audioCharacteristic = nil
os_log("🔍 恢复会话时重新发现服务和特征", log: logger, type: .info)
peripheral.discoverServices([BleConst.PRIMARY_SERVICE_UUID, BleConst.AUDIO_SERVICE_UUID])
os_log("已恢复设备会话: %@", log: logger, type: .info, peripheral.identifier.uuidString)
}
@ -935,6 +943,9 @@ extension BleService: CBCentralManagerDelegate {
for peripheral in restoredPeripherals {
peripheral.delegate = self
// 从重连队列中移除(避免重复处理)
pendingReconnectUUIDs.remove(peripheral.identifier)
if peripheral.state == .connected {
os_log("🔄 外设已连接,恢复会话: %{public}@", log: logger, type: .info,
peripheral.identifier.uuidString)
@ -950,7 +961,7 @@ extension BleService: CBCentralManagerDelegate {
restoredPeripherals.removeAll()
}
// 恢复待重连的设备
// 恢复待重连的设备(只处理不在restoredPeripherals中的设备)
if !pendingReconnectUUIDs.isEmpty {
os_log("🔄 尝试重连%d个待重连设备", log: logger, type: .info, pendingReconnectUUIDs.count)
@ -962,9 +973,6 @@ extension BleService: CBCentralManagerDelegate {
}
}
// 尝试重连队列中的设备
tryReconnectDevices()
case .poweredOff:
os_log("蓝牙已关闭", log: logger, type: .info)
updateConnectionState(BleConst.STATE_DISCONNECTED)
@ -1019,6 +1027,7 @@ extension BleService: CBCentralManagerDelegate {
for peripheral in peripherals {
os_log("🔄 重连设备: %{public}@", log: logger, type: .info, peripheral.identifier.uuidString)
connectPeripheral(peripheral)
// 注意:不在这里移除UUID,让didConnect回调来移除
}
}

178
local_plugins/chat_storage/ios/Classes/ChatStorageHelper.swift

@ -1,15 +1,21 @@
import Foundation
import SQLite3
import os.log
/**
* ChatStorageHelper - 聊天数据存储助手
* 负责处理SQLite数据库操作,提供消息存储和检索功能
* 单例模式确保数据库连接的一致性
*/
class ChatStorageHelper {
public class ChatStorageHelper {
// 单例实例
public static let shared = ChatStorageHelper()
private var db: OpaquePointer?
private let dbPath: String
private let logger = OSLog(subsystem: "com.yunqiinnovation.chat_storage", category: "ChatStorageHelper")
init() {
private init() {
// 获取文档目录路径
let fileURL = try! FileManager.default
.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
@ -19,7 +25,8 @@ class ChatStorageHelper {
// 打开数据库
if sqlite3_open(dbPath, &db) != SQLITE_OK {
print("无法打开数据库")
let errmsg = String(cString: sqlite3_errmsg(db)!)
os_log("无法打开数据库: %{public}@", log: logger, type: .error, errmsg)
return
}
@ -39,7 +46,7 @@ class ChatStorageHelper {
if sqlite3_exec(db, createTableString, nil, nil, nil) != SQLITE_OK {
let errmsg = String(cString: sqlite3_errmsg(db)!)
print("创建表失败: \(errmsg)")
os_log("创建表失败: %{public}@", log: logger, type: .error, errmsg)
}
}
@ -57,7 +64,7 @@ class ChatStorageHelper {
* @param metadata 可选的元数据
* @return 插入的消息ID,失败则返回-1
*/
func saveMessage(sessionId: String, message: String, sender: String, metadata: String?) -> Int64 {
public func saveMessage(sessionId: String, message: String, sender: String, metadata: String?) -> Int64 {
let insertStatementString = "INSERT INTO messages (session_id, message, sender, metadata, created_at) VALUES (?, ?, ?, ?, ?);"
var insertStatement: OpaquePointer?
@ -80,12 +87,14 @@ class ChatStorageHelper {
sqlite3_finalize(insertStatement)
return id
} else {
print("插入消息失败: \(String(cString: sqlite3_errmsg(db)!))")
let errmsg = String(cString: sqlite3_errmsg(db)!)
os_log("插入消息失败: %{public}@", log: logger, type: .error, errmsg)
}
sqlite3_finalize(insertStatement)
} else {
print("插入消息语句准备失败: \(String(cString: sqlite3_errmsg(db)!))")
let errmsg = String(cString: sqlite3_errmsg(db)!)
os_log("插入消息语句准备失败: %{public}@", log: logger, type: .error, errmsg)
}
return -1
@ -98,10 +107,26 @@ class ChatStorageHelper {
* @param pageSize 每页消息数量
* @return 消息列表的JSON字符串
*/
func getMessages(sessionId: String, page: Int, pageSize: Int) -> String {
public func getMessages(sessionId: String, page: Int, pageSize: Int) -> String {
let offset = (page - 1) * pageSize
var messagesArray: [[String: Any]] = []
// 首先获取总记录数
let countQuery = "SELECT COUNT(*) FROM messages WHERE session_id = ?"
var countStatement: OpaquePointer?
var totalCount = 0
if sqlite3_prepare_v2(db, countQuery, -1, &countStatement, nil) == SQLITE_OK {
sqlite3_bind_text(countStatement, 1, (sessionId as NSString).utf8String, -1, nil)
if sqlite3_step(countStatement) == SQLITE_ROW {
totalCount = Int(sqlite3_column_int(countStatement, 0))
}
sqlite3_finalize(countStatement)
}
// 计算总页数
let totalPages = totalCount == 0 ? 0 : (totalCount + pageSize - 1) / pageSize
let queryString = """
SELECT id, message, sender, metadata, created_at
FROM messages
@ -131,11 +156,17 @@ class ChatStorageHelper {
let createdAt = sqlite3_column_int(queryStatement, 4)
// 将时间戳转换为ISO 8601格式的字符串
let date = Date(timeIntervalSince1970: TimeInterval(createdAt))
let formatter = ISO8601DateFormatter()
let timestamp = formatter.string(from: date)
var messageDict: [String: Any] = [
"id": id,
"sessionId": sessionId, // 添加sessionId字段
"message": message,
"sender": sender,
"created_at": createdAt
"timestamp": timestamp // 使用timestamp而不是created_at
]
if let metadata = metadata {
@ -147,22 +178,48 @@ class ChatStorageHelper {
sqlite3_finalize(queryStatement)
// 构建与Android版本一致的返回格式
let result: [String: Any] = [
"messages": messagesArray,
"page": page,
"pageSize": pageSize,
"totalCount": totalCount,
"totalPages": totalPages
]
do {
let jsonData = try JSONSerialization.data(withJSONObject: messagesArray, options: [])
let jsonData = try JSONSerialization.data(withJSONObject: result, options: [])
if let jsonString = String(data: jsonData, encoding: .utf8) {
return jsonString
}
} catch {
print("转换消息为JSON失败: \(error.localizedDescription)")
os_log("getMessages: JSON转换失败: %{public}@", log: logger, type: .error, error.localizedDescription)
}
} else {
print("查询消息失败: \(String(cString: sqlite3_errmsg(db)!))")
let errmsg = String(cString: sqlite3_errmsg(db)!)
os_log("getMessages: SQL准备失败: %{public}@", log: logger, type: .error, errmsg)
}
return "[]"
}
// 返回空结果,但保持格式一致
let emptyResult: [String: Any] = [
"messages": [],
"page": page,
"pageSize": pageSize,
"totalCount": 0,
"totalPages": 0
]
do {
let jsonData = try JSONSerialization.data(withJSONObject: emptyResult, options: [])
if let jsonString = String(data: jsonData, encoding: .utf8) {
return jsonString
}
} catch {
os_log("getMessages: 空结果JSON转换失败: %{public}@", log: logger, type: .error, error.localizedDescription)
}
return "{\"messages\":[],\"page\":\(page),\"pageSize\":\(pageSize),\"totalCount\":0,\"totalPages\":0}"
}
/**
* 删除消息
@ -170,7 +227,7 @@ class ChatStorageHelper {
* @param messageIds 可选的消息ID列表,指定则删除特定消息
* @return 是否删除成功
*/
func deleteMessages(sessionId: String?, messageIds: [Int]?) -> Bool {
public func deleteMessages(sessionId: String?, messageIds: [Int]?) -> Bool {
if let sessionId = sessionId {
let deleteString = "DELETE FROM messages WHERE session_id = ?;"
var deleteStatement: OpaquePointer?
@ -182,12 +239,14 @@ class ChatStorageHelper {
sqlite3_finalize(deleteStatement)
return true
} else {
print("删除会话消息失败: \(String(cString: sqlite3_errmsg(db)!))")
let errmsg = String(cString: sqlite3_errmsg(db)!)
os_log("删除会话消息失败: %{public}@", log: logger, type: .error, errmsg)
}
sqlite3_finalize(deleteStatement)
} else {
print("删除会话消息语句准备失败: \(String(cString: sqlite3_errmsg(db)!))")
let errmsg = String(cString: sqlite3_errmsg(db)!)
os_log("删除会话消息语句准备失败: %{public}@", log: logger, type: .error, errmsg)
}
} else if let messageIds = messageIds, !messageIds.isEmpty {
// 构建参数占位符
@ -204,14 +263,17 @@ class ChatStorageHelper {
sqlite3_finalize(deleteStatement)
return true
} else {
print("删除特定消息失败: \(String(cString: sqlite3_errmsg(db)!))")
let errmsg = String(cString: sqlite3_errmsg(db)!)
os_log("删除特定消息失败: %{public}@", log: logger, type: .error, errmsg)
}
sqlite3_finalize(deleteStatement)
} else {
print("删除特定消息语句准备失败: \(String(cString: sqlite3_errmsg(db)!))")
let errmsg = String(cString: sqlite3_errmsg(db)!)
os_log("删除特定消息语句准备失败: %{public}@", log: logger, type: .error, errmsg)
}
} else {
os_log("删除消息参数无效 - sessionId和messageIds都为空", log: logger, type: .error)
return false
}
@ -222,16 +284,90 @@ class ChatStorageHelper {
* 清空数据库
* @return 是否清空成功
*/
func clearDatabase() -> Bool {
public func clearDatabase() -> Bool {
let deleteString = "DELETE FROM messages;"
if sqlite3_exec(db, deleteString, nil, nil, nil) == SQLITE_OK {
return true
} else {
print("清空数据库失败: \(String(cString: sqlite3_errmsg(db)!))")
let errmsg = String(cString: sqlite3_errmsg(db)!)
os_log("清空数据库失败: %{public}@", log: logger, type: .error, errmsg)
return false
}
}
/**
* 获取指定会话的最近N条消息
* @param sessionId 会话ID
* @param limit 限制数量
* @return 消息列表,按时间正序排列
*/
public func getRecentMessages(sessionId: String, limit: Int) -> [[String: Any]] {
var messages: [[String: Any]] = []
// 首先检查数据库中是否有该会话的消息
let countQuery = "SELECT COUNT(*) FROM messages WHERE session_id = ?"
var countStatement: OpaquePointer?
if sqlite3_prepare_v2(db, countQuery, -1, &countStatement, nil) == SQLITE_OK {
sqlite3_bind_text(countStatement, 1, (sessionId as NSString).utf8String, -1, nil)
if sqlite3_step(countStatement) == SQLITE_ROW {
_ = Int(sqlite3_column_int(countStatement, 0))
}
sqlite3_finalize(countStatement)
}
// 构建查询语句 - 按时间倒序获取最近的N条,然后在结果中再按时间正序
let queryString = """
SELECT * FROM (
SELECT id, message, sender, metadata, created_at
FROM messages
WHERE session_id = ?
ORDER BY created_at DESC
LIMIT ?
) tmp ORDER BY created_at ASC
"""
var queryStatement: OpaquePointer?
if sqlite3_prepare_v2(db, queryString, -1, &queryStatement, nil) == SQLITE_OK {
sqlite3_bind_text(queryStatement, 1, (sessionId as NSString).utf8String, -1, nil)
sqlite3_bind_int(queryStatement, 2, Int32(limit))
while sqlite3_step(queryStatement) == SQLITE_ROW {
let id = sqlite3_column_int(queryStatement, 0)
let messagePtr = sqlite3_column_text(queryStatement, 1)
let message = messagePtr != nil ? String(cString: messagePtr!) : ""
let senderPtr = sqlite3_column_text(queryStatement, 2)
let sender = senderPtr != nil ? String(cString: senderPtr!) : ""
let metadataPtr = sqlite3_column_text(queryStatement, 3)
let metadata = metadataPtr != nil ? String(cString: metadataPtr!) : nil
let createdAt = sqlite3_column_int(queryStatement, 4)
var messageDict: [String: Any] = [
"id": id,
"message": message,
"sender": sender,
"created_at": createdAt
]
if let metadata = metadata {
messageDict["metadata"] = metadata
}
messages.append(messageDict)
}
sqlite3_finalize(queryStatement)
} else {
let errmsg = String(cString: sqlite3_errmsg(db)!)
os_log("查询最近消息失败: %{public}@", log: logger, type: .error, errmsg)
}
return messages
}
}

16
local_plugins/chat_storage/ios/Classes/SwiftChatStoragePlugin.swift

@ -7,19 +7,11 @@ import UIKit
*/
public class SwiftChatStoragePlugin: NSObject, FlutterPlugin {
private let TAG = "ChatStoragePlugin"
private var chatStorageHelper: ChatStorageHelper!
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "chat_storage", binaryMessenger: registrar.messenger())
let instance = SwiftChatStoragePlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
print("\(instance.TAG): 聊天存储插件已加载")
}
override init() {
super.init()
chatStorageHelper = ChatStorageHelper()
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
@ -36,7 +28,7 @@ public class SwiftChatStoragePlugin: NSObject, FlutterPlugin {
let metadata = args["metadata"] as? String
let id = chatStorageHelper.saveMessage(
let id = ChatStorageHelper.shared.saveMessage(
sessionId: sessionId,
message: message,
sender: sender,
@ -64,7 +56,7 @@ public class SwiftChatStoragePlugin: NSObject, FlutterPlugin {
let page = args["page"] as? Int ?? 1
let pageSize = args["pageSize"] as? Int ?? 20
let messagesJson = chatStorageHelper.getMessages(
let messagesJson = ChatStorageHelper.shared.getMessages(
sessionId: sessionId,
page: page,
pageSize: pageSize
@ -87,7 +79,7 @@ public class SwiftChatStoragePlugin: NSObject, FlutterPlugin {
let sessionId = args["sessionId"] as? String
let messageIds = args["messageIds"] as? [Int]
let success = chatStorageHelper.deleteMessages(
let success = ChatStorageHelper.shared.deleteMessages(
sessionId: sessionId,
messageIds: messageIds
)
@ -104,7 +96,7 @@ public class SwiftChatStoragePlugin: NSObject, FlutterPlugin {
case "clearDatabase":
do {
let success = chatStorageHelper.clearDatabase()
let success = ChatStorageHelper.shared.clearDatabase()
result(["success": success])
} catch {
print("\(TAG): 清空数据库时发生错误 \(error.localizedDescription)")

38
local_plugins/open_ai_service/ios/Classes/SystemFunctionHandler.swift

@ -437,21 +437,30 @@ private class SendTextMessageHandler: FunctionHandler {
throw OpenAIException("缺少必要参数")
}
let result = sendTextMessage(contact: contact, message: message)
let result = await sendTextMessage(contact: contact, message: message)
return "{\"result\": \"\(result.message)\"}"
}
private func sendTextMessage(contact: String, message: String) -> Result {
private func sendTextMessage(contact: String, message: String) async -> Result {
// 在iOS中,我们只能打开短信应用,无法直接发送短信
guard let url = URL(string: "sms:\(contact)&body=\(message.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")") else {
return Result(success: false, message: "无法创建短信URL")
}
return await withCheckedContinuation { continuation in
DispatchQueue.main.async {
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url)
return Result(success: true, message: "已打开短信应用")
UIApplication.shared.open(url) { success in
if success {
continuation.resume(returning: Result(success: true, message: "已打开短信应用"))
} else {
return Result(success: false, message: "无法打开短信应用")
continuation.resume(returning: Result(success: false, message: "打开短信应用失败"))
}
}
} else {
continuation.resume(returning: Result(success: false, message: "无法打开短信应用"))
}
}
}
}
}
@ -463,20 +472,29 @@ private class MakePhoneCallHandler: FunctionHandler {
throw OpenAIException("缺少联系人参数")
}
let result = makePhoneCall(contact: contact)
let result = await makePhoneCall(contact: contact)
return "{\"result\": \"\(result.message)\"}"
}
private func makePhoneCall(contact: String) -> Result {
private func makePhoneCall(contact: String) async -> Result {
guard let url = URL(string: "tel:\(contact)") else {
return Result(success: false, message: "无法创建电话URL")
}
return await withCheckedContinuation { continuation in
DispatchQueue.main.async {
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url)
return Result(success: true, message: "已发起电话呼叫")
UIApplication.shared.open(url) { success in
if success {
continuation.resume(returning: Result(success: true, message: "已发起电话呼叫"))
} else {
return Result(success: false, message: "无法拨打电话")
continuation.resume(returning: Result(success: false, message: "发起电话呼叫失败"))
}
}
} else {
continuation.resume(returning: Result(success: false, message: "无法拨打电话"))
}
}
}
}
}

45
test.md

@ -1,45 +0,0 @@
请帮我生成一个基于 GetX 最佳实践的 Flutter 应用项目骨架。要求如下:
1. 目录结构
- lib/
- main.dart
- app/
- data/
- providers/ # 网络请求、API 客户端(使用 GetConnect)
- repositories/ # Repository 层,封装业务数据源
- services/ # 全局服务(如 ThemeService、AuthService)
- modules/
- home/ # 示例模块
- bindings/ # HomeBinding,注册 Controller、Service
- controllers/ # HomeController
- models/ # HomeModel 数据类
- views/ # HomePage 页面
- routes/
- app_routes.dart # 路由常量
- app_pages.dart # GetPage 列表,包括中间件示例
- translations/ # 多语言文件(en_US、zh_CN)和 Translations 实现
- widgets/ # 公共 Widget(Loading、ErrorView)
- utils/ # 日志 Logger、网络拦截器等工具函数
2. main.dart
- 使用 GetMaterialApp
- 配置 initialRoute、getPages、theme、darkTheme、locale、translations
3. 依赖注入(Bindings)
- 在 app/modules/**/bindings 中,用 Get.lazyPut、Get.putAsync、Get.create 分别示例单例、异步初始化和工厂模式
4. 状态管理
- 在 Controller 中使用 Rx<T> + .obs
- 在视图中用 Obx、GetX、GetBuilder
- 在 Controller 的 onInit、onReady、onClose 中写注释说明生命周期
5. 路由管理
- 在 app/routes/app_routes.dart 定义路由名称常量
- 在 app/routes/app_pages.dart 用 GetPage 配置路由、binding
8. 架构模式
- 强调 “Repository → Service → Controller → View” 分层
- 各层代码注释职责
- 代码风格统一、注释清晰
Loading…
Cancel
Save