bleam 0.0.11 → 0.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai.cjs +2314 -2173
- package/dist/ai.d.cts +227 -322
- package/dist/ai.d.ts +227 -322
- package/dist/ai.js +2309 -2159
- package/dist/cli.d.cts +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/config.d.cts +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/{files-Ds1wT8C2.js → files-BXVkPrPN.js} +6 -1
- package/dist/{files-Bo7h9fik.cjs → files-DxaQ-Nv0.cjs} +11 -0
- package/dist/files.cjs +1 -1
- package/dist/files.d.cts +1 -1
- package/dist/files.d.ts +1 -1
- package/dist/files.js +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/schema-B5BfdswF.js +226 -0
- package/dist/schema-BnVZOXfu.cjs +286 -0
- package/dist/schema-D5eImHxu.d.cts +125 -0
- package/dist/schema-SSjokbtw.d.ts +125 -0
- package/dist/schema.cjs +10 -2
- package/dist/schema.d.cts +2 -2
- package/dist/schema.d.ts +2 -2
- package/dist/schema.js +2 -2
- package/dist/state-Dh3HLixb.js +874 -0
- package/dist/state-LssDgpff.cjs +973 -0
- package/dist/state.cjs +16 -12
- package/dist/state.d.cts +144 -140
- package/dist/state.d.ts +145 -140
- package/dist/state.js +3 -3
- package/dist/{ui-CHc4xEs_.d.ts → ui-D7bRLYee.d.ts} +7 -7
- package/dist/ui.d.ts +1 -1
- package/dist/window.d.ts +1 -1
- package/package.json +3 -1
- package/templates/foundation-models/app/index.tsx +77 -15
- package/templates/image-generation/app/index.tsx +2 -2
- package/templates/native/ios/Bleam.xcodeproj/project.pbxproj +46 -46
- package/templates/native/ios/Podfile.lock +173 -173
- package/templates/native/modules/bleam-runtime/ios/AIModule.swift +35 -357
- package/templates/text-generation/app/index.tsx +81 -50
- package/templates/updates/README.md +1 -1
- package/dist/schema-Bo5Jvqus.js +0 -90
- package/dist/schema-CYh6n8GS.d.ts +0 -58
- package/dist/schema-oeOrd3i1.d.cts +0 -58
- package/dist/schema-rQ13mrpD.cjs +0 -102
- package/dist/state-Bx0VlTlO.cjs +0 -852
- package/dist/state-CAwe-Vw1.js +0 -767
- /package/dist/{config-Cms0rvqg.d.ts → config-COcRnn5a.d.cts} +0 -0
- /package/dist/{config-CufOVJV3.d.cts → config-Chi-flpJ.d.ts} +0 -0
- /package/dist/{files-4ZEoAWiv.d.ts → files-DwA7pzr3.d.cts} +0 -0
- /package/dist/{files-Dt5mbzLq.d.cts → files-VrkQlKIT.d.ts} +0 -0
|
@@ -15,90 +15,7 @@ public final class AIModule: Module {
|
|
|
15
15
|
|
|
16
16
|
public func definition() -> ModuleDefinition {
|
|
17
17
|
Name("AI")
|
|
18
|
-
Events("imagegenProgress", "textGenerationUpdate", "localTextGenerationUpdate"
|
|
19
|
-
|
|
20
|
-
Function("isAvailable") { () -> Bool in
|
|
21
|
-
#if canImport(FoundationModels)
|
|
22
|
-
if #available(iOS 26, *) {
|
|
23
|
-
return SystemLanguageModel.default.availability == .available
|
|
24
|
-
}
|
|
25
|
-
#endif
|
|
26
|
-
return false
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
Function("availability") { () -> [String: Any] in
|
|
30
|
-
#if canImport(FoundationModels)
|
|
31
|
-
if #available(iOS 26, *) {
|
|
32
|
-
switch SystemLanguageModel.default.availability {
|
|
33
|
-
case .available:
|
|
34
|
-
return ["available": true]
|
|
35
|
-
case .unavailable(.deviceNotEligible):
|
|
36
|
-
return ["available": false, "reason": "device_not_eligible"]
|
|
37
|
-
case .unavailable(.appleIntelligenceNotEnabled):
|
|
38
|
-
return ["available": false, "reason": "apple_intelligence_not_enabled"]
|
|
39
|
-
case .unavailable(.modelNotReady):
|
|
40
|
-
return ["available": false, "reason": "model_not_ready"]
|
|
41
|
-
case .unavailable:
|
|
42
|
-
return ["available": false, "reason": "unknown"]
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
#endif
|
|
46
|
-
return ["available": false, "reason": "unknown"]
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
AsyncFunction("generate") { [weak self] (request: TextGenerationRequest, promise: Promise) in
|
|
50
|
-
#if canImport(FoundationModels)
|
|
51
|
-
if #available(iOS 26, *) {
|
|
52
|
-
guard SystemLanguageModel.default.availability == .available else {
|
|
53
|
-
promise.reject("MODEL_UNAVAILABLE", "Apple Intelligence model is not available")
|
|
54
|
-
return
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
guard !request.prompt.isEmpty else {
|
|
58
|
-
promise.reject("INVALID_MESSAGE", "Expected prompt")
|
|
59
|
-
return
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
let generationID = request.id ?? UUID().uuidString
|
|
63
|
-
let task = Task { @MainActor [weak self] in
|
|
64
|
-
guard let self else { return }
|
|
65
|
-
do {
|
|
66
|
-
let tools = try Self.tools(
|
|
67
|
-
from: request.tools,
|
|
68
|
-
generationID: generationID,
|
|
69
|
-
coordinator: self.textGeneration,
|
|
70
|
-
emit: { [weak self] event in self?.sendEvent("toolCall", event) }
|
|
71
|
-
)
|
|
72
|
-
let session = Self.session(system: request.system, tools: tools)
|
|
73
|
-
let options = try Self.generationOptions(from: request)
|
|
74
|
-
|
|
75
|
-
if let schema = request.outputSchema {
|
|
76
|
-
let generationSchema = try Self.generationSchema(from: schema)
|
|
77
|
-
let response = try await session.respond(
|
|
78
|
-
to: request.prompt,
|
|
79
|
-
schema: generationSchema,
|
|
80
|
-
includeSchemaInPrompt: true,
|
|
81
|
-
options: options
|
|
82
|
-
)
|
|
83
|
-
promise.resolve(try Self.value(from: response.content, schema: schema))
|
|
84
|
-
return
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
let response = try await session.respond(to: request.prompt, options: options)
|
|
88
|
-
promise.resolve(response.content)
|
|
89
|
-
} catch {
|
|
90
|
-
Self.reject(error, promise: promise)
|
|
91
|
-
}
|
|
92
|
-
self.textGeneration.finish(generationID)
|
|
93
|
-
}
|
|
94
|
-
Task { @MainActor [weak self] in self?.textGeneration.start(generationID, task: task) }
|
|
95
|
-
} else {
|
|
96
|
-
promise.reject("UNSUPPORTED_OS", "Apple Foundation Models require iOS 26 or newer")
|
|
97
|
-
}
|
|
98
|
-
#else
|
|
99
|
-
promise.reject("UNSUPPORTED_OS", "Apple Foundation Models are unavailable in this build")
|
|
100
|
-
#endif
|
|
101
|
-
}
|
|
18
|
+
Events("imagegenProgress", "textGenerationUpdate", "localTextGenerationUpdate")
|
|
102
19
|
|
|
103
20
|
AsyncFunction("streamGenerate") { [weak self] (request: TextGenerationRequest, promise: Promise) in
|
|
104
21
|
#if canImport(FoundationModels)
|
|
@@ -116,13 +33,7 @@ public final class AIModule: Module {
|
|
|
116
33
|
let task = Task { @MainActor [weak self] in
|
|
117
34
|
guard let self else { return }
|
|
118
35
|
do {
|
|
119
|
-
let
|
|
120
|
-
from: request.tools,
|
|
121
|
-
generationID: generationID,
|
|
122
|
-
coordinator: self.textGeneration,
|
|
123
|
-
emit: { [weak self] event in self?.sendEvent("toolCall", event) }
|
|
124
|
-
)
|
|
125
|
-
let session = Self.session(system: request.system, tools: tools)
|
|
36
|
+
let session = try Self.session(system: request.system, messages: request.messages)
|
|
126
37
|
let options = try Self.generationOptions(from: request)
|
|
127
38
|
let stream = session.streamResponse(to: request.prompt, options: options)
|
|
128
39
|
var content = ""
|
|
@@ -152,23 +63,6 @@ public final class AIModule: Module {
|
|
|
152
63
|
return foundationCanceled || localCanceled
|
|
153
64
|
}
|
|
154
65
|
|
|
155
|
-
AsyncFunction("resolveToolCall") { [weak self] (request: ToolResultRequest) -> Bool in
|
|
156
|
-
await self?.textGeneration.resolve(request) ?? false
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
AsyncFunction("generateLocalText") { (request: LocalTextGenerationRequest, promise: Promise) in
|
|
160
|
-
Task { @MainActor in
|
|
161
|
-
do {
|
|
162
|
-
let event = try await ImageGenerationServiceClient.shared.runText(
|
|
163
|
-
request: Self.bonsaiTextRequest(from: request)
|
|
164
|
-
)
|
|
165
|
-
promise.resolve(event.tokenIDs)
|
|
166
|
-
} catch {
|
|
167
|
-
Self.rejectLocalTextGeneration(error, promise: promise)
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
66
|
AsyncFunction("streamLocalText") { [weak self] (request: LocalTextGenerationRequest, promise: Promise) in
|
|
173
67
|
Task { @MainActor in
|
|
174
68
|
do {
|
|
@@ -627,31 +521,35 @@ public final class AIModule: Module {
|
|
|
627
521
|
|
|
628
522
|
#if canImport(FoundationModels)
|
|
629
523
|
@available(iOS 26, *)
|
|
630
|
-
private static func session(
|
|
631
|
-
|
|
632
|
-
|
|
524
|
+
private static func session(
|
|
525
|
+
system: String?,
|
|
526
|
+
messages: [TextMessageRequest]
|
|
527
|
+
) throws -> LanguageModelSession {
|
|
528
|
+
if !messages.isEmpty {
|
|
529
|
+
var entries: [Transcript.Entry] = []
|
|
530
|
+
if system?.isEmpty == false {
|
|
531
|
+
entries.append(.instructions(Transcript.Instructions(
|
|
532
|
+
segments: system.map { [.text(Transcript.TextSegment(content: $0))] } ?? [],
|
|
533
|
+
toolDefinitions: []
|
|
534
|
+
)))
|
|
535
|
+
}
|
|
536
|
+
for message in messages {
|
|
537
|
+
let segments: [Transcript.Segment] = [.text(Transcript.TextSegment(content: message.content))]
|
|
538
|
+
switch message.role {
|
|
539
|
+
case "user":
|
|
540
|
+
entries.append(.prompt(Transcript.Prompt(segments: segments)))
|
|
541
|
+
case "assistant":
|
|
542
|
+
entries.append(.response(Transcript.Response(assetIDs: [], segments: segments)))
|
|
543
|
+
default:
|
|
544
|
+
throw ModuleError.invalidMessage("Unsupported transcript role: \(message.role)")
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
return LanguageModelSession(transcript: Transcript(entries: entries))
|
|
633
548
|
}
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
@available(iOS 26, *)
|
|
638
|
-
private static func tools(
|
|
639
|
-
from definitions: [ToolDefinitionRequest],
|
|
640
|
-
generationID: String,
|
|
641
|
-
coordinator: TextGenerationCoordinator,
|
|
642
|
-
emit: @escaping @MainActor @Sendable ([String: Any]) -> Void
|
|
643
|
-
) throws -> [any Tool] {
|
|
644
|
-
try definitions.map { definition in
|
|
645
|
-
let parameters = try generationSchema(from: definition.inputSchema)
|
|
646
|
-
return DynamicTool(
|
|
647
|
-
name: definition.name,
|
|
648
|
-
description: definition.description,
|
|
649
|
-
parameters: parameters,
|
|
650
|
-
generationID: generationID,
|
|
651
|
-
coordinator: coordinator,
|
|
652
|
-
emit: emit
|
|
653
|
-
)
|
|
549
|
+
if let system, !system.isEmpty {
|
|
550
|
+
return LanguageModelSession(instructions: system)
|
|
654
551
|
}
|
|
552
|
+
return LanguageModelSession()
|
|
655
553
|
}
|
|
656
554
|
|
|
657
555
|
@available(iOS 26, *)
|
|
@@ -663,131 +561,6 @@ public final class AIModule: Module {
|
|
|
663
561
|
)
|
|
664
562
|
}
|
|
665
563
|
|
|
666
|
-
@available(iOS 26, *)
|
|
667
|
-
private static func generationSchema(from schema: [String: Any]) throws -> GenerationSchema {
|
|
668
|
-
do {
|
|
669
|
-
return try GenerationSchema(root: dynamicSchema(from: schema), dependencies: [])
|
|
670
|
-
} catch {
|
|
671
|
-
throw ModuleError.invalidSchema(error.localizedDescription)
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
@available(iOS 26, *)
|
|
676
|
-
private static func dynamicSchema(from schema: [String: Any]) throws -> DynamicGenerationSchema {
|
|
677
|
-
guard let type = schema["type"] as? String else {
|
|
678
|
-
throw ModuleError.invalidSchema("Missing schema type")
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
switch type {
|
|
682
|
-
case "object":
|
|
683
|
-
return try objectSchema(from: schema)
|
|
684
|
-
case "array":
|
|
685
|
-
guard let itemSchema = schema["items"] as? [String: Any] else {
|
|
686
|
-
throw ModuleError.invalidSchema("Array schema must include items")
|
|
687
|
-
}
|
|
688
|
-
return try DynamicGenerationSchema(arrayOf: dynamicSchema(from: itemSchema))
|
|
689
|
-
case "string":
|
|
690
|
-
return DynamicGenerationSchema(type: String.self)
|
|
691
|
-
case "number":
|
|
692
|
-
return DynamicGenerationSchema(type: Double.self)
|
|
693
|
-
case "boolean":
|
|
694
|
-
return DynamicGenerationSchema(type: Bool.self)
|
|
695
|
-
default:
|
|
696
|
-
throw ModuleError.invalidSchema("Unsupported schema type: \(type)")
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
@available(iOS 26, *)
|
|
701
|
-
private static func objectSchema(from schema: [String: Any]) throws -> DynamicGenerationSchema {
|
|
702
|
-
let propertiesDictionary = schema["properties"] as? [String: Any] ?? [:]
|
|
703
|
-
let required = Set(schema["required"] as? [String] ?? [])
|
|
704
|
-
var properties: [DynamicGenerationSchema.Property] = []
|
|
705
|
-
|
|
706
|
-
for key in propertiesDictionary.keys.sorted() {
|
|
707
|
-
guard let propertySchema = propertiesDictionary[key] as? [String: Any] else {
|
|
708
|
-
throw ModuleError.invalidSchema("Property \(key) schema must be an object")
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
properties.append(
|
|
712
|
-
DynamicGenerationSchema.Property(
|
|
713
|
-
name: key,
|
|
714
|
-
description: nil,
|
|
715
|
-
schema: try dynamicSchema(from: propertySchema),
|
|
716
|
-
isOptional: !required.contains(key)
|
|
717
|
-
)
|
|
718
|
-
)
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
return DynamicGenerationSchema(name: schema["title"] as? String ?? "Output", properties: properties)
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
@available(iOS 26, *)
|
|
725
|
-
private static func value(from content: GeneratedContent, schema: [String: Any]) throws -> Any {
|
|
726
|
-
guard let type = schema["type"] as? String else {
|
|
727
|
-
throw ModuleError.invalidSchema("Missing schema type")
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
switch type {
|
|
731
|
-
case "object":
|
|
732
|
-
let propertiesDictionary = schema["properties"] as? [String: Any] ?? [:]
|
|
733
|
-
let output = NSMutableDictionary()
|
|
734
|
-
|
|
735
|
-
for key in propertiesDictionary.keys.sorted() {
|
|
736
|
-
guard let propertySchema = propertiesDictionary[key] as? [String: Any] else {
|
|
737
|
-
throw ModuleError.invalidSchema("Property \(key) schema must be an object")
|
|
738
|
-
}
|
|
739
|
-
output[key] = try propertyValue(from: content, property: key, schema: propertySchema)
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
return output
|
|
743
|
-
default:
|
|
744
|
-
throw ModuleError.invalidSchema("Root schema must be an object")
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
@available(iOS 26, *)
|
|
749
|
-
private static func propertyValue(
|
|
750
|
-
from content: GeneratedContent,
|
|
751
|
-
property: String,
|
|
752
|
-
schema: [String: Any]
|
|
753
|
-
) throws -> Any {
|
|
754
|
-
guard let type = schema["type"] as? String else {
|
|
755
|
-
throw ModuleError.invalidSchema("Missing schema type")
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
switch type {
|
|
759
|
-
case "string":
|
|
760
|
-
return try content.value(String.self, forProperty: property)
|
|
761
|
-
case "number":
|
|
762
|
-
return try content.value(Double.self, forProperty: property)
|
|
763
|
-
case "boolean":
|
|
764
|
-
return try content.value(Bool.self, forProperty: property)
|
|
765
|
-
case "object":
|
|
766
|
-
let nestedContent = try content.value(GeneratedContent.self, forProperty: property)
|
|
767
|
-
return try value(from: nestedContent, schema: schema)
|
|
768
|
-
case "array":
|
|
769
|
-
guard let itemSchema = schema["items"] as? [String: Any], let itemType = itemSchema["type"] as? String else {
|
|
770
|
-
throw ModuleError.invalidSchema("Array schema must include items")
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
switch itemType {
|
|
774
|
-
case "string":
|
|
775
|
-
return try content.value([String].self, forProperty: property)
|
|
776
|
-
case "number":
|
|
777
|
-
return try content.value([Double].self, forProperty: property)
|
|
778
|
-
case "boolean":
|
|
779
|
-
return try content.value([Bool].self, forProperty: property)
|
|
780
|
-
case "object":
|
|
781
|
-
let values = try content.value([GeneratedContent].self, forProperty: property)
|
|
782
|
-
return try values.map { try value(from: $0, schema: itemSchema) }
|
|
783
|
-
default:
|
|
784
|
-
throw ModuleError.invalidSchema("Unsupported array item type: \(itemType)")
|
|
785
|
-
}
|
|
786
|
-
default:
|
|
787
|
-
throw ModuleError.invalidSchema("Unsupported schema type: \(type)")
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
|
|
791
564
|
@available(iOS 26, *)
|
|
792
565
|
private static func reject(_ error: Error, promise: Promise) {
|
|
793
566
|
if error is CancellationError {
|
|
@@ -811,55 +584,14 @@ public final class AIModule: Module {
|
|
|
811
584
|
}
|
|
812
585
|
return
|
|
813
586
|
}
|
|
814
|
-
if let toolError = error as? LanguageModelSession.ToolCallError {
|
|
815
|
-
promise.reject("TOOL_ERROR", toolError.localizedDescription)
|
|
816
|
-
return
|
|
817
|
-
}
|
|
818
|
-
|
|
819
587
|
promise.reject(error)
|
|
820
588
|
}
|
|
821
589
|
#endif
|
|
822
590
|
}
|
|
823
591
|
|
|
824
|
-
#if canImport(FoundationModels)
|
|
825
|
-
@available(iOS 26, *)
|
|
826
|
-
private struct DynamicTool: Tool {
|
|
827
|
-
typealias Arguments = GeneratedContent
|
|
828
|
-
|
|
829
|
-
let name: String
|
|
830
|
-
let description: String
|
|
831
|
-
let parameters: GenerationSchema
|
|
832
|
-
let generationID: String
|
|
833
|
-
let coordinator: TextGenerationCoordinator
|
|
834
|
-
let emit: @MainActor @Sendable ([String: Any]) -> Void
|
|
835
|
-
|
|
836
|
-
func call(arguments: GeneratedContent) async throws -> String {
|
|
837
|
-
let data = Data(arguments.jsonString.utf8)
|
|
838
|
-
let value = try JSONSerialization.jsonObject(with: data)
|
|
839
|
-
return try await coordinator.call(
|
|
840
|
-
generationID: generationID,
|
|
841
|
-
name: name,
|
|
842
|
-
arguments: value,
|
|
843
|
-
emit: emit
|
|
844
|
-
)
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
#endif
|
|
848
|
-
|
|
849
|
-
private struct ToolExecutionError: LocalizedError {
|
|
850
|
-
let message: String
|
|
851
|
-
var errorDescription: String? { message }
|
|
852
|
-
}
|
|
853
|
-
|
|
854
592
|
@MainActor
|
|
855
593
|
private final class TextGenerationCoordinator {
|
|
856
|
-
private struct PendingToolCall {
|
|
857
|
-
let generationID: String
|
|
858
|
-
let continuation: CheckedContinuation<String, Error>
|
|
859
|
-
}
|
|
860
|
-
|
|
861
594
|
private var tasks: [String: Task<Void, Never>] = [:]
|
|
862
|
-
private var toolCalls: [String: PendingToolCall] = [:]
|
|
863
595
|
|
|
864
596
|
func start(_ id: String, task: Task<Void, Never>) {
|
|
865
597
|
tasks[id]?.cancel()
|
|
@@ -868,67 +600,22 @@ private final class TextGenerationCoordinator {
|
|
|
868
600
|
|
|
869
601
|
func finish(_ id: String) {
|
|
870
602
|
tasks[id] = nil
|
|
871
|
-
failToolCalls(for: id, error: CancellationError())
|
|
872
603
|
}
|
|
873
604
|
|
|
874
605
|
func cancel(_ id: String) -> Bool {
|
|
875
606
|
guard let task = tasks.removeValue(forKey: id) else { return false }
|
|
876
607
|
task.cancel()
|
|
877
|
-
failToolCalls(for: id, error: CancellationError())
|
|
878
608
|
return true
|
|
879
609
|
}
|
|
880
|
-
|
|
881
|
-
func call(
|
|
882
|
-
generationID: String,
|
|
883
|
-
name: String,
|
|
884
|
-
arguments: Any,
|
|
885
|
-
emit: @escaping @MainActor @Sendable ([String: Any]) -> Void
|
|
886
|
-
) async throws -> String {
|
|
887
|
-
try Task.checkCancellation()
|
|
888
|
-
let callID = UUID().uuidString
|
|
889
|
-
return try await withCheckedThrowingContinuation { continuation in
|
|
890
|
-
toolCalls[callID] = PendingToolCall(generationID: generationID, continuation: continuation)
|
|
891
|
-
emit([
|
|
892
|
-
"generationId": generationID,
|
|
893
|
-
"callId": callID,
|
|
894
|
-
"name": name,
|
|
895
|
-
"arguments": arguments,
|
|
896
|
-
])
|
|
897
|
-
}
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
func resolve(_ request: ToolResultRequest) -> Bool {
|
|
901
|
-
guard let pending = toolCalls.removeValue(forKey: request.callId),
|
|
902
|
-
pending.generationID == request.generationId
|
|
903
|
-
else { return false }
|
|
904
|
-
|
|
905
|
-
if let error = request.error {
|
|
906
|
-
pending.continuation.resume(throwing: ToolExecutionError(message: error))
|
|
907
|
-
} else {
|
|
908
|
-
pending.continuation.resume(returning: request.output ?? "")
|
|
909
|
-
}
|
|
910
|
-
return true
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
private func failToolCalls(for generationID: String, error: Error) {
|
|
914
|
-
let matches = toolCalls.filter { $0.value.generationID == generationID }
|
|
915
|
-
for (id, pending) in matches {
|
|
916
|
-
toolCalls[id] = nil
|
|
917
|
-
pending.continuation.resume(throwing: error)
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
610
|
}
|
|
921
611
|
|
|
922
612
|
private enum ModuleError: Error, LocalizedError {
|
|
923
|
-
case invalidSchema(String)
|
|
924
613
|
case invalidMessage(String)
|
|
925
614
|
case invalidModel(String)
|
|
926
615
|
case imageGeneration(String)
|
|
927
616
|
|
|
928
617
|
var errorDescription: String? {
|
|
929
618
|
switch self {
|
|
930
|
-
case .invalidSchema(let message):
|
|
931
|
-
return "Invalid schema: \(message)"
|
|
932
619
|
case .invalidMessage(let message), .invalidModel(let message), .imageGeneration(let message):
|
|
933
620
|
return message
|
|
934
621
|
}
|
|
@@ -1368,10 +1055,14 @@ private struct TextGenerationRequest: Record {
|
|
|
1368
1055
|
@Field var id: String?
|
|
1369
1056
|
@Field var system: String?
|
|
1370
1057
|
@Field var prompt: String = ""
|
|
1371
|
-
@Field var
|
|
1058
|
+
@Field var messages: [TextMessageRequest] = []
|
|
1372
1059
|
@Field var temperature: Double?
|
|
1373
1060
|
@Field var maxTokens: Int?
|
|
1374
|
-
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
private struct TextMessageRequest: Record {
|
|
1064
|
+
@Field var role: String = ""
|
|
1065
|
+
@Field var content: String = ""
|
|
1375
1066
|
}
|
|
1376
1067
|
|
|
1377
1068
|
private struct LocalTextGenerationRequest: Record {
|
|
@@ -1389,19 +1080,6 @@ private struct LocalTextGenerationRequest: Record {
|
|
|
1389
1080
|
@Field var seed: Int?
|
|
1390
1081
|
}
|
|
1391
1082
|
|
|
1392
|
-
private struct ToolDefinitionRequest: Record {
|
|
1393
|
-
@Field var name: String = ""
|
|
1394
|
-
@Field var description: String = ""
|
|
1395
|
-
@Field var inputSchema: [String: Any] = [:]
|
|
1396
|
-
}
|
|
1397
|
-
|
|
1398
|
-
private struct ToolResultRequest: Record {
|
|
1399
|
-
@Field var generationId: String = ""
|
|
1400
|
-
@Field var callId: String = ""
|
|
1401
|
-
@Field var output: String?
|
|
1402
|
-
@Field var error: String?
|
|
1403
|
-
}
|
|
1404
|
-
|
|
1405
1083
|
private struct ImageGenerationRequest: Record {
|
|
1406
1084
|
@Field var model: String = ""
|
|
1407
1085
|
@Field var prompt: String = ""
|
|
@@ -1,55 +1,48 @@
|
|
|
1
|
-
import { bonsai, prepareTextModel,
|
|
1
|
+
import { bonsai, chat, prepareTextModel, useChat, useChatList } from 'bleam/ai'
|
|
2
2
|
import { sx } from 'bleam/styles'
|
|
3
3
|
import { Button } from 'bleam/ui'
|
|
4
4
|
import { Window } from 'bleam/window'
|
|
5
|
-
import { useRef, useState } from 'react'
|
|
5
|
+
import { useEffect, useRef, useState } from 'react'
|
|
6
6
|
import { Text, TextInput, View } from 'react-native'
|
|
7
7
|
|
|
8
8
|
const appName = 'Bleam Basic'
|
|
9
9
|
const model = bonsai('ternary-bonsai-4b')
|
|
10
10
|
|
|
11
11
|
export default function App() {
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
)
|
|
15
|
-
const [
|
|
16
|
-
const [status, setStatus] = useState('Ready')
|
|
17
|
-
const [isRunning, setIsRunning] = useState(false)
|
|
18
|
-
const controller = useRef<AbortController>(null)
|
|
12
|
+
const chats = useChatList()
|
|
13
|
+
const creating = useRef(false)
|
|
14
|
+
const [path, setPath] = useState<string | null>(null)
|
|
15
|
+
const [status, setStatus] = useState('Preparing local model...')
|
|
19
16
|
|
|
20
|
-
|
|
21
|
-
if (
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (chats.length > 0 || creating.current) return
|
|
19
|
+
creating.current = true
|
|
20
|
+
void chat.create({ title: 'Private writing' }).catch((error) => {
|
|
21
|
+
setStatus(error instanceof Error ? error.message : String(error))
|
|
22
|
+
})
|
|
23
|
+
}, [chats.length])
|
|
26
24
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
)
|
|
34
|
-
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
const controller = new AbortController()
|
|
27
|
+
void prepareTextModel(model, {
|
|
28
|
+
signal: controller.signal,
|
|
29
|
+
onProgress(progress) {
|
|
30
|
+
setStatus(
|
|
31
|
+
`Preparing model ${Math.round(progress.modelFraction * 100)}%`,
|
|
32
|
+
)
|
|
33
|
+
},
|
|
34
|
+
})
|
|
35
|
+
.then((prepared) => {
|
|
36
|
+
setPath(prepared.path)
|
|
37
|
+
setStatus('Ready')
|
|
35
38
|
})
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
setAnswer(text)
|
|
44
|
-
}
|
|
45
|
-
setStatus('Complete')
|
|
46
|
-
} catch (error) {
|
|
47
|
-
setStatus(error instanceof Error ? error.message : String(error))
|
|
48
|
-
} finally {
|
|
49
|
-
if (controller.current === nextController) controller.current = null
|
|
50
|
-
setIsRunning(false)
|
|
51
|
-
}
|
|
52
|
-
}
|
|
39
|
+
.catch((error) => {
|
|
40
|
+
if (!controller.signal.aborted) {
|
|
41
|
+
setStatus(error instanceof Error ? error.message : String(error))
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
return () => controller.abort()
|
|
45
|
+
}, [])
|
|
53
46
|
|
|
54
47
|
return (
|
|
55
48
|
<View style={sx('flex-1', 'p-8', 'gap-6')}>
|
|
@@ -58,14 +51,41 @@ export default function App() {
|
|
|
58
51
|
Private Writing Assistant
|
|
59
52
|
</Text>
|
|
60
53
|
<Text style={sx('text-secondary-label')}>
|
|
61
|
-
Ternary Bonsai runs locally after its first model download.
|
|
54
|
+
Ternary Bonsai runs locally after its first model download. Chats return
|
|
55
|
+
after relaunch.
|
|
62
56
|
</Text>
|
|
57
|
+
<Text style={sx('text-secondary-label')}>{status}</Text>
|
|
58
|
+
{path && chats[0] ? <Conversation id={chats[0].id} path={path} /> : null}
|
|
59
|
+
</View>
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function Conversation({ id, path }: { id: string; path: string }) {
|
|
64
|
+
const conversation = useChat(id, {
|
|
65
|
+
model,
|
|
66
|
+
path,
|
|
67
|
+
system: 'You are a concise private writing assistant.',
|
|
68
|
+
})
|
|
69
|
+
const [prompt, setPrompt] = useState(
|
|
70
|
+
'Rewrite this note so it is clear and concise: We should probably meet sometime next week to discuss the launch.',
|
|
71
|
+
)
|
|
72
|
+
const latestAssistant = conversation.messages.findLast(
|
|
73
|
+
(message) => message.role === 'assistant',
|
|
74
|
+
)
|
|
75
|
+
const retryable =
|
|
76
|
+
latestAssistant?.status === 'failed' ||
|
|
77
|
+
latestAssistant?.status === 'canceled'
|
|
78
|
+
? latestAssistant
|
|
79
|
+
: null
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<View style={sx('gap-6')}>
|
|
63
83
|
<TextInput
|
|
64
84
|
multiline
|
|
65
85
|
value={prompt}
|
|
66
86
|
onChangeText={setPrompt}
|
|
67
87
|
style={sx(
|
|
68
|
-
|
|
88
|
+
{ minHeight: 128 },
|
|
69
89
|
'rounded-xl',
|
|
70
90
|
'border',
|
|
71
91
|
'border-separator',
|
|
@@ -74,20 +94,31 @@ export default function App() {
|
|
|
74
94
|
/>
|
|
75
95
|
<View style={sx('flex-row', 'gap-3')}>
|
|
76
96
|
<Button
|
|
77
|
-
title="
|
|
78
|
-
loading={
|
|
79
|
-
disabled={
|
|
80
|
-
onPress={() => void
|
|
97
|
+
title="Send"
|
|
98
|
+
loading={conversation.isGenerating}
|
|
99
|
+
disabled={!conversation.canSend}
|
|
100
|
+
onPress={() => void conversation.send(prompt)}
|
|
81
101
|
/>
|
|
82
102
|
<Button
|
|
83
103
|
title="Cancel"
|
|
84
104
|
variant="secondary"
|
|
85
|
-
disabled={!
|
|
86
|
-
onPress={() =>
|
|
105
|
+
disabled={!conversation.canCancel}
|
|
106
|
+
onPress={() => void conversation.cancel()}
|
|
87
107
|
/>
|
|
108
|
+
{retryable ? (
|
|
109
|
+
<Button
|
|
110
|
+
title="Retry"
|
|
111
|
+
variant="secondary"
|
|
112
|
+
onPress={() => void conversation.retry(retryable.id)}
|
|
113
|
+
/>
|
|
114
|
+
) : null}
|
|
88
115
|
</View>
|
|
89
|
-
|
|
90
|
-
|
|
116
|
+
{conversation.messages.map((message) => (
|
|
117
|
+
<Text key={message.id} style={sx('text-lg', 'text-label')}>
|
|
118
|
+
{message.role === 'user' ? 'You: ' : 'Assistant: '}
|
|
119
|
+
{message.content || message.status}
|
|
120
|
+
</Text>
|
|
121
|
+
))}
|
|
91
122
|
</View>
|
|
92
123
|
)
|
|
93
124
|
}
|