feat: add AI assistant to generate and merge code commands and visual generation pages

This commit is contained in:
zhuyasen 2025-04-12 12:49:10 +08:00
parent 22f863765c
commit 3b663ebf24
22 changed files with 128 additions and 23 deletions

View File

@ -208,14 +208,15 @@ func NewUserExampleDao(collection *mongo.Collection, xCache cache.UserExampleCac
// nolint
var ErrnoAssistantMarker = fmt.Errorf("\n%s%s\n\n",
`No code requiring AI assistant generation has been detected. To enable the AI assistant to automatically generate code, the following two conditions must be met:
1. The function body must include the panic("implement me") marker.
2. A comment describing the function's purpose must be added above the function.
`No Go code requiring AI assistant generation was detected. To trigger the AI assistant, ensure the following conditions are met:
1. Define a function in Go code.
2. Add detailed function comments (used as AI prompts).
3. Add panic("implement me") inside the function body.
Example:`, fmt.Sprintf(`
%s
func MyFunc() {
func MyFuncName() {
%s
}`, color.HiCyanString("// Describe the specific functionality of the function"), color.HiCyanString(`panic("implement me")`)))

View File

@ -41,7 +41,7 @@ func GenerateCommand() *cobra.Command {
Use: "generate",
Short: "Generate code using AI assistant",
Long: "Generate code using AI assistant. Automatically locate the positions in Go files that require code implementation, and let the AI assistant generate the corresponding business logic based on the context.",
Example: color.HiBlackString(` # Generate code using deepseek, default model is deepseek-v3, you can specify deepseek-reasoner by --model.
Example: color.HiBlackString(` # Generate code using deepseek, default model is deepseek-chat, you can specify deepseek-reasoner by --model.
sponge assistant generate --type=deepseek --api-key=your-api-key --dir=your-project-dir
# Generate code using gemini, default model is gemini-2.5-pro-exp-03-25
@ -63,7 +63,8 @@ func GenerateCommand() *cobra.Command {
}
total := len(fileCodeMap)
if total == 0 {
return ErrnoAssistantMarker
fmt.Println(ErrnoAssistantMarker)
return nil
}
if maxAssistantNum > total {

View File

@ -55,7 +55,7 @@ func MergeAssistantCode() *cobra.Command {
return err
}
if len(fileMap) == 0 {
fmt.Printf("No %s assistant generated code found, nothing to merge.\n", m.assistantType)
fmt.Printf("\nNo %s assistant generated code found, nothing to merge, please generate code before merging.\n", m.assistantType)
return nil
}
@ -70,9 +70,9 @@ func MergeAssistantCode() *cobra.Command {
}
var deleteFiles []string
backupDir := getBackupDir()
if len(mergeCodes) > 0 {
fmt.Printf("Merge Go files:\n")
backupDir := getBackupDir()
fmt.Printf("Merged to Go files:\n")
for srcFile, code := range mergeCodes {
backupFile(srcFile, backupDir)
if err = os.WriteFile(srcFile, []byte(code), 0666); err != nil {
@ -89,6 +89,10 @@ func MergeAssistantCode() *cobra.Command {
deleteGenFiles(deleteFiles, m.assistantType)
}
if len(mergeCodes) > 0 {
fmt.Printf("\n[Tip] You can view the pre-merge Go code files here:\n %s\n\n", backupDir)
}
return nil
},
}

View File

@ -10,8 +10,8 @@ import (
func MergeCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "merge",
Short: "Merge the generated code into local go file",
Long: `Merge the generated code into local go file, you don't worry about it affecting
Short: "Merge the generated code into local Go file",
Long: `Merge the generated code into local Go file, you don't worry about it affecting
the logic code you have already written, in case of accidents, you can find the
pre-merge code in the directory /tmp/sponge_merge_backup_code`,
SilenceErrors: true,

View File

@ -91,8 +91,7 @@ func PluginsCommand() *cobra.Command {
return cmd
}
func checkInstallPlugins() ([]string, []string) {
var installedNames, lackNames = []string{}, []string{}
func checkInstallPlugins() (installedNames []string, lackNames []string) {
for _, name := range pluginNames {
_, err := gobash.Exec("which", name)
if err != nil {

View File

@ -65,6 +65,49 @@ func ListDbDrivers(c *gin.Context) {
response.Success(c, data)
}
// ListLLM list llm info
func ListLLM(c *gin.Context) {
llmTypes := []string{
"deepseek",
"chatgpt",
"gemini",
}
llmTypeOptions := []kv{}
for _, driver := range llmTypes {
llmTypeOptions = append(llmTypeOptions, kv{
Label: driver,
Value: driver,
})
}
allLLMOptions := map[string][]kv{
"deepseek": {
{Label: "deepseek-chat", Value: "deepseek-chat"},
{Label: "deepseek-reasoner", Value: "deepseek-reasoner"},
},
"chatgpt": {
{Label: "gpt-4o", Value: "gpt-4o"},
{Label: "gpt-4o-mini", Value: "gpt-4o-mini"},
{Label: "gpt-4-turbo", Value: "gpt-4-turbo"},
{Label: "gpt-4", Value: "gpt-4"},
{Label: "o1-mini", Value: "o1-mini"},
{Label: "o1-preview", Value: "o1-preview"},
},
"gemini": {
{Label: "gemini-2.5-pro-exp-03-25", Value: "gemini-2.5-pro-exp-03-25"},
{Label: "gemini-2.0-flash", Value: "gemini-2.0-flash"},
{Label: "gemini-2.0-flash-thinking-exp", Value: "gemini-2.0-flash-thinking-exp"},
{Label: "gemini-2.0-pro-exp", Value: "gemini-2.0-pro-exp"},
},
}
response.Success(c, gin.H{
"llmTypeOptions": llmTypeOptions,
"allLLMOptions": allLLMOptions,
})
}
// ListTables list tables
func ListTables(c *gin.Context) {
form := &dbInfoForm{}
@ -251,6 +294,42 @@ func handleGenerateCode(c *gin.Context, outPath string, arg string) {
}()
}
// HandleAssistant handle assistant generate and merge code
func HandleAssistant(c *gin.Context) {
form := &GenerateCodeForm{}
err := c.ShouldBindJSON(form)
if err != nil {
responseErr(c, err, errcode.InvalidParams)
return
}
args := strings.Split(form.Arg, " ")
params := parseCommandArgs(args)
ctx, _ := context.WithTimeout(context.Background(), time.Minute*30) // nolint
result := gobash.Run(ctx, "sponge", args...)
resultInfo := ""
count := 0
for v := range result.StdOut {
count++
if count == 1 { // first line is the command
continue
}
if strings.Contains(v, "Waiting for assistant responses") {
continue
}
resultInfo += v
}
if result.Err != nil {
responseErr(c, result.Err, errcode.InternalServerError)
return
}
recordObj().set(c.ClientIP(), form.Path, params)
response.Success(c, resultInfo)
}
// GetRecord generate run command record
func GetRecord(c *gin.Context) {
pathParam := c.Param("path")

View File

@ -60,9 +60,11 @@ func NewRouter(spongeAddr string, isLog bool) *gin.Engine {
apiV1 := r.Group("/api/v1")
apiV1.POST("/generate", GenerateCode)
apiV1.POST("/getTemplateInfo", GetTemplateInfo)
apiV1.POST("/assistant", HandleAssistant)
apiV1.POST("/uploadFiles", UploadFiles)
apiV1.POST("/listTables", ListTables)
apiV1.GET("/listDrivers", ListDbDrivers)
apiV1.GET("/listLLM", ListLLM)
apiV1.GET("/record/:path", GetRecord)
return r

View File

@ -37,6 +37,13 @@ type parameters struct {
DepProtoDir string `json:"depProtoDir"`
OnlyPrint bool `json:"onlyPrint"`
LLMType string `json:"llmType"`
LLMModel string `json:"llmModel"`
APIKey string `json:"apiKey"`
GoDir string `json:"goDir"`
GoFile string `json:"goFile"`
IsSpecifiedGoFile bool `json:"isSpecifiedGoFile"`
SuitedMonoRepo bool `json:"suitedMonoRepo"`
}
@ -63,7 +70,7 @@ func recordObj() *record {
}
func (r *record) set(ip string, commandType string, params *parameters) {
utils.SafeRunWithTimeout(time.Second*3, func(cancel context.CancelFunc) {
utils.SafeRunWithTimeout(time.Second*5, func(cancel context.CancelFunc) {
r.mux.Lock()
defer func() {
r.mux.Unlock()
@ -167,6 +174,18 @@ func parseCommandArgs(args []string) *parameters {
params.DepProtoDir = val
case "--suited-mono-repo":
params.SuitedMonoRepo = val == "true"
case "--type":
params.LLMType = val
case "--model":
params.LLMModel = val
case "--api-key":
params.APIKey = val
case "--dir":
params.GoDir = val
params.IsSpecifiedGoFile = false
case "--file":
params.GoFile = val
params.IsSpecifiedGoFile = true
}
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

View File

@ -1,2 +1,2 @@
<!DOCTYPE html><html><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>Sponge Generate Code</title><link rel=icon type=image/png sizes=32x32 href="/static/favicon.png?v=1.0"><script type=text/javascript src=/static/appConfig.js async></script><link href=/static/css/app.ed47037de66db185370fd15687055936.css rel=stylesheet></head><body style="margin: 0px; padding: 0px;"><style>.el-tooltip__popper {box-shadow: 3px 3px 10px 5px #d3d3d6;border-width: 0px !important;}
.el-tooltip__popper[x-placement^="top"] .popper__arrow:after {border-top-color: #dcdfe6 !important;}</style><div id=app></div><script type=text/javascript src=/static/js/manifest.2ae2e69a05c33dfc65f8.js></script><script type=text/javascript src=/static/js/vendor.d8cdb3748af43d2ae51a.js></script><script type=text/javascript src=/static/js/app.f70a2d3e4cd5fa3d2df2.js></script></body></html>
<!DOCTYPE html><html><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>Sponge Generate Code</title><link rel=icon type=image/png sizes=32x32 href="/static/favicon.png?v=1.0"><script type=text/javascript src=/static/appConfig.js async></script><link href=/static/css/app.e80eebc832ddd33bba273d897aa765f1.css rel=stylesheet></head><body style="margin: 0px; padding: 0px;"><style>.el-tooltip__popper {box-shadow: 3px 3px 10px 5px #d3d3d6;border-width: 0px !important;}
.el-tooltip__popper[x-placement^="top"] .popper__arrow:after {border-top-color: #dcdfe6 !important;}</style><div id=app></div><script type=text/javascript src=/static/js/manifest.2ae2e69a05c33dfc65f8.js></script><script type=text/javascript src=/static/js/vendor.d8cdb3748af43d2ae51a.js></script><script type=text/javascript src=/static/js/app.9cfd96e5dc1ceb9e7d1e.js></script></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
{"version":3,"sources":["webpack:///webpack/bootstrap f54dd04edd717b03c094"],"names":["parentJsonpFunction","window","chunkIds","moreModules","executeModules","moduleId","chunkId","result","i","resolves","length","installedChunks","push","Object","prototype","hasOwnProperty","call","modules","shift","__webpack_require__","s","installedModules","2","exports","module","l","m","c","d","name","getter","o","defineProperty","configurable","enumerable","get","n","__esModule","object","property","p","oe","err","console","error"],"mappings":"aACA,IAAAA,EAAAC,OAAA,aACAA,OAAA,sBAAAC,EAAAC,EAAAC,GAIA,IADA,IAAAC,EAAAC,EAAAC,EAAAC,EAAA,EAAAC,KACQD,EAAAN,EAAAQ,OAAoBF,IAC5BF,EAAAJ,EAAAM,GACAG,EAAAL,IACAG,EAAAG,KAAAD,EAAAL,GAAA,IAEAK,EAAAL,GAAA,EAEA,IAAAD,KAAAF,EACAU,OAAAC,UAAAC,eAAAC,KAAAb,EAAAE,KACAY,EAAAZ,GAAAF,EAAAE,IAIA,IADAL,KAAAE,EAAAC,EAAAC,GACAK,EAAAC,QACAD,EAAAS,OAAAT,GAEA,GAAAL,EACA,IAAAI,EAAA,EAAYA,EAAAJ,EAAAM,OAA2BF,IACvCD,EAAAY,IAAAC,EAAAhB,EAAAI,IAGA,OAAAD,GAIA,IAAAc,KAGAV,GACAW,EAAA,GAIA,SAAAH,EAAAd,GAGA,GAAAgB,EAAAhB,GACA,OAAAgB,EAAAhB,GAAAkB,QAGA,IAAAC,EAAAH,EAAAhB,IACAG,EAAAH,EACAoB,GAAA,EACAF,YAUA,OANAN,EAAAZ,GAAAW,KAAAQ,EAAAD,QAAAC,IAAAD,QAAAJ,GAGAK,EAAAC,GAAA,EAGAD,EAAAD,QAKAJ,EAAAO,EAAAT,EAGAE,EAAAQ,EAAAN,EAGAF,EAAAS,EAAA,SAAAL,EAAAM,EAAAC,GACAX,EAAAY,EAAAR,EAAAM,IACAhB,OAAAmB,eAAAT,EAAAM,GACAI,cAAA,EACAC,YAAA,EACAC,IAAAL,KAMAX,EAAAiB,EAAA,SAAAZ,GACA,IAAAM,EAAAN,KAAAa,WACA,WAA2B,OAAAb,EAAA,SAC3B,WAAiC,OAAAA,GAEjC,OADAL,EAAAS,EAAAE,EAAA,IAAAA,GACAA,GAIAX,EAAAY,EAAA,SAAAO,EAAAC,GAAsD,OAAA1B,OAAAC,UAAAC,eAAAC,KAAAsB,EAAAC,IAGtDpB,EAAAqB,EAAA,IAGArB,EAAAsB,GAAA,SAAAC,GAA8D,MAApBC,QAAAC,MAAAF,GAAoBA","file":"static/js/manifest.2ae2e69a05c33dfc65f8.js","sourcesContent":[" \t// install a JSONP callback for chunk loading\n \tvar parentJsonpFunction = window[\"webpackJsonp\"];\n \twindow[\"webpackJsonp\"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) {\n \t\t// add \"moreModules\" to the modules object,\n \t\t// then flag all \"chunkIds\" as loaded and fire callback\n \t\tvar moduleId, chunkId, i = 0, resolves = [], result;\n \t\tfor(;i < chunkIds.length; i++) {\n \t\t\tchunkId = chunkIds[i];\n \t\t\tif(installedChunks[chunkId]) {\n \t\t\t\tresolves.push(installedChunks[chunkId][0]);\n \t\t\t}\n \t\t\tinstalledChunks[chunkId] = 0;\n \t\t}\n \t\tfor(moduleId in moreModules) {\n \t\t\tif(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {\n \t\t\t\tmodules[moduleId] = moreModules[moduleId];\n \t\t\t}\n \t\t}\n \t\tif(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules, executeModules);\n \t\twhile(resolves.length) {\n \t\t\tresolves.shift()();\n \t\t}\n \t\tif(executeModules) {\n \t\t\tfor(i=0; i < executeModules.length; i++) {\n \t\t\t\tresult = __webpack_require__(__webpack_require__.s = executeModules[i]);\n \t\t\t}\n \t\t}\n \t\treturn result;\n \t};\n\n \t// The module cache\n \tvar installedModules = {};\n\n \t// objects to store loaded and loading chunks\n \tvar installedChunks = {\n \t\t2: 0\n \t};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, {\n \t\t\t\tconfigurable: false,\n \t\t\t\tenumerable: true,\n \t\t\t\tget: getter\n \t\t\t});\n \t\t}\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"/\";\n\n \t// on error function for async loading\n \t__webpack_require__.oe = function(err) { console.error(err); throw err; };\n\n\n\n// WEBPACK FOOTER //\n// webpack/bootstrap f54dd04edd717b03c094"],"sourceRoot":""}
{"version":3,"sources":["webpack:///webpack/bootstrap 47fb42a9a00032f75590"],"names":["parentJsonpFunction","window","chunkIds","moreModules","executeModules","moduleId","chunkId","result","i","resolves","length","installedChunks","push","Object","prototype","hasOwnProperty","call","modules","shift","__webpack_require__","s","installedModules","2","exports","module","l","m","c","d","name","getter","o","defineProperty","configurable","enumerable","get","n","__esModule","object","property","p","oe","err","console","error"],"mappings":"aACA,IAAAA,EAAAC,OAAA,aACAA,OAAA,sBAAAC,EAAAC,EAAAC,GAIA,IADA,IAAAC,EAAAC,EAAAC,EAAAC,EAAA,EAAAC,KACQD,EAAAN,EAAAQ,OAAoBF,IAC5BF,EAAAJ,EAAAM,GACAG,EAAAL,IACAG,EAAAG,KAAAD,EAAAL,GAAA,IAEAK,EAAAL,GAAA,EAEA,IAAAD,KAAAF,EACAU,OAAAC,UAAAC,eAAAC,KAAAb,EAAAE,KACAY,EAAAZ,GAAAF,EAAAE,IAIA,IADAL,KAAAE,EAAAC,EAAAC,GACAK,EAAAC,QACAD,EAAAS,OAAAT,GAEA,GAAAL,EACA,IAAAI,EAAA,EAAYA,EAAAJ,EAAAM,OAA2BF,IACvCD,EAAAY,IAAAC,EAAAhB,EAAAI,IAGA,OAAAD,GAIA,IAAAc,KAGAV,GACAW,EAAA,GAIA,SAAAH,EAAAd,GAGA,GAAAgB,EAAAhB,GACA,OAAAgB,EAAAhB,GAAAkB,QAGA,IAAAC,EAAAH,EAAAhB,IACAG,EAAAH,EACAoB,GAAA,EACAF,YAUA,OANAN,EAAAZ,GAAAW,KAAAQ,EAAAD,QAAAC,IAAAD,QAAAJ,GAGAK,EAAAC,GAAA,EAGAD,EAAAD,QAKAJ,EAAAO,EAAAT,EAGAE,EAAAQ,EAAAN,EAGAF,EAAAS,EAAA,SAAAL,EAAAM,EAAAC,GACAX,EAAAY,EAAAR,EAAAM,IACAhB,OAAAmB,eAAAT,EAAAM,GACAI,cAAA,EACAC,YAAA,EACAC,IAAAL,KAMAX,EAAAiB,EAAA,SAAAZ,GACA,IAAAM,EAAAN,KAAAa,WACA,WAA2B,OAAAb,EAAA,SAC3B,WAAiC,OAAAA,GAEjC,OADAL,EAAAS,EAAAE,EAAA,IAAAA,GACAA,GAIAX,EAAAY,EAAA,SAAAO,EAAAC,GAAsD,OAAA1B,OAAAC,UAAAC,eAAAC,KAAAsB,EAAAC,IAGtDpB,EAAAqB,EAAA,IAGArB,EAAAsB,GAAA,SAAAC,GAA8D,MAApBC,QAAAC,MAAAF,GAAoBA","file":"static/js/manifest.2ae2e69a05c33dfc65f8.js","sourcesContent":[" \t// install a JSONP callback for chunk loading\n \tvar parentJsonpFunction = window[\"webpackJsonp\"];\n \twindow[\"webpackJsonp\"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) {\n \t\t// add \"moreModules\" to the modules object,\n \t\t// then flag all \"chunkIds\" as loaded and fire callback\n \t\tvar moduleId, chunkId, i = 0, resolves = [], result;\n \t\tfor(;i < chunkIds.length; i++) {\n \t\t\tchunkId = chunkIds[i];\n \t\t\tif(installedChunks[chunkId]) {\n \t\t\t\tresolves.push(installedChunks[chunkId][0]);\n \t\t\t}\n \t\t\tinstalledChunks[chunkId] = 0;\n \t\t}\n \t\tfor(moduleId in moreModules) {\n \t\t\tif(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {\n \t\t\t\tmodules[moduleId] = moreModules[moduleId];\n \t\t\t}\n \t\t}\n \t\tif(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules, executeModules);\n \t\twhile(resolves.length) {\n \t\t\tresolves.shift()();\n \t\t}\n \t\tif(executeModules) {\n \t\t\tfor(i=0; i < executeModules.length; i++) {\n \t\t\t\tresult = __webpack_require__(__webpack_require__.s = executeModules[i]);\n \t\t\t}\n \t\t}\n \t\treturn result;\n \t};\n\n \t// The module cache\n \tvar installedModules = {};\n\n \t// objects to store loaded and loading chunks\n \tvar installedChunks = {\n \t\t2: 0\n \t};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, {\n \t\t\t\tconfigurable: false,\n \t\t\t\tenumerable: true,\n \t\t\t\tget: getter\n \t\t\t});\n \t\t}\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"/\";\n\n \t// on error function for async loading\n \t__webpack_require__.oe = function(err) { console.error(err); throw err; };\n\n\n\n// WEBPACK FOOTER //\n// webpack/bootstrap 47fb42a9a00032f75590"],"sourceRoot":""}