Repository: ollama/ollama
Pull request: fix: set template without triple quotes

this changes updates `/set` to better handle multiline strings. `/set` now correctly sets template or system without using triple quotes

```
>>> /set template {{ .Prompt }}
Set prompt template.
```

additionally, use a strings.Builder instead of concatenating string values for prompt building

```
>>> """hello
... world"""
```

```
>>> """
... hello
... world
... """
```

```
>>> /set system """
... you are a llama
... """
```


```
>>> /set template """
... {{.System}}
... User: {{.Prompt}}
... Assistant: {{.Response}}
... """
```

resolves #1609
resolves #1607

Changed files:
filename: cmd/interactive.go
patch: @@ -139,8 +139,8 @@ func generateInteractive(cmd *cobra.Command, opts generateOptions) error {
 	fmt.Print(readline.StartBracketedPaste)
 	defer fmt.Printf(readline.EndBracketedPaste)
 
+	var sb strings.Builder
 	var multiline MultilineState
-	var prompt string
 
 	for {
 		line, err := scanner.Readline()
@@ -154,46 +154,49 @@ func generateInteractive(cmd *cobra.Command, opts generateOptions) error {
 			}
 
 			scanner.Prompt.UseAlt = false
-			prompt = ""
+			sb.Reset()
 
 			continue
 		case err != nil:
 			return err
 		}
 
 		switch {
-		case strings.HasPrefix(prompt, `"""`):
-			// if the prompt so far starts with """ then we're in multiline mode
-			// and we need to keep reading until we find a line that ends with """
-			cut, found := strings.CutSuffix(line, `"""`)
-			prompt += cut
-
-			if !found {
-				prompt += "\n"
+		case multiline != MultilineNone:
+			// check if there's a multiline terminating string
+			before, ok := strings.CutSuffix(line, `"""`)
+			sb.WriteString(before)
+			if !ok {
+				fmt.Fprintln(&sb)
 				continue
 			}
 
-			prompt = strings.TrimPrefix(prompt, `"""`)
-			scanner.Prompt.UseAlt = false
-
 			switch multiline {
 			case MultilineSystem:
-				opts.System = prompt
-				prompt = ""
+				opts.System = sb.String()
 				fmt.Println("Set system message.")
+				sb.Reset()
 			case MultilineTemplate:
-				opts.Template = prompt
-				prompt = ""
+				opts.Template = sb.String()
 				fmt.Println("Set prompt template.")
+				sb.Reset()
 			}
+
 			multiline = MultilineNone
-		case strings.HasPrefix(line, `"""`) && len(prompt) == 0:
-			scanner.Prompt.UseAlt = true
-			multiline = MultilinePrompt
-			prompt += line + "\n"
-			continue
+			scanner.Prompt.UseAlt = false
+		case strings.HasPrefix(line, `"""`):
+			line := strings.TrimPrefix(line, `"""`)
+			line, ok := strings.CutSuffix(line, `"""`)
+			sb.WriteString(line)
+			if !ok {
+				// no multiline terminating string; need more input
+				fmt.Fprintln(&sb)
+				multiline = MultilinePrompt
+				scanner.Prompt.UseAlt = true
+				break
+			}
 		case scanner.Pasting:
-			prompt += line + "\n"
+			fmt.Fprintln(&sb, line)
 			continue
 		case strings.HasPrefix(line, "/list"):
 			args := strings.Fields(line)
@@ -251,33 +254,41 @@ func generateInteractive(cmd *cobra.Command, opts generateOptions) error {
 						usageSet()
 						continue
 					}
+
+					if args[1] == "system" {
+						multiline = MultilineSystem
+					} else if args[1] == "template" {
+						multiline = MultilineTemplate
+					}
+
 					line := strings.Join(args[2:], " ")
-					line = strings.TrimPrefix(line, `"""`)
-					if strings.HasPrefix(args[2], `"""`) {
-						cut, found := strings.CutSuffix(line, `"""`)
-						prompt += cut
-						if found {
-							if args[1] == "system" {
-								opts.System = prompt
-								fmt.Println("Set system message.")
-							} else {
-								opts.Template = prompt
-								fmt.Println("Set prompt template.")
-							}
-							prompt = ""
-						} else {
-							prompt = `"""` + prompt + "\n"
-							if args[1] == "system" {
-								multiline = MultilineSystem
-							} else {
-								multiline = MultilineTemplate
-							}
-							scanner.Prompt.UseAlt = true
-						}
+					line, ok := strings.CutPrefix(line, `"""`)
+					if !ok {
+						multiline = MultilineNone
 					} else {
-						opts.System = line
+						// only cut suffix if the line is multiline
+						line, ok = strings.CutSuffix(line, `"""`)
+						if ok {
+							multiline = MultilineNone
+						}
+					}
+
+					sb.WriteString(line)
+					if multiline != MultilineNone {
+						scanner.Prompt.UseAlt = true
+						continue
+					}
+
+					if args[1] == "system" {
+						opts.System = sb.String()
 						fmt.Println("Set system message.")
+					} else if args[1] == "template" {
+						opts.Template = sb.String()
+						fmt.Println("Set prompt template.")
 					}
+
+					sb.Reset()
+					continue
 				default:
 					fmt.Printf("Unknown command '/set %s'. Type /? for help\n", args[1])
 				}
@@ -390,20 +401,20 @@ func generateInteractive(cmd *cobra.Command, opts generateOptions) error {
 				}
 			}
 
-			if isFile {
-				prompt += line
-			} else {
+			if !isFile {
 				fmt.Printf("Unknown command '%s'. Type /? for help\n", args[0])
 				continue
 			}
+
+			sb.WriteString(line)
 		default:
-			prompt += line
+			sb.WriteString(line)
 		}
 
-		if len(prompt) > 0 && multiline == MultilineNone {
-			opts.Prompt = prompt
+		if sb.Len() > 0 && multiline == MultilineNone {
+			opts.Prompt = sb.String()
 			if multiModal {
-				newPrompt, images, err := extractFileData(prompt)
+				newPrompt, images, err := extractFileData(sb.String())
 				if err != nil {
 					return err
 				}
@@ -419,15 +430,16 @@ func generateInteractive(cmd *cobra.Command, opts generateOptions) error {
 				if len(opts.Images) == 0 {
 					fmt.Println("This model requires you to add a jpeg, png, or svg image.")
 					fmt.Println()
-					prompt = ""
+					sb.Reset()
 					continue
 				}
 			}
+
 			if err := generate(cmd, opts); err != nil {
 				return err
 			}
 
-			prompt = ""
+			sb.Reset()
 		}
 	}
 }