Skip to content

Commit 59287db

Browse files
committed
initial version
1 parent 400c7c6 commit 59287db

File tree

9 files changed

+341
-25
lines changed

9 files changed

+341
-25
lines changed

blockchain/blockchain.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,9 @@ func (bl *FxBlockchain) serve(w http.ResponseWriter, r *http.Request) {
533533
actionFetchContainerLogs: func(from peer.ID, w http.ResponseWriter, r *http.Request) {
534534
bl.handleFetchContainerLogs(r.Context(), from, w, r)
535535
},
536+
actionChatWithAI: func(from peer.ID, w http.ResponseWriter, r *http.Request) {
537+
bl.handleChatWithAI(r.Context(), from, w, r)
538+
},
536539
actionFindBestAndTargetInLogs: func(from peer.ID, w http.ResponseWriter, r *http.Request) {
537540
bl.handleFindBestAndTargetInLogs(r.Context(), from, w, r)
538541
},
@@ -981,7 +984,7 @@ func (bl *FxBlockchain) authorized(pid peer.ID, action string) bool {
981984
switch action {
982985
case actionReplicateInPool:
983986
return (bl.authorizer == bl.h.ID() || bl.authorizer == "")
984-
case actionBloxFreeSpace, actionAccountFund, actionManifestBatchUpload, actionAssetsBalance, actionGetDatastoreSize, actionGetFolderSize, actionFindBestAndTargetInLogs, actionFetchContainerLogs, actionEraseBlData, actionWifiRemoveall, actionReboot, actionPartition, actionDeleteWifi, actionDisconnectWifi, actionDeleteFulaConfig, actionGetAccount, actionSeeded, actionAccountExists, actionPoolCreate, actionPoolJoin, actionPoolCancelJoin, actionPoolRequests, actionPoolList, actionPoolVote, actionPoolLeave, actionManifestUpload, actionManifestStore, actionManifestAvailable, actionManifestRemove, actionManifestRemoveStorer, actionManifestRemoveStored, actionTransferToMumbai, actionListPlugins, actionListActivePlugins, actionInstallPlugin, actionUninstallPlugin, actionGetInstallStatus, actionGetInstallOutput, actionUpdatePlugin:
987+
case actionBloxFreeSpace, actionAccountFund, actionManifestBatchUpload, actionAssetsBalance, actionGetDatastoreSize, actionGetFolderSize, actionFindBestAndTargetInLogs, actionFetchContainerLogs, actionChatWithAI, actionEraseBlData, actionWifiRemoveall, actionReboot, actionPartition, actionDeleteWifi, actionDisconnectWifi, actionDeleteFulaConfig, actionGetAccount, actionSeeded, actionAccountExists, actionPoolCreate, actionPoolJoin, actionPoolCancelJoin, actionPoolRequests, actionPoolList, actionPoolVote, actionPoolLeave, actionManifestUpload, actionManifestStore, actionManifestAvailable, actionManifestRemove, actionManifestRemoveStorer, actionManifestRemoveStored, actionTransferToMumbai, actionListPlugins, actionListActivePlugins, actionInstallPlugin, actionUninstallPlugin, actionGetInstallStatus, actionGetInstallOutput, actionUpdatePlugin:
985988
bl.authorizedPeersLock.RLock()
986989
_, ok := bl.authorizedPeers[pid]
987990
bl.authorizedPeersLock.RUnlock()

blockchain/blox.go

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package blockchain
22

33
import (
4+
"bufio"
45
"bytes"
56
"context"
67
"encoding/json"
78
"fmt"
89
"io"
910
"net/http"
11+
"sync"
1012

1113
"github.com/functionland/go-fula/wap/pkg/wifi"
1214
"github.com/libp2p/go-libp2p/core/network"
@@ -20,6 +22,49 @@ const (
2022
GB = 1024 * MB
2123
)
2224

25+
type StreamBuffer struct {
26+
mu sync.Mutex
27+
chunks []string
28+
closed bool
29+
err error
30+
}
31+
32+
func NewStreamBuffer() *StreamBuffer {
33+
return &StreamBuffer{
34+
chunks: make([]string, 0),
35+
}
36+
}
37+
38+
func (b *StreamBuffer) AddChunk(chunk string) {
39+
b.mu.Lock()
40+
defer b.mu.Unlock()
41+
if b.closed {
42+
return
43+
}
44+
b.chunks = append(b.chunks, chunk)
45+
}
46+
47+
func (b *StreamBuffer) Close(err error) {
48+
b.mu.Lock()
49+
defer b.mu.Unlock()
50+
b.closed = true
51+
b.err = err
52+
}
53+
54+
func (b *StreamBuffer) GetChunk() (string, error) {
55+
b.mu.Lock()
56+
defer b.mu.Unlock()
57+
if len(b.chunks) > 0 {
58+
chunk := b.chunks[0]
59+
b.chunks = b.chunks[1:]
60+
return chunk, nil
61+
}
62+
if b.closed {
63+
return "", b.err
64+
}
65+
return "", nil // No chunk available yet
66+
}
67+
2368
func (bl *FxBlockchain) BloxFreeSpace(ctx context.Context, to peer.ID) ([]byte, error) {
2469
if bl.allowTransientConnection {
2570
ctx = network.WithUseTransient(ctx, "fx.blockchain")
@@ -245,6 +290,55 @@ func (bl *FxBlockchain) FetchContainerLogs(ctx context.Context, to peer.ID, r wi
245290
}
246291
}
247292

293+
func (bl *FxBlockchain) ChatWithAI(ctx context.Context, to peer.ID, r wifi.ChatWithAIRequest) (*StreamBuffer, error) {
294+
if bl.allowTransientConnection {
295+
ctx = network.WithUseTransient(ctx, "fx.blockchain")
296+
}
297+
298+
// Encode the request into JSON
299+
var buf bytes.Buffer
300+
if err := json.NewEncoder(&buf).Encode(r); err != nil {
301+
return nil, fmt.Errorf("failed to encode request: %v", err)
302+
}
303+
304+
// Create the HTTP request
305+
req, err := http.NewRequestWithContext(ctx, http.MethodPost, "http://"+to.String()+".invalid/"+actionChatWithAI, &buf)
306+
if err != nil {
307+
return nil, fmt.Errorf("failed to create request: %v", err)
308+
}
309+
310+
resp, err := bl.c.Do(req)
311+
if err != nil {
312+
return nil, fmt.Errorf("failed to send request: %v", err)
313+
}
314+
if resp.StatusCode != http.StatusOK {
315+
defer resp.Body.Close()
316+
bodyBytes, _ := io.ReadAll(resp.Body)
317+
return nil, fmt.Errorf("unexpected response: %d; body: %s", resp.StatusCode, string(bodyBytes))
318+
}
319+
320+
buffer := NewStreamBuffer() // Create a new StreamBuffer
321+
322+
go func() {
323+
defer resp.Body.Close()
324+
reader := bufio.NewReader(resp.Body)
325+
for {
326+
line, err := reader.ReadString('\n') // Read each chunk line by line
327+
if err != nil {
328+
if err == io.EOF {
329+
buffer.Close(nil) // Close buffer with no error
330+
} else {
331+
buffer.Close(fmt.Errorf("error reading response stream: %v", err))
332+
}
333+
break
334+
}
335+
buffer.AddChunk(line) // Add each chunk to the buffer
336+
}
337+
}()
338+
339+
return buffer, nil // Return the StreamBuffer
340+
}
341+
248342
func (bl *FxBlockchain) FindBestAndTargetInLogs(ctx context.Context, to peer.ID, r wifi.FindBestAndTargetInLogsRequest) ([]byte, error) {
249343

250344
if bl.allowTransientConnection {
@@ -468,6 +562,64 @@ func (bl *FxBlockchain) handleFetchContainerLogs(ctx context.Context, from peer.
468562

469563
}
470564

565+
func (bl *FxBlockchain) handleChatWithAI(ctx context.Context, from peer.ID, w http.ResponseWriter, r *http.Request) {
566+
log := log.With("action", actionChatWithAI, "from", from)
567+
568+
var req wifi.ChatWithAIRequest
569+
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
570+
log.Error("failed to decode request: %v", err)
571+
http.Error(w, "failed to decode request", http.StatusBadRequest)
572+
return
573+
}
574+
575+
w.Header().Set("Content-Type", "application/json")
576+
w.WriteHeader(http.StatusAccepted) // Use StatusAccepted for consistency
577+
578+
flusher, ok := w.(http.Flusher)
579+
if !ok {
580+
http.Error(w, "Streaming not supported", http.StatusInternalServerError)
581+
return
582+
}
583+
584+
chunks, err := wifi.FetchAIResponse(ctx, req.AIModel, req.UserMessage)
585+
if err != nil {
586+
log.Error("error in fetchAIResponse: %v", err)
587+
http.Error(w, fmt.Sprintf("Error fetching AI response: %v", err), http.StatusInternalServerError)
588+
return
589+
}
590+
591+
log.Debugw("Streaming AI response started", "ai_model", req.AIModel)
592+
defer log.Debugw("Streaming AI response ended", "ai_model", req.AIModel)
593+
594+
for {
595+
select {
596+
case <-ctx.Done(): // Handle client disconnect or cancellation
597+
log.Warn("client disconnected")
598+
return
599+
case chunk, ok := <-chunks:
600+
if !ok {
601+
return // Channel closed
602+
}
603+
response := wifi.ChatWithAIResponse{
604+
Status: true,
605+
Msg: chunk,
606+
}
607+
608+
if err := json.NewEncoder(w).Encode(response); err != nil {
609+
log.Error("failed to write response: %v", err)
610+
errorResponse := wifi.ChatWithAIResponse{
611+
Status: false,
612+
Msg: fmt.Sprintf("Error writing response: %v", err),
613+
}
614+
json.NewEncoder(w).Encode(errorResponse) // Send error as part of stream
615+
flusher.Flush()
616+
return
617+
}
618+
flusher.Flush()
619+
}
620+
}
621+
}
622+
471623
func (bl *FxBlockchain) handleFindBestAndTargetInLogs(ctx context.Context, from peer.ID, w http.ResponseWriter, r *http.Request) {
472624
log := log.With("action", actionFindBestAndTargetInLogs, "from", from)
473625

blockchain/interface.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ const (
6464
actionGetInstallOutput = "get-install-output"
6565
actionGetInstallStatus = "get-install-status"
6666
actionUpdatePlugin = "update-plugin"
67+
68+
// AI
69+
actionChatWithAI = "chat-ai"
6770
)
6871

6972
type ReplicateRequest struct {
@@ -520,6 +523,9 @@ type Blockchain interface {
520523
GetInstallOutput(context.Context, peer.ID, string, string) ([]byte, error)
521524
GetInstallStatus(context.Context, peer.ID, string) ([]byte, error)
522525
UpdatePlugin(context.Context, peer.ID, string) ([]byte, error)
526+
527+
// AI
528+
ChatWithAI(context.Context, peer.ID, wifi.ChatWithAIRequest) (*StreamBuffer, error)
523529
}
524530

525531
var requestTypes = map[string]reflect.Type{
@@ -574,6 +580,9 @@ var requestTypes = map[string]reflect.Type{
574580
actionGetInstallOutput: reflect.TypeOf(GetInstallOutputRequest{}),
575581
actionGetInstallStatus: reflect.TypeOf(GetInstallStatusRequest{}),
576582
actionUpdatePlugin: reflect.TypeOf(UpdatePluginRequest{}),
583+
584+
// AI
585+
actionChatWithAI: reflect.TypeOf(wifi.ChatWithAIRequest{}),
577586
}
578587

579588
var responseTypes = map[string]reflect.Type{
@@ -628,4 +637,7 @@ var responseTypes = map[string]reflect.Type{
628637
actionGetInstallOutput: reflect.TypeOf(GetInstallOutputResponse{}),
629638
actionGetInstallStatus: reflect.TypeOf(GetInstallStatusResponse{}),
630639
actionUpdatePlugin: reflect.TypeOf(UpdatePluginResponse{}),
640+
641+
// AI
642+
actionChatWithAI: reflect.TypeOf(wifi.ChatWithAIResponse{}),
631643
}

go.mod

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
module github.com/functionland/go-fula
22

3-
go 1.21
3+
go 1.22.0
4+
5+
toolchain go1.22.1
46

57
require (
68
github.com/docker/docker v24.0.7+incompatible
9+
github.com/google/uuid v1.6.0
710
github.com/grandcat/zeroconf v1.0.0
811
github.com/ipfs-cluster/ipfs-cluster v1.0.8
912
github.com/ipfs/boxo v0.17.0
@@ -28,14 +31,16 @@ require (
2831
github.com/mdp/qrterminal v1.0.1
2932
github.com/mr-tron/base58 v1.2.0
3033
github.com/multiformats/go-multiaddr v0.12.2
34+
github.com/multiformats/go-multibase v0.2.0
3135
github.com/multiformats/go-multicodec v0.9.0
3236
github.com/multiformats/go-multihash v0.2.3
3337
github.com/multiformats/go-varint v0.0.7
38+
github.com/sony/gobreaker v0.5.0
3439
github.com/tyler-smith/go-bip39 v1.1.0
3540
github.com/urfave/cli/v2 v2.27.1
3641
go.uber.org/ratelimit v0.3.0
37-
golang.org/x/crypto v0.21.0
38-
golang.org/x/sync v0.6.0
42+
golang.org/x/crypto v0.32.0
43+
golang.org/x/sync v0.10.0
3944
gopkg.in/ini.v1 v1.67.0
4045
gopkg.in/yaml.v3 v3.0.1
4146
)
@@ -49,7 +54,7 @@ require (
4954
github.com/morikuni/aec v1.0.0 // indirect
5055
github.com/opencontainers/go-digest v1.0.0 // indirect
5156
github.com/opencontainers/image-spec v1.0.2 // indirect
52-
golang.org/x/sys v0.18.0 // indirect
57+
golang.org/x/sys v0.29.0 // indirect
5358
gotest.tools/v3 v3.4.0 // indirect
5459
)
5560

@@ -110,7 +115,6 @@ require (
110115
github.com/golang/snappy v0.0.4 // indirect
111116
github.com/google/gopacket v1.1.19 // indirect
112117
github.com/google/pprof v0.0.0-20231229205709-960ae82b1e42 // indirect
113-
github.com/google/uuid v1.6.0 // indirect
114118
github.com/gorilla/mux v1.8.1 // indirect
115119
github.com/gorilla/websocket v1.5.1 // indirect
116120
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect
@@ -193,7 +197,6 @@ require (
193197
github.com/multiformats/go-base36 v0.2.0 // indirect
194198
github.com/multiformats/go-multiaddr-dns v0.3.1 // indirect
195199
github.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect
196-
github.com/multiformats/go-multibase v0.2.0 // indirect
197200
github.com/multiformats/go-multistream v0.5.0 // indirect
198201
github.com/onsi/ginkgo/v2 v2.13.2 // indirect
199202
github.com/opencontainers/runtime-spec v1.1.0 // indirect
@@ -233,7 +236,6 @@ require (
233236
github.com/rs/cors v1.10.1 // indirect
234237
github.com/russross/blackfriday/v2 v2.1.0 // indirect
235238
github.com/samber/lo v1.39.0 // indirect
236-
github.com/sony/gobreaker v0.5.0 // indirect
237239
github.com/spaolacci/murmur3 v1.1.0 // indirect
238240
github.com/stretchr/testify v1.8.4 // indirect
239241
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect
@@ -268,12 +270,12 @@ require (
268270
go.uber.org/zap v1.26.0 // indirect
269271
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
270272
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc // indirect
271-
golang.org/x/mobile v0.0.0-20240320162201-c76e57eead38 // indirect
272-
golang.org/x/mod v0.16.0 // indirect
273-
golang.org/x/net v0.22.0 // indirect
273+
golang.org/x/mobile v0.0.0-20250106192035-c31d5b91ecc3 // indirect
274+
golang.org/x/mod v0.22.0 // indirect
275+
golang.org/x/net v0.34.0 // indirect
274276
golang.org/x/oauth2 v0.16.0 // indirect
275-
golang.org/x/text v0.14.0 // indirect
276-
golang.org/x/tools v0.19.0 // indirect
277+
golang.org/x/text v0.21.0 // indirect
278+
golang.org/x/tools v0.29.0 // indirect
277279
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
278280
gonum.org/v1/gonum v0.14.0 // indirect
279281
google.golang.org/appengine v1.6.8 // indirect

0 commit comments

Comments
 (0)