Blodeuwedd Initial
This commit is contained in:
parent
e5e4084fae
commit
5f3ff65f6a
|
@ -0,0 +1,281 @@
|
|||
package blodeuwedd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"cwtch.im/cwtch/app"
|
||||
"cwtch.im/cwtch/event"
|
||||
"cwtch.im/cwtch/model"
|
||||
"cwtch.im/cwtch/model/attr"
|
||||
"cwtch.im/cwtch/model/constants"
|
||||
"cwtch.im/cwtch/peer"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"git.openprivacy.ca/openprivacy/connectivity"
|
||||
"git.openprivacy.ca/openprivacy/log"
|
||||
"os"
|
||||
"os/exec"
|
||||
path "path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
const BlodueweddSummary = event.Type("BlodeuweddSummary")
|
||||
const Summary = event.Field("Summary")
|
||||
const BlodueweddTranslation = event.Type("BlodeuweddTranslation")
|
||||
const Translation = event.Field("Translation")
|
||||
|
||||
// Functionality groups some common UI triggered functions for contacts...
|
||||
type Functionality struct {
|
||||
port *bufio.ReadWriter
|
||||
lock sync.Mutex
|
||||
process *exec.Cmd
|
||||
path string
|
||||
}
|
||||
|
||||
func Init(acn connectivity.ACN, appdir string) *Functionality {
|
||||
bld := new(Functionality)
|
||||
return bld
|
||||
}
|
||||
|
||||
func (bld *Functionality) OnACNStatusEvent(appl app.Application, e *event.Event) {
|
||||
|
||||
}
|
||||
|
||||
func (bld *Functionality) Enable(application app.Application, acn connectivity.ACN) {
|
||||
settings := application.ReadSettings()
|
||||
if settings.ExperimentsEnabled {
|
||||
if enabled, exists := settings.Experiments[constants.BlodeuweddExperiment]; enabled && exists {
|
||||
if settings.BlodeuweddPath != bld.path {
|
||||
bld.path = settings.BlodeuweddPath
|
||||
bld.runProcess()
|
||||
}
|
||||
// short circuit here so we don't kill the existing process...
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (bld *Functionality) UpdateSettings(application app.Application, acn connectivity.ACN) {
|
||||
bld.Enable(application, acn)
|
||||
}
|
||||
|
||||
func (f Functionality) EventsToRegister() []event.Type {
|
||||
return []event.Type{}
|
||||
}
|
||||
|
||||
func (f Functionality) ExperimentsToRegister() []string {
|
||||
return []string{constants.BlodeuweddExperiment}
|
||||
}
|
||||
|
||||
type BlodeweddJob struct {
|
||||
ID string
|
||||
Error string
|
||||
}
|
||||
|
||||
// Translate calls on the Blodewedd assistant to translate a particular message
|
||||
func (bld *Functionality) Translate(profile peer.CwtchPeer, conversation int, mid int, language string) string {
|
||||
log.Infof("Translating Message....")
|
||||
if profile.IsFeatureEnabled(constants.BlodeuweddExperiment) {
|
||||
message, _, err := profile.GetChannelMessage(conversation, 0, mid)
|
||||
if err == nil {
|
||||
contents := getMessageContents(message)
|
||||
ev := event.NewEvent(BlodueweddTranslation, map[event.Field]string{})
|
||||
ev.Data[event.ProfileOnion] = profile.GetOnion()
|
||||
ev.Data[event.ConversationID] = strconv.Itoa(conversation)
|
||||
ev.Data[event.Index] = strconv.Itoa(mid)
|
||||
log.Infof("Running Prompt....")
|
||||
go bld.runPrompt(profile, ev, "Translation", contents, language)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func getMessageContents(msg string) string {
|
||||
mw := new(model.MessageWrapper)
|
||||
err := json.Unmarshal([]byte(msg), &mw)
|
||||
if err == nil {
|
||||
if mw.Overlay == model.OverlayChat {
|
||||
return mw.Data
|
||||
}
|
||||
} else {
|
||||
log.Infof("ignored err %v", err)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Summarize calls on the Blodewedd assistant to summarize the last 20 message in a conversation.
|
||||
func (bld *Functionality) Summarize(profile peer.CwtchPeer, conversation int) string {
|
||||
log.Infof("Summarizing Conversation....")
|
||||
if profile.IsFeatureEnabled(constants.BlodeuweddExperiment) {
|
||||
log.Infof("Experiments Enabled Summarizing Conversation....")
|
||||
messages, err := profile.GetMostRecentMessages(conversation, 0, 0, 10)
|
||||
if err != nil {
|
||||
errJob := BlodeweddJob{
|
||||
ID: "",
|
||||
Error: err.Error(),
|
||||
}
|
||||
data, _ := json.Marshal(errJob)
|
||||
return string(data)
|
||||
}
|
||||
|
||||
transcript := ""
|
||||
for i := len(messages) - 1; i >= 0; i-- {
|
||||
msg := messages[i]
|
||||
sender := msg.Attr[constants.AttrAuthor]
|
||||
if ci, err := profile.FetchConversationInfo(sender); err == nil {
|
||||
if nickname, exists := ci.GetAttribute(attr.PublicScope, attr.ProfileZone, constants.Name); exists {
|
||||
nickname = strings.Map(func(r rune) rune {
|
||||
if r < unicode.MaxASCII {
|
||||
return r
|
||||
}
|
||||
return -1
|
||||
}, nickname)
|
||||
sender = nickname
|
||||
}
|
||||
}
|
||||
if sender == profile.GetOnion() {
|
||||
if nickname, exists := profile.GetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.Name); exists {
|
||||
sender = nickname
|
||||
}
|
||||
}
|
||||
|
||||
contents := getMessageContents(msg.Body)
|
||||
if contents != "" {
|
||||
transcript = fmt.Sprintf("%s%s: %s\n", transcript, sender, contents)
|
||||
}
|
||||
|
||||
}
|
||||
ev := event.NewEvent(BlodueweddSummary, map[event.Field]string{})
|
||||
ev.Data[event.ProfileOnion] = profile.GetOnion()
|
||||
ev.Data[event.ConversationID] = strconv.Itoa(conversation)
|
||||
|
||||
log.Infof("Running Prompt....")
|
||||
go bld.runPrompt(profile, ev, "Summarization", transcript, "")
|
||||
|
||||
successJob := BlodeweddJob{
|
||||
ID: ev.EventID,
|
||||
Error: "",
|
||||
}
|
||||
data, _ := json.Marshal(successJob)
|
||||
return string(data)
|
||||
}
|
||||
errJob := BlodeweddJob{
|
||||
ID: "",
|
||||
Error: "blodeuwedd experiment is not enabled",
|
||||
}
|
||||
data, _ := json.Marshal(errJob)
|
||||
return string(data)
|
||||
}
|
||||
|
||||
type BlodeuweddTaskConfig struct {
|
||||
TaskType string `json:"task_type""`
|
||||
Input string `json:"input""`
|
||||
Context string `json:"context"`
|
||||
}
|
||||
|
||||
func (bld *Functionality) runPrompt(profile peer.CwtchPeer, ev event.Event, task_type string, input string, context string) string {
|
||||
bld.lock.Lock()
|
||||
defer bld.lock.Unlock()
|
||||
log.Infof("Loading Blodeuwedd Model: %v", bld)
|
||||
if bld.process == nil || bld.port == nil {
|
||||
return "could not run blodeuwedd"
|
||||
}
|
||||
|
||||
// don't allow prompt to contain a terminating sequence
|
||||
log.Infof("Sending Prompt to Blodeuwedd")
|
||||
|
||||
taskConfig := &BlodeuweddTaskConfig{TaskType: task_type, Input: input, Context: context}
|
||||
data, _ := json.Marshal(taskConfig)
|
||||
|
||||
log.Infof("Sending the following task to blodeuwedd: %s", data)
|
||||
bld.port.WriteString(fmt.Sprintf("%s\n", data))
|
||||
bld.port.Flush()
|
||||
log.Infof("reading response")
|
||||
response := ""
|
||||
word := getWord(bld.port)
|
||||
for strings.TrimSpace(word) != "<BLODEUWEDD_END>" {
|
||||
log.Infof("reading response %v", word)
|
||||
switch task_type {
|
||||
case "Translation":
|
||||
ev.Data[Translation] = word
|
||||
case "Summarization":
|
||||
ev.Data[Summary] = word
|
||||
}
|
||||
profile.PublishEvent(ev)
|
||||
word = getWord(bld.port)
|
||||
}
|
||||
|
||||
log.Infof("Received Response from Blodeuwedd")
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
func getWord(reader *bufio.ReadWriter) string {
|
||||
word := []rune{}
|
||||
for {
|
||||
b, _, err := reader.ReadRune()
|
||||
if err != nil {
|
||||
return string(word)
|
||||
}
|
||||
word = append(word, b)
|
||||
if b == '\n' {
|
||||
return string(word)
|
||||
} else if b == ' ' {
|
||||
return string(word)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (bld *Functionality) killProcess() {
|
||||
if bld.process != nil {
|
||||
if bld.process.Process != nil {
|
||||
bld.process.Process.Kill()
|
||||
bld.process.Wait()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (bld *Functionality) runProcess() {
|
||||
|
||||
bld.lock.Lock()
|
||||
defer bld.lock.Unlock()
|
||||
|
||||
bld.killProcess()
|
||||
|
||||
executable := path.Join(bld.path, "blodeuwedd")
|
||||
log.Infof("starting blodeuwedd process: %v", executable)
|
||||
cmd := exec.Command(executable)
|
||||
cmd.Stderr = os.Stderr
|
||||
stdin, err := cmd.StdinPipe()
|
||||
if nil != err {
|
||||
log.Errorf("Error obtaining stdin: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if nil != err {
|
||||
log.Errorf("Error obtaining stdout: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
bld.port = bufio.NewReadWriter(bufio.NewReader(stdout), bufio.NewWriter(stdin))
|
||||
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
log.Error("error starting blodeuwedd process: %v %v", executable, err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Infof("loading blodeuwedd model")
|
||||
line, err := bld.port.ReadString('\n')
|
||||
if strings.TrimSpace(line) == "done" {
|
||||
log.Infof("blodeuwedd is loaded")
|
||||
} else {
|
||||
log.Errorf("failed to load blodeuwedd %s %v", line, err)
|
||||
}
|
||||
|
||||
bld.process = cmd
|
||||
log.Infof("Loading Blodeuwedd Model: %v", bld)
|
||||
}
|
|
@ -91,6 +91,8 @@ func main() {
|
|||
generatedBindings = generateAppFunction(generatedBindings, fName, args)
|
||||
case "exp":
|
||||
generatedBindings = generateExpFunction(generatedBindings, fName, experiment, args)
|
||||
case "json(exp)":
|
||||
generatedBindings = generateJsonExpFunction(generatedBindings, fName, experiment, args)
|
||||
case "(json)app":
|
||||
generatedBindings = generateJsonAppFunction(generatedBindings, fName, args)
|
||||
case "profile":
|
||||
|
@ -310,6 +312,37 @@ func {{FNAME}}({{GO_ARGS_SPEC}}) {
|
|||
return bindings
|
||||
}
|
||||
|
||||
func generateJsonExpFunction(bindings string, name string, exp string, argsTypes []string) string {
|
||||
appPrototype := `
|
||||
//export c_{{FNAME}}
|
||||
func c_{{FNAME}}({{C_ARGS}}) *C.char {
|
||||
return C.CString({{FNAME}}({{C2GO_ARGS}}))
|
||||
}
|
||||
|
||||
func {{FNAME}}({{GO_ARGS_SPEC}}) string {
|
||||
cwtchProfile := application.GetPeer(profile)
|
||||
if cwtchProfile != nil {
|
||||
return {{EXPERIMENT}}.{{LIBNAME}}(cwtchProfile, {{GO_ARG}})
|
||||
}
|
||||
return ""
|
||||
}
|
||||
`
|
||||
|
||||
cArgs, c2GoArgs, goSpec, gUse := mapArgs(argsTypes)
|
||||
appPrototype = strings.ReplaceAll(appPrototype, "{{FNAME}}", strings.TrimPrefix(name, "Enhanced"))
|
||||
appPrototype = strings.ReplaceAll(appPrototype, "{{EXPERIMENT}}", exp)
|
||||
appPrototype = strings.ReplaceAll(appPrototype, "{{LIBNAME}}", name)
|
||||
// We need to prepend a set of profile handle arguments...
|
||||
pArgs, c2GoPArg, goSpecP, _ := profileHandleArgPrototype()
|
||||
appPrototype = strings.ReplaceAll(appPrototype, "{{C_ARGS}}", strings.Join(append([]string{pArgs}, cArgs), ","))
|
||||
appPrototype = strings.ReplaceAll(appPrototype, "{{C2GO_ARGS}}", strings.Join(append([]string{c2GoPArg}, c2GoArgs), ","))
|
||||
appPrototype = strings.ReplaceAll(appPrototype, "{{GO_ARGS_SPEC}}", strings.Join(append([]string{goSpecP}, goSpec), ","))
|
||||
appPrototype = strings.ReplaceAll(appPrototype, "{{GO_ARG}}", gUse)
|
||||
|
||||
bindings += appPrototype
|
||||
return bindings
|
||||
}
|
||||
|
||||
func generateJsonAppFunction(bindings string, name string, argsTypes []string) string {
|
||||
appPrototype := `
|
||||
//export c_{{FNAME}}
|
||||
|
|
5
go.mod
5
go.mod
|
@ -10,6 +10,8 @@ require (
|
|||
github.com/mutecomm/go-sqlcipher/v4 v4.4.2
|
||||
)
|
||||
|
||||
replace cwtch.im/cwtch v0.19.3 => /home/sarah/workspace/src/cwtch.im/cwtch
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.0.0 // indirect
|
||||
git.openprivacy.ca/cwtch.im/tapir v0.6.0 // indirect
|
||||
|
@ -19,9 +21,6 @@ require (
|
|||
github.com/mimoo/StrobeGo v0.0.0-20220103164710-9a04d6ca976b // indirect
|
||||
go.etcd.io/bbolt v1.3.6 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d // indirect
|
||||
golang.org/x/mobile v0.0.0-20230301163155-e0f57694e12c // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b // indirect
|
||||
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 // indirect
|
||||
golang.org/x/tools v0.1.12 // indirect
|
||||
)
|
||||
|
|
10
go.sum
10
go.sum
|
@ -1,8 +1,4 @@
|
|||
cwtch.im/cwtch v0.18.0/go.mod h1:StheazFFY7PKqBbEyDVLhzWW6WOat41zV0ckC240c5Y=
|
||||
cwtch.im/cwtch v0.19.2 h1:H7DrSKQ9J7aNkKQkdyGGWckEV+dPKbL5PMRq0GoAn6I=
|
||||
cwtch.im/cwtch v0.19.2/go.mod h1:h8S7EgEM+8pE1k+XLB5jAFdIPlOzwoXEY0GH5mQye5A=
|
||||
cwtch.im/cwtch v0.19.3 h1:LRcJFgSw5LwUlOOcVtDC5mRb9NsuXUwNloGo5zNZb9A=
|
||||
cwtch.im/cwtch v0.19.3/go.mod h1:h8S7EgEM+8pE1k+XLB5jAFdIPlOzwoXEY0GH5mQye5A=
|
||||
filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
|
||||
filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek=
|
||||
filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
|
||||
|
@ -92,12 +88,8 @@ golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPh
|
|||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d h1:3qF+Z8Hkrw9sOhrFHti9TlB1Hkac1x+DNRkv0XQiFjo=
|
||||
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/mobile v0.0.0-20230301163155-e0f57694e12c h1:Gk61ECugwEHL6IiyyNLXNzmu8XslmRP2dS0xjIYhbb4=
|
||||
golang.org/x/mobile v0.0.0-20230301163155-e0f57694e12c/go.mod h1:aAjjkJNdrh3PMckS4B10TGS2nag27cbKR1y2BpUxsiY=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
|
@ -148,8 +140,6 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
|
|||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
||||
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
|
6
spec
6
spec
|
@ -42,6 +42,12 @@ import "cwtch.im/cwtch/functionality/filesharing"
|
|||
@(json)profile-experiment EnhancedGetSharedFiles filesharing conversation
|
||||
|
||||
|
||||
# Blodeuwedd Management
|
||||
!blodeuweddExperiment import "git.openprivacy.ca/cwtch.im/cwtch-autobindings/experiments/blodeuwedd"
|
||||
!blodeuweddExperiment global blodeuweddExperiment *blodeuwedd.Functionality blodeuwedd
|
||||
!blodeuweddExperiment json(exp) Summarize conversation
|
||||
!blodeuweddExperiment json(exp) Translate conversation int:message string:language
|
||||
|
||||
# Server Hosting Experiment
|
||||
!serverExperiment import "git.openprivacy.ca/cwtch.im/cwtch-autobindings/experiments/servers"
|
||||
!serverExperiment global serverExperiment *servers.ServersFunctionality servers
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"cwtch.im/cwtch/settings"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"git.openprivacy.ca/cwtch.im/cwtch-autobindings/experiments/blodeuwedd"
|
||||
"git.openprivacy.ca/cwtch.im/cwtch-autobindings/experiments/servers"
|
||||
"os"
|
||||
"strconv"
|
||||
|
@ -627,6 +628,8 @@ func (eh *EventHandler) startHandlingPeer(onion string) {
|
|||
eventBus.Subscribe(event.FileDownloaded, q)
|
||||
eventBus.Subscribe(event.TokenManagerInfo, q)
|
||||
eventBus.Subscribe(event.ProtocolEngineCreated, q)
|
||||
eventBus.Subscribe(blodeuwedd.BlodueweddSummary, q)
|
||||
eventBus.Subscribe(blodeuwedd.BlodueweddTranslation, q)
|
||||
go eh.forwardProfileMessages(onion, q)
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue