new version for apigo.cc/ai

This commit is contained in:
Star 2024-11-03 11:36:46 +08:00
parent 6d990dfb14
commit bbce32b3fc
11 changed files with 195 additions and 126 deletions

2
.gitignore vendored
View File

@ -7,3 +7,5 @@ package.json
*.bak.* *.bak.*
*.png *.png
*.jpg *.jpg
*.jpeg
*.xls

22
ai_test.go Normal file
View File

@ -0,0 +1,22 @@
package textin_test
import (
"testing"
_ "apigo.cc/ai"
_ "apigo.cc/ai/textin"
"apigo.cc/gojs"
_ "apigo.cc/gojs/console"
_ "apigo.cc/gojs/file"
_ "apigo.cc/gojs/util"
"github.com/ssgo/u"
)
func TestAI(t *testing.T) {
gojs.ExportForDev()
r, err := gojs.RunFile("ai_test.js")
if err != nil {
t.Fatal(err.Error())
}
println(u.Cyan(u.JsonP(r)))
}

8
ai_test.js Normal file
View File

@ -0,0 +1,8 @@
import file from 'apigo.cc/gojs/file'
import u from 'apigo.cc/gojs/util'
import co from 'apigo.cc/gojs/console'
import ai from 'apigo.cc/ai'
let r = ai.textin.scanTable(file.readBytes('table.jpeg'), { excel: true })
file.write('table.xls', u.unBase64(r.detail.excel))
return r

20
config.go Normal file
View File

@ -0,0 +1,20 @@
package textin
import (
_ "embed"
"apigo.cc/ai"
"github.com/ssgo/u"
)
//go:embed default.yml
var defaultYml string
func init() {
defaultConf := ai.AILoadConfig{}
u.Convert(u.UnYamlMap(defaultYml), &defaultConf)
ai.Register("textin", &ai.Agent{
ScanConfigs: defaultConf.Scan,
Scan: Scan,
})
}

5
default.yml Normal file
View File

@ -0,0 +1,5 @@
scan:
scanText:
type: text
scanTable:
type: table

13
go.mod
View File

@ -3,9 +3,11 @@ module apigo.cc/ai/textin
go 1.18 go 1.18
require ( require (
apigo.cc/gojs v0.0.1 apigo.cc/ai v0.0.2
apigo.cc/gojs/file v0.0.1 apigo.cc/gojs v0.0.4
github.com/ssgo/config v1.7.7 apigo.cc/gojs/console v0.0.1
apigo.cc/gojs/file v0.0.2
apigo.cc/gojs/util v0.0.3
github.com/ssgo/httpclient v1.7.7 github.com/ssgo/httpclient v1.7.7
github.com/ssgo/u v1.7.9 github.com/ssgo/u v1.7.9
) )
@ -14,12 +16,15 @@ require (
github.com/dlclark/regexp2 v1.11.4 // indirect github.com/dlclark/regexp2 v1.11.4 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd // indirect github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/ssgo/config v1.7.8 // indirect
github.com/ssgo/log v1.7.7 // indirect github.com/ssgo/log v1.7.7 // indirect
github.com/ssgo/standard v1.7.7 // indirect github.com/ssgo/standard v1.7.7 // indirect
github.com/ssgo/tool v0.4.27 // indirect github.com/ssgo/tool v0.4.27 // indirect
golang.org/x/net v0.30.0 // indirect golang.org/x/net v0.30.0 // indirect
golang.org/x/sys v0.26.0 // indirect golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.19.0 // indirect golang.org/x/text v0.19.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

128
scan.go Normal file
View File

@ -0,0 +1,128 @@
package textin
import (
"bytes"
"errors"
"fmt"
"strings"
"time"
"apigo.cc/ai"
"github.com/ssgo/httpclient"
"github.com/ssgo/u"
)
var hc = httpclient.GetClient(time.Second * 60)
func Scan(aiConf *ai.AIConfig, image []byte, conf map[string]any) (ai.ScanResult, error) {
if bytes.HasPrefix(image, []byte("http")) && bytes.Contains(image, []byte("://")) {
if r := hc.Get(u.String(image)); r.Error != nil {
return ai.ScanResult{}, r.Error
} else {
image = r.Bytes()
}
} else if bytes.HasPrefix(image, []byte("data:")) {
// TODO 解析BLOB
}
endpoint := "https://api.textin.com/ai/service/v2"
if aiConf.Endpoint != "" {
endpoint = aiConf.Endpoint
}
typ := u.String(conf["type"])
aksk := strings.SplitN(aiConf.ApiKey, ",", 2)
if len(aksk) == 1 {
aksk = append(aksk, aksk[0])
}
character := u.Int(conf["character"])
straighten := u.Int(conf["straighten"])
if typ == "table" {
output_order := u.String(conf["output_order"])
if output_order == "" {
output_order = "perpendicular"
}
table_type_hint := u.String(conf["table_type_hint"])
if table_type_hint == "" {
table_type_hint = "automatic"
}
excel := u.Int(conf["excel"])
resMap := hc.Post(fmt.Sprintf("%s/recognize/table/multipage?character=%d&straighten=%d&output_order=%s&table_type_hint=%s&excel=%d", endpoint, character, straighten, output_order, table_type_hint, excel), image, "x-ti-app-id", aksk[0], "x-ti-secret-code", aksk[1]).Map()
r := struct {
Code int
Message string
Duration float64
Result struct {
Pages []struct {
Tables []struct {
Lines []struct {
Text string
}
Table_cells []struct {
Text string
}
}
}
}
}{}
u.Convert(resMap, &r)
if r.Code != 200 {
return ai.ScanResult{}, errors.New(r.Message)
}
results := make([]string, 0)
for _, table := range r.Result.Pages[len(r.Result.Pages)-1].Tables {
partResults := make([]string, 0)
for _, line := range table.Lines {
partResults = append(partResults, line.Text)
}
if len(partResults) > 0 {
results = append(results, strings.Join(partResults, " "))
}
partResults = make([]string, 0)
for _, line := range table.Table_cells {
partResults = append(partResults, line.Text)
}
if len(partResults) > 0 {
results = append(results, strings.Join(partResults, " "))
}
}
detail := map[string]any{}
u.Convert(resMap["result"], &detail)
return ai.ScanResult{
Result: strings.Join(results, "\n"),
Detail: detail,
UsedTime: int64(r.Duration),
}, nil
} else {
// 通用文本识别
resMap := hc.Post(fmt.Sprintf("%s/recognize/multipage?character=%d&straighten=%d", endpoint, character, straighten), image, "x-ti-app-id", aksk[0], "x-ti-secret-code", aksk[1]).Map()
r := struct {
Code int
Message string
Duration float64
Result struct {
Pages []struct {
Lines []struct {
Text string
}
}
}
}{}
u.Convert(resMap, &r)
if r.Code != 200 {
return ai.ScanResult{}, errors.New(r.Message)
}
results := make([]string, 0)
for _, line := range r.Result.Pages[len(r.Result.Pages)-1].Lines {
results = append(results, line.Text)
}
detail := map[string]any{}
u.Convert(resMap["result"], &detail)
return ai.ScanResult{
Result: strings.Join(results, " "),
Detail: detail,
UsedTime: int64(r.Duration),
}, nil
}
}

View File

@ -1,6 +0,0 @@
import ti from "apigo.cc/ai/textin"
import file from "apigo.cc/gojs/file"
function main(){
return {a : ti.OCR(file.readBytes("img.png")), b : ti.OCRFromFile("img.png")}
}

View File

@ -1,86 +0,0 @@
package textin
import (
"apigo.cc/gojs"
"apigo.cc/gojs/goja"
_ "embed"
"errors"
"github.com/ssgo/config"
"github.com/ssgo/httpclient"
"github.com/ssgo/u"
"time"
)
//go:embed textin.ts
var textinTS string
type Conf struct {
AKey string
SKey string
}
var conf = Conf{}
var confAes = u.NewAes([]byte("?GQ$0K0GgLdO=f+~L68PLm$uhKr4'=tV"), []byte("VFs7@sK61cj^f?HZ"))
var keysIsSet = false
func SetSSKey(key, iv []byte) {
if !keysIsSet {
confAes = u.NewAes(key, iv)
keysIsSet = true
}
}
func init() {
obj := map[string]interface{}{
"name": "textin",
"OCR": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
args := gojs.MakeArgs(&argsIn, vm).Check(1)
return vm.ToValue(OCR(args.Bytes(0), vm))
},
"OCRFromFile": func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
path := gojs.MakeArgs(&argsIn, vm).Check(1).Path(0)
if file, err := u.ReadFileBytes(path); err != nil {
panic(vm.NewGoError(err))
} else {
return vm.ToValue(OCR(file, vm))
}
},
}
config.LoadConfig("textin", &conf)
conf.AKey = confAes.DecryptUrlBase64ToString(conf.AKey)
conf.SKey = confAes.DecryptUrlBase64ToString(conf.SKey)
//log.DefaultLogger.Info("conf", "", conf)
gojs.Register("apigo.cc/ai/textin", gojs.Module{
Object: obj,
TsCode: textinTS,
Example: "",
})
}
func OCR(file []byte, vm *goja.Runtime) string {
resMap := httpclient.GetClient(time.Second*30).Post("https://api.textin.com/ai/service/v2/recognize/multipage", file, "x-ti-app-id", conf.AKey, "x-ti-secret-code", conf.SKey).Map()
res := struct {
Result struct {
Pages []struct {
Lines []struct {
Text string
} `json:"lines"`
} `json:"pages"`
} `json:"result"`
}{}
//log.DefaultLogger.Info("Result", "", resMap)
if resMap["code"] != 200 {
panic(vm.NewGoError(errors.New(u.BRed(resMap["message"]))))
}
str := ""
u.Convert(resMap, &res)
if len(res.Result.Pages) < 1 {
panic(vm.NewGoError(errors.New(u.BRed("no characters recognized"))))
}
for _, k := range res.Result.Pages[len(res.Result.Pages)-1].Lines {
str += "" + k.Text
}
//log.DefaultLogger.Info("OCRResult", "Map", resMap, "Text", str)
return str
}

View File

@ -1,7 +0,0 @@
export default {
OCR,
OCRFromFile
}
function OCR(pictureData) {return ""}
function OCRFromFile(picturePath) {return ""}

View File

@ -1,22 +0,0 @@
package textin
import (
"apigo.cc/gojs"
_ "apigo.cc/gojs/file"
"fmt"
"github.com/ssgo/u"
"testing"
)
func TestExport(t *testing.T) {
gojs.ExportForDev()
}
func TestLLM(t *testing.T) {
r, err := gojs.RunFile("test.js")
if err != nil {
t.Fatal(err)
}
fmt.Println()
fmt.Println(u.JsonP(r))
}