placeholder google gemini

This commit is contained in:
Adrien Bouvais 2024-05-18 09:33:02 +02:00
parent 9563af34f9
commit da6ead9ea9
14 changed files with 528 additions and 88 deletions

View File

@ -379,7 +379,7 @@ func LoadKeysHandler(c *fiber.Ctx) error {
if !checkIfLogin() {
return c.SendString("")
}
openaiExists, anthropicExists, mistralExists, groqExists := getExistingKeys()
openaiExists, anthropicExists, mistralExists, groqExists, gooseaiExists, googleExists := getExistingKeys()
out, err := pongo2.Must(pongo2.FromFile("views/partials/popover-keys.html")).Execute(pongo2.Context{
"IsLogin": checkIfLogin(),
@ -387,7 +387,9 @@ func LoadKeysHandler(c *fiber.Ctx) error {
"AnthropicExists": anthropicExists,
"MistralExists": mistralExists,
"GroqExists": groqExists,
"AnyExists": openaiExists || anthropicExists || mistralExists || groqExists,
"GooseaiExists": gooseaiExists,
"GoogleExists": googleExists,
"AnyExists": openaiExists || anthropicExists || mistralExists || groqExists || gooseaiExists || googleExists,
})
if err != nil {
c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{

View File

@ -24,7 +24,7 @@ func GeneratePlaceholderHandler(c *fiber.Ctx) error {
// Step 1 I create a User message and send it as output with a placeholder
// that will make a request to GenerateMultipleMessagesHandler when loading
message := c.FormValue("message", "")
selectedLLMIds := []string{"1e5a07c4-12fe-11ef-8da6-67d29b408c53", "3cd15ca8-1433-11ef-9f22-93f2b78c78de", "95774e62-1447-11ef-bfea-33f555b75c17", "af3d8686-1447-11ef-bfea-07d880a979ff"} // TODO Hanle in the UI
selectedLLMIds := []string{"1e5a07c4-12fe-11ef-8da6-67d29b408c53", "3cd15ca8-1433-11ef-9f22-93f2b78c78de", "95774e62-1447-11ef-bfea-33f555b75c17", "af3d8686-1447-11ef-bfea-07d880a979ff", "be7a922a-1478-11ef-a819-238de8775b87"} // TODO Hanle in the UI
var selectedLLMs []LLM
var selectedLLM LLM
@ -105,6 +105,8 @@ func GenerateMultipleMessagesHandler(c *fiber.Ctx) error {
addMessageFunc = addMistralMessage
case "groq":
addMessageFunc = addGroqMessage
case "gooseai":
addMessageFunc = addGooseaiMessage
}
var messageID edgedb.UUID

188
RequestGoogle.go Normal file
View File

@ -0,0 +1,188 @@
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"github.com/edgedb/edgedb-go"
)
type GoogleRequestMessage struct {
Role string `json:"role"`
Parts []GooglePart `json:"parts"`
}
type GooglePart struct {
Text string `json:"text"`
}
type GoogleChatCompletionRequest struct {
Messages []GoogleRequestMessage `json:"contents"`
}
type GoogleChatCompletionResponse struct {
ID string `json:"id"`
Object string `json:"object"`
Created int64 `json:"created"`
Model string `json:"model"`
Usage GoogleUsage `json:"usage"`
Choices []GoogleChoice `json:"choices"`
}
type GoogleUsage struct {
PromptTokens int32 `json:"prompt_tokens"`
CompletionTokens int32 `json:"completion_tokens"`
TotalTokens int32 `json:"total_tokens"`
}
type GoogleChoice struct {
Message Message `json:"message"`
FinishReason string `json:"finish_reason"`
Index int `json:"index"`
}
func addGoogleMessage(llm LLM, selected bool) edgedb.UUID {
Messages := getAllSelectedMessages()
chatCompletion, err := RequestGoogle(llm.Model.ModelID, Messages, float64(llm.Temperature))
if err != nil {
fmt.Println("Error:", err)
} else if len(chatCompletion.Choices) == 0 {
fmt.Println("No response from OpenAI")
id := insertBotMessage("No response from OpenAI", selected, llm.ID)
return id
} else {
Content := chatCompletion.Choices[0].Message.Content
id := insertBotMessage(Content, selected, llm.ID)
return id
}
return edgedb.UUID{}
}
func TestGoogleKey(apiKey string) bool {
url := "https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=" + apiKey
googlePart := GooglePart{
Text: "Hello",
}
// Convert messages to OpenAI format
googleMessages := []GoogleRequestMessage{
{
Role: "user",
Parts: []GooglePart{googlePart},
},
}
requestBody := GoogleChatCompletionRequest{
Messages: googleMessages,
}
jsonBody, err := json.Marshal(requestBody)
if err != nil {
return false
}
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonBody))
if err != nil {
return false
}
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return false
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return false
}
fmt.Println(string(body))
var chatCompletionResponse GoogleChatCompletionResponse
err = json.Unmarshal(body, &chatCompletionResponse)
if err != nil {
return false
}
if chatCompletionResponse.Usage.CompletionTokens == 0 {
return false
}
return true
}
func RequestGoogle(model string, messages []Message, temperature float64) (OpenaiChatCompletionResponse, error) {
var apiKey string
err := edgeClient.QuerySingle(edgeCtx, `
with
filtered_keys := (
select Key {
key
} filter .company.name = <str>$0 AND .<keys[is Setting].<setting[is User] = global currentUser
)
select filtered_keys.key limit 1
`, &apiKey, "openai")
if err != nil {
return OpenaiChatCompletionResponse{}, fmt.Errorf("error getting OpenAI API key: %w", err)
}
url := "https://api.openai.com/v1/chat/completions"
requestBody := OpenaiChatCompletionRequest{
Model: model,
Messages: Message2RequestMessage(messages),
Temperature: temperature,
}
jsonBody, err := json.Marshal(requestBody)
if err != nil {
return OpenaiChatCompletionResponse{}, fmt.Errorf("error marshaling JSON: %w", err)
}
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonBody))
if err != nil {
return OpenaiChatCompletionResponse{}, fmt.Errorf("error creating request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+apiKey)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return OpenaiChatCompletionResponse{}, fmt.Errorf("error sending request: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return OpenaiChatCompletionResponse{}, fmt.Errorf("error reading response body: %w", err)
}
var chatCompletionResponse OpenaiChatCompletionResponse
err = json.Unmarshal(body, &chatCompletionResponse)
if err != nil {
return OpenaiChatCompletionResponse{}, fmt.Errorf("error unmarshaling JSON: %w", err)
}
var usedModelInfo ModelInfo
edgeClient.QuerySingle(edgeCtx, `
Select ModelInfo {
inputPrice,
outputPrice
}
Filter ModelInfo.model = <str>$0
`, &usedModelInfo, model)
var inputCost float32 = float32(chatCompletionResponse.Usage.PromptTokens) * usedModelInfo.InputPrice
var outputCost float32 = float32(chatCompletionResponse.Usage.CompletionTokens) * usedModelInfo.OutputPrice
addUsage(inputCost, outputCost, chatCompletionResponse.Usage.PromptTokens, chatCompletionResponse.Usage.CompletionTokens, model)
return chatCompletionResponse, nil
}

155
RequestGooseai.go Normal file
View File

@ -0,0 +1,155 @@
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"github.com/edgedb/edgedb-go"
)
type GooseaiCompletionRequest struct {
Model string `json:"model"`
Prompt []string `json:"prompt"`
Temperature float64 `json:"temperature"`
}
type GooseaiCompletionResponse struct {
ID string `json:"id"`
Created int64 `json:"created"`
Model string `json:"model"`
Choices []GooseaiChoice `json:"choices"`
}
type GooseaiChoice struct {
Text string `json:"text"`
FinishReason string `json:"finish_reason"`
Index int `json:"index"`
}
func addGooseaiMessage(llm LLM, selected bool) edgedb.UUID {
Messages := getAllSelectedMessages()
chatCompletion, err := RequestGooseai(llm.Model.ModelID, Messages, float64(llm.Temperature))
if err != nil {
fmt.Println("Error:", err)
} else if len(chatCompletion.Choices) == 0 {
fmt.Println("No response from GooseAI")
id := insertBotMessage("No response from GooseAI", selected, llm.ID)
return id
} else {
Content := chatCompletion.Choices[0].Text
id := insertBotMessage(Content, selected, llm.ID)
return id
}
return edgedb.UUID{}
}
func TestGooseaiKey(apiKey string) bool {
url := "https://api.goose.ai/v1/engines/gpt-j-6b/completions"
requestBody := GooseaiCompletionRequest{
Model: "gpt-j-6b",
Prompt: []string{"Hello, how are you?"},
Temperature: 0,
}
jsonBody, err := json.Marshal(requestBody)
if err != nil {
return false
}
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonBody))
if err != nil {
return false
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+apiKey)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return false
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return false
}
// Print the response body
fmt.Println(string(body))
var chatCompletionResponse GooseaiCompletionResponse
err = json.Unmarshal(body, &chatCompletionResponse)
if err != nil {
return false
}
if chatCompletionResponse.Choices[0].Text == "" {
return false
}
return true
}
func RequestGooseai(model string, messages []Message, temperature float64) (GooseaiCompletionResponse, error) {
var apiKey string
err := edgeClient.QuerySingle(edgeCtx, `
with
filtered_keys := (
select Key {
key
} filter .company.name = "gooseai" AND .<keys[is Setting].<setting[is User] = global currentUser
)
select filtered_keys.key limit 1
`, &apiKey)
if err != nil {
return GooseaiCompletionResponse{}, fmt.Errorf("error getting GooseAI API key: %w", err)
}
url := "https://api.goose.ai/v1/engines/" + model + "/completions"
requestBody := GooseaiCompletionRequest{
Model: model,
Prompt: []string{messages[len(messages)-1].Content},
Temperature: temperature,
}
jsonBody, err := json.Marshal(requestBody)
if err != nil {
return GooseaiCompletionResponse{}, fmt.Errorf("error marshaling JSON: %w", err)
}
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonBody))
if err != nil {
return GooseaiCompletionResponse{}, fmt.Errorf("error creating request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+apiKey)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return GooseaiCompletionResponse{}, fmt.Errorf("error sending request: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return GooseaiCompletionResponse{}, fmt.Errorf("error reading response body: %w", err)
}
var chatCompletionResponse GooseaiCompletionResponse
err = json.Unmarshal(body, &chatCompletionResponse)
if err != nil {
return GooseaiCompletionResponse{}, fmt.Errorf("error unmarshaling JSON: %w", err)
}
addUsage(0, 0, 0, 0, model)
return chatCompletionResponse, nil
}

View File

@ -115,7 +115,7 @@ func RequestGroq(model string, messages []Message, temperature float64) (GroqCha
filtered_keys := (
select Key {
key
} filter .company.name = <str>$0
} filter .company.name = <str>$0 AND .<keys[is Setting].<setting[is User] = global currentUser
)
select filtered_keys.key limit 1
`, &apiKey, "groq")

View File

@ -121,7 +121,7 @@ func RequestMistral(model string, messages []Message, temperature float64) (Mist
filtered_keys := (
select Key {
key
} filter .company.name = <str>$0
} filter .company.name = <str>$0 AND .<keys[is Setting].<setting[is User] = global currentUser
)
select filtered_keys.key limit 1
`, &apiKey, "mistral")

View File

@ -115,7 +115,7 @@ func RequestOpenai(model string, messages []Message, temperature float64) (Opena
filtered_keys := (
select Key {
key
} filter .company.name = <str>$0
} filter .company.name = <str>$0 AND .<keys[is Setting].<setting[is User] = global currentUser
)
select filtered_keys.key limit 1
`, &apiKey, "openai")

198
main.go
View File

@ -137,33 +137,19 @@ func addKeys(c *fiber.Ctx) error {
anthropicKey := c.FormValue("anthropic_key")
mistralKey := c.FormValue("mistral_key")
groqKey := c.FormValue("groq_key")
gooseaiKey := c.FormValue("goose_key")
googleKey := c.FormValue("google_key")
var Exists bool
// Handle OpenAI key
if openaiKey != "" {
// Check if the OpenAI key already exists
err := edgeClient.QuerySingle(edgeCtx, `
select exists (
select global currentUser.setting.keys
filter .company.name = "openai" AND .key = <str>$0
);
`, &Exists, openaiKey)
if err != nil {
fmt.Println("Error in edgedb.QuerySingle: in addOpenaiKey: ", err)
return c.SendString("")
}
if Exists {
fmt.Println("OpenAI key already exists")
return c.SendString("")
}
if !TestOpenaiKey(openaiKey) {
fmt.Println("Invalid OpenAI API Key")
return c.SendString("Invalid OpenAI API Key\n")
}
// Check if the company key already exists
err = edgeClient.QuerySingle(edgeCtx, `
err := edgeClient.QuerySingle(edgeCtx, `
select exists (
select global currentUser.setting.keys
filter .company.name = "openai"
@ -188,16 +174,18 @@ func addKeys(c *fiber.Ctx) error {
}
} else {
err = edgeClient.Execute(edgeCtx, `
WITH
c := (SELECT Company FILTER .name = "openai" LIMIT 1)
UPDATE global currentUser.setting
SET {
keys += (
INSERT Key {
company := <str>$0,
key := <str>$1,
name := <str>$2,
company := c,
key := <str>$0,
name := "OpenAI API Key",
}
)
}`, "openai", openaiKey, "OpenAI API Key")
}`, openaiKey)
if err != nil {
fmt.Println("Error in edgedb.QuerySingle: in addOpenaiKey")
fmt.Println(err)
@ -207,29 +195,13 @@ func addKeys(c *fiber.Ctx) error {
// Handle Anthropic key
if anthropicKey != "" {
// Check if the OpenAI key already exists
err := edgeClient.QuerySingle(edgeCtx, `
select exists (
select global currentUser.setting.keys
filter .company.name = "anthropic" AND .key = <str>$0
);
`, &Exists, openaiKey)
if err != nil {
fmt.Println("Error in edgedb.QuerySingle: in addAnthropicKey: ", err)
return c.SendString("")
}
if Exists {
fmt.Println("Anthropic key already exists")
return c.SendString("")
}
if !TestAnthropicKey(anthropicKey) {
fmt.Println("Invalid Anthropic API Key")
return c.SendString("Invalid Anthropic API Key\n")
}
// Check if the company key already exists
err = edgeClient.QuerySingle(edgeCtx, `
err := edgeClient.QuerySingle(edgeCtx, `
select exists (
select global currentUser.setting.keys
filter .company.name = "anthropic"
@ -253,11 +225,13 @@ func addKeys(c *fiber.Ctx) error {
}
} else {
err = edgeClient.Execute(edgeCtx, `
WITH
c := (SELECT Company FILTER .name = "anthropic" LIMIT 1)
UPDATE global currentUser.setting
SET {
keys += (
INSERT Key {
company := "anthropic",
company := c,
key := <str>$0,
name := "Anthropic API Key",
}
@ -272,29 +246,13 @@ func addKeys(c *fiber.Ctx) error {
// Handle Mistral key
if mistralKey != "" {
// Check if the OpenAI key already exists
err := edgeClient.QuerySingle(edgeCtx, `
select exists (
select global currentUser.setting.keys
filter .company.name = "mistral" AND .key = <str>$0
);
`, &Exists, openaiKey)
if err != nil {
fmt.Println("Error in edgedb.QuerySingle: in addMistralKey: ", err)
return c.SendString("")
}
if Exists {
fmt.Println("Mistral key already exists")
return c.SendString("")
}
if !TestMistralKey(mistralKey) {
fmt.Println("Invalid Mistral API Key")
return c.SendString("Invalid Mistral API Key\n")
}
// Check if the company key already exists
err = edgeClient.QuerySingle(edgeCtx, `
err := edgeClient.QuerySingle(edgeCtx, `
select exists (
select global currentUser.setting.keys
filter .company.name = "mistral"
@ -318,11 +276,13 @@ func addKeys(c *fiber.Ctx) error {
}
} else {
err = edgeClient.Execute(edgeCtx, `
WITH
c := (SELECT Company FILTER .name = "mistral" LIMIT 1)
UPDATE global currentUser.setting
SET {
keys += (
INSERT Key {
company := "mistral",
company := c,
key := <str>$0,
name := "Mistral API Key",
}
@ -337,29 +297,13 @@ func addKeys(c *fiber.Ctx) error {
// Handle Groq key
if groqKey != "" {
// Check if the OpenAI key already exists
err := edgeClient.QuerySingle(edgeCtx, `
select exists (
select global currentUser.setting.keys
filter .company.name = "groq" AND .key = <str>$0
);
`, &Exists, openaiKey)
if err != nil {
fmt.Println("Error in edgedb.QuerySingle: in addGroqKey: ", err)
return c.SendString("")
}
if Exists {
fmt.Println("Groq key already exists")
return c.SendString("")
}
if !TestGroqKey(groqKey) {
fmt.Println("Invalid Groq API Key")
return c.SendString("Invalid Groq API Key\n")
}
// Check if the company key already exists
err = edgeClient.QuerySingle(edgeCtx, `
err := edgeClient.QuerySingle(edgeCtx, `
select exists (
select global currentUser.setting.keys
filter .company.name = "groq"
@ -383,11 +327,13 @@ func addKeys(c *fiber.Ctx) error {
}
} else {
err = edgeClient.Execute(edgeCtx, `
WITH
c := (SELECT Company FILTER .name = "groq" LIMIT 1)
UPDATE global currentUser.setting
SET {
keys += (
INSERT Key {
company := "groq",
company := c,
key := <str>$0,
name := "Groq API Key",
}
@ -400,5 +346,107 @@ func addKeys(c *fiber.Ctx) error {
}
}
// Handle Gooseai key
if gooseaiKey != "" {
if !TestGooseaiKey(gooseaiKey) {
fmt.Println("Invalid Gooseai API Key")
return c.SendString("Invalid Gooseai API Key\n")
}
// Check if the company key already exists
err := edgeClient.QuerySingle(edgeCtx, `
select exists (
select global currentUser.setting.keys
filter .company.name = "gooseai"
);
`, &Exists)
if err != nil {
fmt.Println("Error in edgedb.QuerySingle: in addGooseaiKey")
fmt.Println(err)
}
if Exists {
err = edgeClient.Execute(edgeCtx, `
UPDATE Key filter .company.name = "gooseai" AND .key = <str>$0
SET {
key := <str>$0,
}
`, gooseaiKey)
if err != nil {
fmt.Println("Error in edgedb.QuerySingle: in addGooseaiKey")
fmt.Println(err)
}
} else {
err = edgeClient.Execute(edgeCtx, `
WITH
c := (SELECT Company FILTER .name = "gooseai" LIMIT 1)
UPDATE global currentUser.setting
SET {
keys += (
INSERT Key {
company := c,
key := <str>$0,
name := "Gooseai API Key",
}
)
}`, gooseaiKey)
if err != nil {
fmt.Println("Error in edgedb.QuerySingle: in addGooseaiKey")
fmt.Println(err)
}
}
}
// Handle Google key
if googleKey != "" {
if !TestGoogleKey(googleKey) {
fmt.Println("Invalid Google API Key")
return c.SendString("Invalid Google API Key\n")
}
// Check if the company key already exists
err := edgeClient.QuerySingle(edgeCtx, `
select exists (
select global currentUser.setting.keys
filter .company.name = "google"
);
`, &Exists)
if err != nil {
fmt.Println("Error in edgedb.QuerySingle: in addGoogleKey")
fmt.Println(err)
}
if Exists {
err = edgeClient.Execute(edgeCtx, `
UPDATE Key filter .company.name = "google" AND .key = <str>$0
SET {
key := <str>$0,
}
`, googleKey)
if err != nil {
fmt.Println("Error in edgedb.QuerySingle: in addGoogleKey")
fmt.Println(err)
}
} else {
err = edgeClient.Execute(edgeCtx, `
WITH
c := (SELECT Company FILTER .name = "google" LIMIT 1)
UPDATE global currentUser.setting
SET {
keys += (
INSERT Key {
company := c,
key := <str>$0,
name := "Google API Key",
}
)
}`, googleKey)
if err != nil {
fmt.Println("Error in edgedb.QuerySingle: in addGoogleKey")
fmt.Println(err)
}
}
}
return c.SendString("<script>window.location.reload()</script>")
}

BIN
static/icons/azure.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

BIN
static/icons/gemini.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
static/icons/gooseai.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -38,15 +38,19 @@ func addCopyButtonsToCode(htmlContent string) string {
return updatedHTML
}
func getExistingKeys() (bool, bool, bool, bool) {
func getExistingKeys() (bool, bool, bool, bool, bool, bool) {
if edgeClient == nil {
return false, false, false, false
return false, false, false, false, false, false
}
var openaiExists bool
var anthropicExists bool
var mistralExists bool
var groqExists bool
var (
openaiExists bool
anthropicExists bool
mistralExists bool
groqExists bool
gooseaiExists bool
googleExists bool
)
err := edgeClient.QuerySingle(edgeCtx, `
select exists (
@ -93,7 +97,29 @@ func getExistingKeys() (bool, bool, bool, bool) {
panic(err)
}
return openaiExists, anthropicExists, mistralExists, groqExists
err = edgeClient.QuerySingle(edgeCtx, `
select exists (
select global currentUser.setting.keys
filter .company.name = "gooseai"
);
`, &gooseaiExists)
if err != nil {
fmt.Println("Error in edgedb.QuerySingle checking for gooseai: ", err)
panic(err)
}
err = edgeClient.QuerySingle(edgeCtx, `
select exists (
select global currentUser.setting.keys
filter .company.name = "google"
);
`, &googleExists)
if err != nil {
fmt.Println("Error in edgedb.QuerySingle checking for google: ", err)
panic(err)
}
return openaiExists, anthropicExists, mistralExists, groqExists, gooseaiExists, googleExists
}
func Message2RequestMessage(messages []Message) []RequestMessage {

View File

@ -40,6 +40,25 @@
</span>
</p>
</div>
<div class="field has-addons" title="Gemini is inailable because im in Europe">
<p class="control has-icons-left is-expanded">
<input class="input is-small {% if GoogleExists %}is-success{% endif %}" type="text"
disabled placeholder="Google API key" name="google_key" autocomplete="off">
<span class="icon is-small is-left">
<i class="fas fa-lock"></i>
</span>
</p>
</div>
<div class="field has-addons">
<p class="control has-icons-left is-expanded">
<input class="input is-small {% if GooseaiExists %}is-success{% endif %}" type="text"
placeholder="Gooseai API key" {%if not IsLogin %}disabled{% endif %}
name="goose_key" autocomplete="off">
<span class="icon is-small is-left">
<i class="fas fa-lock"></i>
</span>
</p>
</div>
<div class="field has-addons">
<p class="control has-icons-left is-expanded">
<input class="input is-small {% if GroqExists %}is-success{% endif %}" type="text"